Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sprint 1] 거래 내역을 추가 및 수정하는 모달에 대한 UI 구현 #5 #42

Merged
merged 23 commits into from
Feb 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1c0b501
warning에 따른 props명 수정
jhLim97 Feb 4, 2022
ad873eb
fix: 헤더의 날짜 및 arrow 버튼 margin 간격 재조정
jhLim97 Feb 5, 2022
73a5a86
chore: theme color 속성 추가
jhLim97 Feb 5, 2022
5e94456
chore: GlobalStyles button padding 초기화 속성 추가
jhLim97 Feb 5, 2022
62b894a
chore: 뒤로가기 아이콘 추가
jhLim97 Feb 5, 2022
2b45a4c
feat: 모달 상태관리를 위한 recoil atom 구현
jhLim97 Feb 5, 2022
da22f04
feat: 특정 거래내역 클릭 시 해당내역에 대한 상세내역을 모달로 띄우기 위한 이벤트 추가
jhLim97 Feb 5, 2022
21aec12
feat: 거래내역 클릭 시 해당 거래내역의 상세정보를 보여주는 모달창 구현
jhLim97 Feb 5, 2022
3b1c869
feat: 거래내역 모달 추가
jhLim97 Feb 5, 2022
22b5da3
feat: 거래내역 모달창 반응형 요소 적용
jhLim97 Feb 5, 2022
41e8308
refactor: style 파일 분리
jhLim97 Feb 5, 2022
21555f8
refactor: Transaction 모달의 Form 요소 별도의 컴포넌트로 분리
jhLim97 Feb 5, 2022
fab4ada
refactor: 트랜잭션 모달관련 파일명 수정 및 props 요소 추가
jhLim97 Feb 5, 2022
a3ae07f
feat: testid 및 form 태그에 aria-label 추가
jhLim97 Feb 5, 2022
35db825
test: 거래내역 모달 컴포넌트 테스트코드 작성
jhLim97 Feb 5, 2022
6c55733
거래내역 모달 컴포넌트 로컬 커스텀 훅 구현
jhLim97 Feb 5, 2022
e7b2c52
feat: props 형태 변경
jhLim97 Feb 5, 2022
b79ead6
test: TransactionUpateForm 기본요소 렌더링 테스트코드 작성
jhLim97 Feb 5, 2022
8759b0b
test: 거래내역 업데이트 form의 경우 input에 값도 적절히 들어갔는지에 대한 테스트코드 추가
jhLim97 Feb 5, 2022
a86f300
fix: 버튼 type 지정 및 props 변경
jhLim97 Feb 5, 2022
36c0a9c
test: 뒤로가기 버튼을 클릭하는 로직에 대한 테스트코드 작성
jhLim97 Feb 5, 2022
ef628bc
test: 삭제 버튼을 누르는 경우 로직에 대한 테스트코드 작성
jhLim97 Feb 5, 2022
ae169da
test: 수정 버튼을 누르는 경우 form 데이터와 함께 수행되는 로직에 대한 테스트코드 작성
jhLim97 Feb 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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