Skip to content

Commit

Permalink
Merge pull request #42 from jhLim97/transaction-detail
Browse files Browse the repository at this point in the history
[Sprint 1] 거래 내역을 추가 및 수정하는 모달에 대한 UI 구현
  • Loading branch information
jhLim97 authored Feb 5, 2022
2 parents 834ebf8 + ae169da commit 67dcd8f
Show file tree
Hide file tree
Showing 15 changed files with 421 additions and 9 deletions.
3 changes: 3 additions & 0 deletions frontend/public/assets/back-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions frontend/src/components/Header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ const Header = ({ current }) => {
</ArrowButton>
</DateBox>
<PageBox>
<PageTarget to="/" isSelected={current === 'main'}>
<PageTarget to="/" selected={current === 'main'}>
내역
</PageTarget>
<PageTarget to="/calendar" isSelected={current === 'calendar'}>
<PageTarget to="/calendar" selected={current === 'calendar'}>
달력
</PageTarget>
<PageTarget to="/statistics" isSelected={current === 'statistics'}>
<PageTarget to="/statistics" selected={current === 'statistics'}>
통계
</PageTarget>
</PageBox>
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/Header/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Title = styled.h1`
`;

export const DateBox = styled.div`
margin-top: 7px;
margin: 7px 0px 0px 0px;
display: flex;
flex-direction: row;
Expand All @@ -48,10 +48,12 @@ export const DateBox = styled.div`
`;

export const ArrowButton = styled.button`
margin-top: 3px;
margin: 3px 0px 0px 15px;
`;

export const Date = styled.strong`
margin-left: 15px;
font-weight: bold;
color: ${({ theme }) => theme.color.white};
`;
Expand All @@ -77,7 +79,7 @@ export const PageTarget = styled(Link)`
color: ${({ theme }) =>
(props) =>
props.isSelected ? theme.color.white : theme.color.black};
props.selected ? theme.color.white : theme.color.black};
&:hover {
cursor: pointer;
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/components/Transaction/index.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import React from 'react';
import { useSetRecoilState } from 'recoil';
import PropTypes from 'prop-types';

import { modalState } from '../../recoil/modal/atom';
import { Wrapper, OuterBox, Category, InnerBox, Content, Method, Cost } from './style';

const Transaction = ({ transaction }) => {
const setCurrentModal = useSetRecoilState(modalState);

const changeModalState = () => {
const state = {
current: 'transaction',
props: transaction,
};
setCurrentModal(state);
};

return (
<Wrapper>
<Wrapper onClick={changeModalState}>
<OuterBox>
<Category>{transaction.category}</Category>
<InnerBox>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/components/TransactionModal/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useRecoilState } from 'recoil';

import { modalState } from '../../recoil/modal/atom';

export const useModal = () => {
const [modal, setModal] = useRecoilState(modalState);

const closeModal = () => {
setModal({ current: null, props: null });
};

return [modal, closeModal];
};
24 changes: 24 additions & 0 deletions frontend/src/components/TransactionModal/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import TransactionUpdateForm from '../TransactionUpdateForm';
import { useModal } from './hooks';
import { Wrapper, BackgroundDim } from './style';

const TransactionModal = () => {
const [modal, closeModal] = useModal();

if (modal.props == null) return null;
return (
<Wrapper active={modal.current === 'transaction'} data-testid="modal">
<BackgroundDim data-testid="dim" />
<TransactionUpdateForm
transaction={modal.props}
onUpdate={() => null}
onDelete={() => null}
onCancle={closeModal}
/>
</Wrapper>
);
};

export default TransactionModal;
18 changes: 18 additions & 0 deletions frontend/src/components/TransactionModal/style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styled from 'styled-components';

export const Wrapper = styled.div`
display: ${(props) => (props.active ? 'block' : 'none')};
`;

export const BackgroundDim = styled.div`
position: fixed;
z-index: 1;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: ${({ theme }) => theme.color.black};
opacity: 0.57;
`;
49 changes: 49 additions & 0 deletions frontend/src/components/TransactionModal/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from 'styled-components';
import { render, screen } from '../../test-utils';
import '@testing-library/jest-dom';

import TransactionModal from '.';
import { modalState } from '../../recoil/modal/atom';
import theme from '../../styles/theme';

const TEST_DATA = {
category: '카페/간식',
color: '#D092E2',
content: '녹차 스무디',
method: '현금',
sign: '-',
cost: '5700',
date: '2022-01-28',
};

describe('TransactionModal 컴포넌트 테스트', () => {
const initializeState = ({ set }) => {
set(modalState, {
current: 'transaction',
props: TEST_DATA,
});
};

it('배경 Dim, 입력 폼이 표시된다.', () => {
render(
<RecoilRoot initializeState={initializeState}>
<ThemeProvider theme={theme}>
<TransactionModal />
</ThemeProvider>
</RecoilRoot>,
);

const BackgroundDim = screen.getByTestId('dim');
const transactionForm = screen.getByRole('form', { name: /transaction/i });
expect(BackgroundDim).toBeInTheDocument();
expect(transactionForm).toBeInTheDocument();
});

it('현재 선택된 modal의 props 속성이 없으면 모달이 표시되지 않는다.', () => {
render(<TransactionModal />);
const modalDiv = screen.queryByTestId('modal');
expect(modalDiv).not.toBeInTheDocument();
});
});
94 changes: 94 additions & 0 deletions frontend/src/components/TransactionUpdateForm/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';

import back from '../../../public/assets/back-button.svg';
import { Wrapper, Element, BackImg, Input, ButtonContainer, DecisionButton } from './style';

const TransactionUpdateForm = ({ transaction, onUpdate, onDelete, onCancle }) => {
const shapedForm = (elements) => {
const data = {};
Array.from(elements).forEach((element) => {
if (element.value) {
data[element.id] = element.value;
}
});
return data;
};

const handleUpdateSubmit = (e) => {
e.preventDefault();

const form = e.target;
const data = shapedForm(form.elements);
onUpdate(data);
};

return (
<Wrapper aria-label="transactionUpdate" onSubmit={handleUpdateSubmit}>
<Element>
<button type="button" aria-label="back" onClick={onCancle}>
<BackImg src={back} />
</button>
</Element>
<Element>
<label htmlFor="date">날짜</label>
<Input
type="text"
id="date"
placeholder="YYYY-MM-DD"
autoComplete="off"
defaultValue={transaction.date}
/>
</Element>
<Element>
<label htmlFor="category">카테고리</label>
<Input
type="text"
id="category"
placeholder="입력하세요."
autoComplete="off"
defaultValue={transaction.category}
/>
</Element>
<Element>
<label htmlFor="content">내용</label>
<Input
type="text"
id="content"
placeholder="입력하세요."
autoComplete="off"
defaultValue={transaction.content}
/>
</Element>
<Element>
<label htmlFor="method">결제수단</label>
<Input
type="text"
id="method"
placeholder="입력하세요."
autoComplete="off"
defaultValue={transaction.method}
/>
</Element>
<Element>
<label htmlFor="cost">금액</label>
<Input
type="text"
id="cost"
placeholder="숫자만 기입하세요.(ex 3000)"
autoComplete="off"
defaultValue={transaction.cost}
/>
</Element>
<ButtonContainer>
<DecisionButton type="button" action="delete" onClick={onDelete}>
삭제
</DecisionButton>
<DecisionButton type="submit" action="update">
수정
</DecisionButton>
</ButtonContainer>
</Wrapper>
);
};

export default TransactionUpdateForm;
88 changes: 88 additions & 0 deletions frontend/src/components/TransactionUpdateForm/style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import styled from 'styled-components';

import { MAX_MOBILE_DEVICE } from '../../utils/constant/device-size';

export const Wrapper = styled.form`
position: fixed;
z-index: 2;
width: 700px;
height: 500px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.color.white};
box-shadow: ${({ theme }) => theme.shadow.thick};
@media screen and (max-width: ${MAX_MOBILE_DEVICE}px) {
width: 100vw;
height: 100vh;
top: 0;
left: 0;
transform: none;
justify-content: space-evenly;
box-shadow: none;
}
`;

export const Element = styled.div`
margin-bottom: 10px;
width: 250px;
display: flex;
flex-direction: column;
font-weight: bold;
@media screen and (max-width: ${MAX_MOBILE_DEVICE}px) {
width: 80vw;
height: 12vh;
justify-content: center;
}
`;

export const BackImg = styled.img`
float: left;
`;

export const Input = styled.input`
margin-top: 5px;
padding: 10px;
border: 1px solid ${({ theme }) => theme.color.brigtenL1Gray};
border-radius: 10px;
background: ${({ theme }) => theme.color.whiteSmoke};
@media screen and (max-width: ${MAX_MOBILE_DEVICE}px) {
height: 80%;
}
`;

export const ButtonContainer = styled.div`
padding-left: 0px;
width: 250px;
display: flex;
flex-direction: row;
justify-content: space-between;
@media screen and (max-width: ${MAX_MOBILE_DEVICE}px) {
width: 80vw;
height: 12vh;
}
`;

export const DecisionButton = styled.button`
font-weight: bold;
font-size: ${({ theme }) => theme.fontSize.default};
color: ${(props) => (props.action === 'update' ? '#0990d6' : '#ff000f')};
`;
Loading

0 comments on commit 67dcd8f

Please sign in to comment.