diff --git a/client/src/hooks/useEventMember.ts b/client/src/hooks/useEventMember.ts index 54214723e..617022db5 100644 --- a/client/src/hooks/useEventMember.ts +++ b/client/src/hooks/useEventMember.ts @@ -1,6 +1,6 @@ -import {useEffect, useState} from 'react'; +import {useEffect, useState, useCallback, useMemo} from 'react'; -import {Reports, Report, AllMembers, MemberWithDeposited} from 'types/serviceType'; +import {Report} from 'types/serviceType'; import validateMemberName from '@utils/validate/validateMemberName'; import useRequestDeleteMember from './queries/member/useRequestDeleteMember'; @@ -9,7 +9,7 @@ import useRequestGetReports from './queries/report/useRequestGetReports'; interface ReturnUseEventMember { reports: Report[]; - isCanRequest: boolean; + isCanSubmit: boolean; changeMemberName: (memberId: number, newName: string) => void; toggleDepositStatus: (memberId: number) => void; handleDeleteMember: (memberId: number) => void; @@ -23,84 +23,72 @@ const useEventMember = (): ReturnUseEventMember => { const [reports, setReports] = useState(initialReports); const [deleteMembers, setDeleteMembers] = useState([]); - const [changedMembers, setChangedMembers] = useState([]); - const [isCanRequest, setIsCanRequest] = useState(false); - const [filteredChangedMembers, setFilteredChangedMembers] = useState([]); useEffect(() => { setReports(initialReports); }, [initialReports]); - useEffect(() => { - const changedMembers = getChangedMembers(); + const isCanSubmit = useMemo(() => { + // 중복되는 이름이 존재하는지 확인 + const hasDuplicateMemberName = (): boolean => { + const nameSet = new Set(reports.map(member => member.memberName)); + return nameSet.size !== reports.length; + }; - // 중복된 이름이 존재할 경우 isCanRequest를 false로 변경 if (hasDuplicateMemberName()) { - setIsCanRequest(false); - } else { - // 변경된 사항이 존재하거나 삭제한 member가 존재한다면 isCanRequest를 true로 변경 - setIsCanRequest(changedMembers.length > 0 || deleteMembers.length > 0); + // 중복 이름이라면 false + return false; } - // memberName 유효성 검사 (0글자) - const hasEmptyName = changedMembers.some(member => member.name.trim().length === 0); - if (hasEmptyName) setIsCanRequest(false); - - setFilteredChangedMembers(changedMembers); - }, [reports, changedMembers, deleteMembers]); - - const changeMemberName = (memberId: number, newName: string) => { - // 유효성 검사 (4글자) - if (!validateMemberName(newName).isValid) { - return; - } + // 이름이 공백이라면 false + const hasEmptyName = reports.some(report => report.memberName.trim().length === 0); + if (hasEmptyName) return false; - setReports(prevReports => - prevReports.map(report => (report.memberId === memberId ? {...report, memberName: newName} : report)), + // 초기 값과 비교하여 변경된 사항이 존재하는지 확인 + const hasChanges = reports.some(report => + initialReports.find( + initial => + initial.memberId === report.memberId && + (initial.memberName !== report.memberName || initial.isDeposited !== report.isDeposited), + ), ); - setChangedMembers(prev => { - const existing = prev.find(member => member.id === memberId); - const isDeposited = reports.find(report => report.memberId === memberId)?.isDeposited ?? false; // 기본값 제공 + // 변경된 사항이 존재 혹은 삭제된 member가 존재한다면 true + return hasChanges || deleteMembers.length > 0; + }, [reports, initialReports, deleteMembers]); - if (existing) { - return prev.map(member => (member.id === memberId ? {...member, name: newName, isDeposited} : member)); + const changeMemberName = useCallback( + (memberId: number, newName: string) => { + // 유효성 검사 (4자 이하) + if (!validateMemberName(newName).isValid) { + return; } - return [...prev, {id: memberId, name: newName, isDeposited}]; - }); - setIsCanRequest(true); - }; + setReports(prevReports => + prevReports.map(report => (report.memberId === memberId ? {...report, memberName: newName} : report)), + ); + }, + [setReports, validateMemberName], + ); - const toggleDepositStatus = (memberId: number) => { + const toggleDepositStatus = useCallback((memberId: number) => { setReports(prevReports => prevReports.map(report => report.memberId === memberId ? {...report, isDeposited: !report.isDeposited} : report, ), ); + }, []); - setChangedMembers(prev => { - const existing = prev.find(member => member.id === memberId); - const name = reports.find(report => report.memberId === memberId)?.memberName ?? ''; // 기본값 제공 - const newIsDeposited = !reports.find(report => report.memberId === memberId)?.isDeposited ?? false; - - if (existing) { - return prev.map(member => (member.id === memberId ? {...member, isDeposited: newIsDeposited} : member)); - } - return [...prev, {id: memberId, name, isDeposited: newIsDeposited}]; - }); - - setIsCanRequest(true); - }; - - const handleDeleteMember = (memberId: number) => { + // 삭제할 member를 따로 deleteMembers 상태에서 id만 저장 + const handleDeleteMember = useCallback((memberId: number) => { setDeleteMembers(prev => [memberId, ...prev]); + // 삭제할 member들의 데이터를 reports에서 제거 setReports(prevReports => prevReports.filter(report => report.memberId !== memberId)); - setChangedMembers(prev => prev.filter(member => member.id !== memberId)); - }; + }, []); - const updateMembersOnServer = () => { - // 삭제할 member(deleteMembers)가 존재한다면 Delete 요청 실행 + const updateMembersOnServer = useCallback(async () => { + // DELETE 요청 선행 + // deleteMembers에 값이 하나라도 전재하면 반복문을 통해 DELETE api 요청 if (deleteMembers.length > 0) { for (const id of deleteMembers) { deleteAsyncMember({memberId: id}); @@ -108,44 +96,18 @@ const useEventMember = (): ReturnUseEventMember => { } // 변경된 값(filteredChangedMembers)이 존재한다면 PUT 요청 실행 - if (filteredChangedMembers.length > 0) { - putAsyncMember({members: filteredChangedMembers}); + if (reports.length > 0) { + putAsyncMember({ + members: reports.map(report => ({ + id: report.memberId, + name: report.memberName, + isDeposited: report.isDeposited, + })), + }); } - }; - - const getChangedMembers = () => { - // 초기 상태에서 변경된 값이 존재하는 것만 filtering하여 return 한다. - - // 초기 상태에서 memberId를 키로 갖는 맵 생성 - const initialReportsMap = new Map(initialReports.map(report => [report.memberId, report])); - - // 변경된 값(changedMembers)에서 초기 상태와 동일한 값을 삭제 - const filteredChangedMembers = changedMembers.filter(changedMember => { - const initialMember = initialReportsMap.get(changedMember.id); - return ( - !initialMember || - initialMember.memberName !== changedMember.name || - initialMember.isDeposited !== changedMember.isDeposited - ); - }); - - return filteredChangedMembers; - }; - - const hasDuplicateMemberName = (): boolean => { - const nameCount: {[key: string]: number} = {}; - - for (const member of reports) { - if (nameCount[member.memberName]) { - return true; // 중복된 이름이 존재하면 즉시 true 반환 - } - nameCount[member.memberName] = 1; - } - - return false; // 중복된 이름이 없으면 false 반환 - }; + }, [deleteMembers, reports, initialReports, deleteAsyncMember, putAsyncMember]); - return {reports, isCanRequest, changeMemberName, handleDeleteMember, updateMembersOnServer, toggleDepositStatus}; + return {reports, isCanSubmit, changeMemberName, handleDeleteMember, updateMembersOnServer, toggleDepositStatus}; }; export default useEventMember; diff --git a/client/src/pages/EventPage/AdminPage/EventMemberManage.style.ts b/client/src/pages/EventPage/AdminPage/EventMember.style.ts similarity index 96% rename from client/src/pages/EventPage/AdminPage/EventMemberManage.style.ts rename to client/src/pages/EventPage/AdminPage/EventMember.style.ts index 1fcbc3fd3..f59f288bd 100644 --- a/client/src/pages/EventPage/AdminPage/EventMemberManage.style.ts +++ b/client/src/pages/EventPage/AdminPage/EventMember.style.ts @@ -3,7 +3,7 @@ import {css} from '@emotion/react'; import {Theme} from '@components/Design/theme/theme.type'; import TYPOGRAPHY from '@components/Design/token/typography'; -export const eventMemberMangeStyle = () => +export const eventMemberStyle = () => css({ padding: '0 1rem', }); diff --git a/client/src/pages/EventPage/AdminPage/EventMemberManage.tsx b/client/src/pages/EventPage/AdminPage/EventMember.tsx similarity index 80% rename from client/src/pages/EventPage/AdminPage/EventMemberManage.tsx rename to client/src/pages/EventPage/AdminPage/EventMember.tsx index 3be8dcf88..a59f124f8 100644 --- a/client/src/pages/EventPage/AdminPage/EventMemberManage.tsx +++ b/client/src/pages/EventPage/AdminPage/EventMember.tsx @@ -17,10 +17,10 @@ import { } from '@components/Design'; import {useTheme} from '@components/Design'; -import {eventMemberMangeStyle, memberList, eventMember, memberEditInput, noneReports} from './EventMemberManage.style'; +import {eventMemberStyle, memberList, eventMember, memberEditInput, noneReports} from './EventMember.style'; -const EventMemberManage = () => { - const {reports, isCanRequest, changeMemberName, handleDeleteMember, updateMembersOnServer, toggleDepositStatus} = +const EventMember = () => { + const {reports, isCanSubmit, changeMemberName, handleDeleteMember, updateMembersOnServer, toggleDepositStatus} = useEventMember(); return ( @@ -28,7 +28,7 @@ const EventMemberManage = () => { -
+
@@ -43,7 +43,7 @@ const EventMemberManage = () => { ) : ( reports.map(member => { return ( - { {reports.length === 0 ? ( <> ) : ( - + 수정완료 )} @@ -66,14 +66,14 @@ const EventMemberManage = () => { ); }; -interface EventMemberProps { +interface MemberProps { member: Report; changeMemberName: (memberId: number, newName: string) => void; handleDeleteMember: (memberId: number) => void; toggleDepositStatus: (memberId: number) => void; } -const EventMember = ({member, changeMemberName, handleDeleteMember, toggleDepositStatus}: EventMemberProps) => { +const Member = ({member, changeMemberName, handleDeleteMember, toggleDepositStatus}: MemberProps) => { const {theme} = useTheme(); const handleChangeName = (e: React.ChangeEvent) => { @@ -102,4 +102,4 @@ const EventMember = ({member, changeMemberName, handleDeleteMember, toggleDeposi ); }; -export default EventMemberManage; +export default EventMember; diff --git a/client/src/router.tsx b/client/src/router.tsx index 56a4a006a..735b09786 100644 --- a/client/src/router.tsx +++ b/client/src/router.tsx @@ -6,7 +6,7 @@ import ErrorPage from '@pages/ErrorPage/ErrorPage'; import EventLoginPage from '@pages/EventPage/AdminPage/EventLoginPage'; import AddBillFunnel from '@pages/AddBillFunnel/AddBillFunnel'; import CreateEventFunnel from '@pages/CreateEventPage/CreateEventFunnel'; -import EventMemberManage from '@pages/EventPage/AdminPage/EventMemberManage'; +import EventMember from '@pages/EventPage/AdminPage/EventMember'; import EditBillPage from '@pages/EditBillPage/EditBillPage'; import Account from '@pages/AccountPage/Account'; @@ -50,7 +50,7 @@ const router = createBrowserRouter([ }, { path: ROUTER_URLS.member, - element: , + element: , }, { path: ROUTER_URLS.editBill,