Skip to content

Commit

Permalink
feat: 지출 내역 추가 변경사항 적용 (#414)
Browse files Browse the repository at this point in the history
* design: 지출 추가, 인원 추가 button 퍼블리싱

* feat: 변경된 Button에 BottomSheet onClick연결

* chore: 디자인시스템 버전 업데이트

* feat: step.type이 Bill이 아니더라도 isAddEditableItem가 true라면 BillStepItem을 렌더링하여 지출 내역 추가 Input을 띄우기

* feat: onChange에 billInput 변경하기 및 blur시 조건을 충족할 경우 서버로 api 요청 보내기

* feat: 지출 내역 post api를 요청하면 지출 내역 Input을 닫기

* feat: 디자인시스템과의 병합을 위해 Input에 value 추가

* chore: 불필요한 주석 제거

* fix: 불필요한 조건문 제거

* chore: 불필요한 console.log 삭제

* feat: 마지막 BillItem에만 EditableItem.Input을 렌더링하기

* design: 관리 페이지 디자인 수정

* fix: billInput 값을 초기화

* feat: BillItem이 존재하지 않을 때, 새로운 BillItem을 생성하여 지출 input 만들기

* feat: member action 추가후, 빈 stepList가 생성되지 않는 에러 해결

* chore: 디자인시스템 업데이트

---------

Co-authored-by: 이태훈 <[email protected]>
  • Loading branch information
soi-ha and Todari authored Aug 21, 2024
1 parent 9ab0a96 commit 4463eec
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 45 deletions.
8 changes: 4 additions & 4 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"@emotion/react": "^11.11.4",
"@sentry/react": "^8.25.0",
"@tanstack/react-query": "^5.51.23",
"haengdong-design": "^0.1.74",
"haengdong-design": "^0.1.76",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
Expand All @@ -80,4 +80,4 @@
"npm": ">=10.7.0",
"node": ">=20.15.1"
}
}
}
84 changes: 62 additions & 22 deletions client/src/components/StepList/BillStepItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import type {BillStep, MemberReport} from 'types/serviceType';

import {DragHandleItem, DragHandleItemContainer} from 'haengdong-design';
import {DragHandleItem, DragHandleItemContainer, EditableItem, Flex, Text} from 'haengdong-design';
import {Fragment, useState} from 'react';
import {useOutletContext} from 'react-router-dom';

import {BillStep, MemberReport} from 'types/serviceType';
import {PutAndDeleteBillActionModal} from '@components/Modal/SetActionModal/PutAndDeleteBillActionModal';
import {MemberListInBillStep} from '@components/Modal/MemberListInBillStep';
import {EventPageContextProps} from '@pages/EventPage/EventPageLayout';

import useSetBillInput from '@hooks/useSetBillInput';

interface BillStepItemProps {
step: BillStep;
isOpenBottomSheet: boolean;
setIsOpenBottomSheet: React.Dispatch<React.SetStateAction<boolean>>;
isAddEditableItem: boolean;
setIsAddEditableItem: React.Dispatch<React.SetStateAction<boolean>>;
isLastBillItem: boolean;
index: number;
}

const BillStepItem: React.FC<BillStepItemProps> = ({step, isOpenBottomSheet, setIsOpenBottomSheet}) => {
const BillStepItem: React.FC<BillStepItemProps> = ({
step,
isOpenBottomSheet,
setIsOpenBottomSheet,
isAddEditableItem,
setIsAddEditableItem,
isLastBillItem,
index,
}) => {
const {isAdmin} = useOutletContext<EventPageContextProps>();
const {handleBlurBillRequest, handleChangeBillInput, billInput} = useSetBillInput({setIsAddEditableItem});

const [clickedIndex, setClickedIndex] = useState(-1);
const [isOpenMemberListInBillStep, setIsOpenMemberListInBillStep] = useState(false);

const stepName = `차`;
const totalPrice = step.actions.reduce((acc, cur) => acc + cur.price, 0);
const totalPrice = step.actions && step.type === 'BILL' ? step.actions.reduce((acc, cur) => acc + cur.price, 0) : 0;

const handleDragHandleItemClick = (index: number) => {
setClickedIndex(index);
Expand All @@ -39,31 +54,56 @@ const BillStepItem: React.FC<BillStepItemProps> = ({step, isOpenBottomSheet, set
return (
<>
<DragHandleItemContainer
id={`${index}`}
topLeftText={stepName}
topRightText={`${step.members.length}명`}
bottomLeftText="총액"
bottomRightText={`${totalPrice.toLocaleString('ko-kr')} 원`}
backgroundColor="white"
onTopRightTextClick={handleTopRightTextClick}
>
{step.actions.map((action, index) => (
<Fragment key={action.actionId}>
<DragHandleItem
hasDragHandler={isAdmin}
prefix={action.name}
suffix={`${action.price.toLocaleString('ko-kr')} 원`}
backgroundColor="lightGrayContainer"
onClick={() => handleDragHandleItemClick(index)}
/>
{isOpenBottomSheet && clickedIndex === index && isAdmin && (
<PutAndDeleteBillActionModal
billAction={action}
isBottomSheetOpened={isOpenBottomSheet}
setIsBottomSheetOpened={setIsOpenBottomSheet}
{step.actions &&
step.type === 'BILL' &&
step.actions.map((action, index) => (
<Fragment key={action.actionId}>
<DragHandleItem
hasDragHandler={isAdmin}
prefix={action.name}
suffix={`${action.price.toLocaleString('ko-kr')} 원`}
backgroundColor="lightGrayContainer"
onClick={() => handleDragHandleItemClick(index)}
/>
)}
</Fragment>
))}

{isOpenBottomSheet && clickedIndex === index && isAdmin && (
<PutAndDeleteBillActionModal
billAction={action}
isBottomSheetOpened={isOpenBottomSheet}
setIsBottomSheetOpened={setIsOpenBottomSheet}
/>
)}
</Fragment>
))}

{isAddEditableItem && isLastBillItem && (
<EditableItem backgroundColor="lightGrayContainer" onBlur={handleBlurBillRequest}>
<EditableItem.Input
placeholder="지출 내역"
textSize="bodyBold"
value={billInput.title}
onChange={e => handleChangeBillInput('title', e)}
></EditableItem.Input>
<Flex gap="0.25rem" alignItems="center">
<EditableItem.Input
placeholder="0"
type="number"
value={billInput.price}
onChange={e => handleChangeBillInput('price', e)}
style={{textAlign: 'right'}}
></EditableItem.Input>
<Text size="caption"></Text>
</Flex>
</EditableItem>
)}
</DragHandleItemContainer>
{isOpenMemberListInBillStep && (
<MemberListInBillStep
Expand Down
33 changes: 28 additions & 5 deletions client/src/components/StepList/Step.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
import type {BillStep, MemberStep} from 'types/serviceType';

import {useState} from 'react';
import {useEffect, useState} from 'react';

import BillStepItem from './BillStepItem';
import MemberStepItem from './MemberStepItem';

interface StepProps {
step: BillStep | MemberStep;
isAddEditableItem: boolean;
lastBillItemIndex: number;
lastItemIndex: number;
index: number;
setIsAddEditableItem: React.Dispatch<React.SetStateAction<boolean>>;
}

const Step = ({step}: StepProps) => {
const Step = ({step, isAddEditableItem, lastBillItemIndex, lastItemIndex, setIsAddEditableItem, index}: StepProps) => {
const [isOpenBottomSheet, setIsOpenBottomSheet] = useState<boolean>(false);
const [isLastBillItem, setIsLastBillItem] = useState<boolean>(false);

if (step.type === 'BILL') {
useEffect(() => {
if (index === lastBillItemIndex && lastBillItemIndex === lastItemIndex) {
// index를 사용하여 마지막 BillStep인지 확인
setIsLastBillItem(true);
} else {
setIsLastBillItem(false);
}
}, [index, lastBillItemIndex]);

if (step.actions && step.type === 'BILL') {
return (
<BillStepItem step={step} isOpenBottomSheet={isOpenBottomSheet} setIsOpenBottomSheet={setIsOpenBottomSheet} />
<BillStepItem
index={index}
step={step as BillStep}
isOpenBottomSheet={isOpenBottomSheet}
setIsOpenBottomSheet={setIsOpenBottomSheet}
isAddEditableItem={isAddEditableItem}
setIsAddEditableItem={setIsAddEditableItem}
isLastBillItem={isLastBillItem}
/>
);
} else if (step.type === 'IN' || step.type === 'OUT') {
} else if (step.actions && (step.type === 'IN' || step.type === 'OUT')) {
return (
<MemberStepItem step={step} isOpenBottomSheet={isOpenBottomSheet} setIsOpenBottomSheet={setIsOpenBottomSheet} />
);
Expand Down
57 changes: 54 additions & 3 deletions client/src/components/StepList/StepList.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
import {Flex} from 'haengdong-design';
import {useEffect, useMemo, useState} from 'react';

import {BillStep, MemberStep} from 'types/serviceType';
import useRequestGetStepList from '@hooks/queries/useRequestGetStepList';

import Step from './Step';

const StepList = () => {
interface StepListProps {
isAddEditableItem: boolean;
setIsAddEditableItem: React.Dispatch<React.SetStateAction<boolean>>;
}

const StepList = ({isAddEditableItem, setIsAddEditableItem}: StepListProps) => {
const {data: stepListData} = useRequestGetStepList();
const stepList = stepListData ?? ([] as (MemberStep | BillStep)[]);
const [stepList, setStepList] = useState<(MemberStep | BillStep)[]>([]);
const existIndexInStepList = stepList.map((step, index) => ({...step, index}));
const [hasAddedItem, setHasAddedItem] = useState(false);

useEffect(() => {
if (stepListData) {
setStepList(stepListData);
}
}, [stepListData]);

const lastBillItemIndex = useMemo(() => {
const billSteps = existIndexInStepList.filter(step => step.type === 'BILL');

// billSteps 배열이 비어 있지 않으면 마지막 항목의 index를 반환, 그렇지 않으면 -1을 반환
return billSteps.length > 0 ? billSteps.slice(-1)[0].index : -1;
}, [stepList]);

const lastItemIndex = useMemo(() => {
return existIndexInStepList.length > 0 ? existIndexInStepList.slice(-1)[0].index : -1;
}, [existIndexInStepList]);

useEffect(() => {
// 최초로 빈 stepList가 생성되고 난 후, 다시 hasAddedItem을 false로 변환하기 위한 조건문
if (hasAddedItem) setHasAddedItem(prev => !prev);

if (isAddEditableItem && lastBillItemIndex !== lastItemIndex && !hasAddedItem) {
setStepList(prev => [
...prev,
{
type: 'BILL',
stepName: '',
members: [],
actions: [],
},
]);
setHasAddedItem(prev => !prev);
}
}, [isAddEditableItem, lastBillItemIndex, lastItemIndex, hasAddedItem, existIndexInStepList, stepListData]);

return (
<Flex flexDirection="column" gap="0.5rem" paddingInline="0.5rem">
{stepList.map((step, index) => (
<Step step={step} key={`${step.stepName}${index}`} />
<Step
index={index}
step={step}
lastBillItemIndex={lastBillItemIndex}
lastItemIndex={lastItemIndex}
key={`${step.stepName}${index}`}
isAddEditableItem={isAddEditableItem}
setIsAddEditableItem={setIsAddEditableItem}
/>
))}
</Flex>
);
Expand Down
66 changes: 66 additions & 0 deletions client/src/hooks/useSetBillInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {useState} from 'react';

import validatePurchase from '@utils/validate/validatePurchase';
import {Bill} from 'types/serviceType';

import useRequestPostBillList from './queries/useRequestPostBillList';
import {BillInputType, InputPair} from './useDynamicBillActionInput';

interface UseSetBillInputProps {
setIsAddEditableItem: React.Dispatch<React.SetStateAction<boolean>>;
}

interface UseSetBillInputReturns {
billInput: Bill;
handleChangeBillInput: (field: BillInputType, event: React.ChangeEvent<HTMLInputElement>) => void;
handleBlurBillRequest: () => void;
}

const useSetBillInput = ({setIsAddEditableItem}: UseSetBillInputProps): UseSetBillInputReturns => {
const initialInput = {title: '', price: 0};
const [billInput, setBillInput] = useState<Bill>(initialInput);

const {mutate: postBillList} = useRequestPostBillList();

const handleChangeBillInput = (field: BillInputType, event: React.ChangeEvent<HTMLInputElement>) => {
const {value} = event.target;
const {isValid} = validatePurchase({
...billInput,
[field]: value,
});

if (isValid) {
setBillInput(prev => ({
...prev,
[field]: value,
}));
}
};

const handleBlurBillRequest = () => {
const isEmptyTitle = billInput.title.trim().length;
const isEmptyPrice = Number(billInput.price);

// 두 input의 값이 모두 채워졌을 때 api 요청
// api 요청을 하면 Input을 띄우지 않음
if (isEmptyTitle && isEmptyPrice) {
postBillList(
{billList: [billInput]},
{
onSuccess: () => {
setBillInput(initialInput);
setIsAddEditableItem(false);
},
},
);
}
};

return {
billInput,
handleBlurBillRequest,
handleChangeBillInput,
};
};

export default useSetBillInput;
11 changes: 9 additions & 2 deletions client/src/pages/EventPage/AdminPage/AdminPage.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export const receiptStyle = () =>
css({
display: 'flex',
flexDirection: 'column',
gap: '8px',
padding: '0 8px',
gap: '1rem',
paddingBottom: '8.75rem',
});

Expand All @@ -14,3 +13,11 @@ export const titleAndListButtonContainerStyle = () =>
display: 'flex',
flexDirection: 'column',
});

export const buttonGroupStyle = () =>
css({
display: 'flex',
width: '100%',
padding: '0 0.5rem',
gap: '0.5rem',
});
Loading

0 comments on commit 4463eec

Please sign in to comment.