From c5723a570437838b9d9fd6b4cfa56c98a119780d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=EC=98=81?= Date: Tue, 17 Feb 2026 16:00:45 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20preMember=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/club/entity.ts | 16 +++ src/apis/club/index.ts | 11 ++ src/pages/Manager/ManagedMemberList/index.tsx | 110 +++++++++++++++++- src/pages/Manager/hooks/useManagerQuery.ts | 25 ++++ 4 files changed, 160 insertions(+), 2 deletions(-) diff --git a/src/apis/club/entity.ts b/src/apis/club/entity.ts index 3347bebe..b6467221 100644 --- a/src/apis/club/entity.ts +++ b/src/apis/club/entity.ts @@ -267,3 +267,19 @@ export interface AddPreMemberResponse { studentNumber: string; name: string; } + +export interface preMember { + preMemberId: number; + studentNumber: string; + name: string; + clubPosition: 'MANAGER' | 'MEMBER'; +} + +export interface preMembersList { + preMembers: preMember[]; +} + +export interface preMemberDeleteRequest { + clubId: number; + preMemberId: number; +} diff --git a/src/apis/club/index.ts b/src/apis/club/index.ts index 8aa521a0..a12cee98 100644 --- a/src/apis/club/index.ts +++ b/src/apis/club/index.ts @@ -27,6 +27,7 @@ import { type AddPreMemberRequest, type AddPreMemberResponse, type PositionType, + type preMembersList, } from './entity'; export type { Bank, ClubFeeRequest }; @@ -195,3 +196,13 @@ export const postAddPreMember = async (clubId: number, data: AddPreMemberRequest }); return response; }; + +export const getPreMembers = async (clubId: number) => { + const response = await apiClient.get(`clubs/${clubId}/pre-members`, { requiresAuth: true }); + return response; +}; + +export const deletePreMember = async (clubId: number, preMemberId: number) => { + const response = await apiClient.delete(`clubs/${clubId}/pre-members/${preMemberId}`, { requiresAuth: true }); + return response; +}; diff --git a/src/pages/Manager/ManagedMemberList/index.tsx b/src/pages/Manager/ManagedMemberList/index.tsx index 6f0c6163..90b7baaa 100644 --- a/src/pages/Manager/ManagedMemberList/index.tsx +++ b/src/pages/Manager/ManagedMemberList/index.tsx @@ -1,6 +1,6 @@ import { useState, useMemo } from 'react'; import { useParams } from 'react-router-dom'; -import type { ClubMember, PositionType } from '@/apis/club/entity'; +import type { ClubMember, PositionType, preMember } from '@/apis/club/entity'; import CheckIcon from '@/assets/svg/check.svg'; import BottomModal from '@/components/common/BottomModal'; import Card from '@/components/common/Card'; @@ -12,6 +12,8 @@ import { useAddPreMember, useChangeMemberPosition, useChangeVicePresident, + useDeletePreMember, + useGetPreMemberList, useManagedMembers, useRemoveMember, useTransferPresident, @@ -69,9 +71,16 @@ function ManagedMemberList() { onSuccess: () => showToast('부원이 추가되었습니다'), }); - const isPending = isTransferring || isChangingVP || isChangingPosition || isRemoving || isAdding; + const { preMembersList } = useGetPreMemberList(clubId); + const { mutate: deletePreMemberMutate, isPending: isDeletingPreMember } = useDeletePreMember(clubId, { + onSuccess: () => showToast('사전 등록 회원이 삭제되었습니다'), + }); + + const isPending = + isTransferring || isChangingVP || isChangingPosition || isRemoving || isAdding || isDeletingPreMember; const [selectedMember, setSelectedMember] = useState(null); + const [selectedPreMember, setSelectedPreMember] = useState(null); const { value: isActionOpen, setTrue: openAction, setFalse: closeAction } = useBooleanState(); const { value: isTransferOpen, setTrue: openTransfer, setFalse: closeTransfer } = useBooleanState(); @@ -79,6 +88,16 @@ function ManagedMemberList() { const { value: isPositionOpen, setTrue: openPosition, setFalse: closePosition } = useBooleanState(); const { value: isRemoveOpen, setTrue: openRemove, setFalse: closeRemove } = useBooleanState(); const { value: isAddOpen, setTrue: openAdd, setFalse: closeAdd } = useBooleanState(); + const { + value: isPreMemberActionOpen, + setTrue: openPreMemberAction, + setFalse: closePreMemberAction, + } = useBooleanState(); + const { + value: isPreMemberDeleteOpen, + setTrue: openPreMemberDelete, + setFalse: closePreMemberDelete, + } = useBooleanState(); const [transferTarget, setTransferTarget] = useState(null); const [vpTarget, setVPTarget] = useState(null); @@ -145,6 +164,23 @@ function ManagedMemberList() { setNewMemberName(''); }; + const handlePreMemberAction = (member: preMember) => { + setSelectedPreMember(member); + openPreMemberAction(); + }; + + const handleOpenPreMemberDelete = () => { + closePreMemberAction(); + openPreMemberDelete(); + }; + + const handleDeletePreMember = () => { + if (!selectedPreMember) return; + deletePreMemberMutate(selectedPreMember.preMemberId); + closePreMemberDelete(); + setSelectedPreMember(null); + }; + return (
@@ -209,6 +245,35 @@ function ManagedMemberList() { ))}
))} + + {preMembersList.preMembers.length > 0 && ( +
+
사전 등록 회원
+ {preMembersList.preMembers.map((member) => ( + +
+
+ {member.name.charAt(0)} +
+
+
+ {member.name} ({member.studentNumber}) +
+
사전 등록
+
+
+ +
+ ))} +
+ )}
{/* Member Action Modal */} @@ -416,6 +481,47 @@ function ManagedMemberList() { + {/* Pre-Member Action Modal */} + +
+
{selectedPreMember?.name} 관리
+ +
+
+ + {/* Pre-Member Delete Confirm Modal */} + +
+
사전 등록 삭제
+
+ 정말 {selectedPreMember?.name}님의 사전 등록을 삭제하시겠어요? +
+
+ + +
+
+
+ ); diff --git a/src/pages/Manager/hooks/useManagerQuery.ts b/src/pages/Manager/hooks/useManagerQuery.ts index 4998a787..1ac3e1f6 100644 --- a/src/pages/Manager/hooks/useManagerQuery.ts +++ b/src/pages/Manager/hooks/useManagerQuery.ts @@ -21,6 +21,8 @@ import { patchVicePresident, patchMemberPosition, deleteMember, + getPreMembers, + deletePreMember, } from '@/apis/club'; import type { AddPreMemberRequest, @@ -51,6 +53,7 @@ const managerQueryKeys = { banks: () => [...managerQueryKeys.all, 'banks'], managedClubFee: (clubId: number) => [...managerQueryKeys.all, 'managedClubFee', clubId], managedMembers: (clubId: number) => [...managerQueryKeys.all, 'managedMembers', clubId], + preMembersList: (clubId: number) => [...managerQueryKeys.all, 'preMembersList', clubId], }; export const useManagerQuery = () => { @@ -290,6 +293,28 @@ export const useAddPreMember = (clubId: number, options: MutationOptions = {}) = mutationFn: (data: AddPreMemberRequest) => postAddPreMember(clubId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: managerQueryKeys.managedMembers(clubId) }); + queryClient.invalidateQueries({ queryKey: managerQueryKeys.preMembersList(clubId) }); + options.onSuccess?.(); + }, + }); +}; + +export const useGetPreMemberList = (clubId: number) => { + const { data: preMembersList } = useSuspenseQuery({ + queryKey: managerQueryKeys.preMembersList(clubId), + queryFn: () => getPreMembers(clubId), + }); + + return { preMembersList }; +}; + +export const useDeletePreMember = (clubId: number, options: MutationOptions = {}) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (preMemberId: number) => deletePreMember(clubId, preMemberId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: managerQueryKeys.preMembersList(clubId) }); options.onSuccess?.(); }, }); From f8b1966d99b591e0d01b2dbe213a8d272ac9036f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=EC=98=81?= Date: Tue, 17 Feb 2026 16:07:01 +0900 Subject: [PATCH 2/4] chore: Pascal case --- src/apis/club/entity.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apis/club/entity.ts b/src/apis/club/entity.ts index b6467221..9e6db44e 100644 --- a/src/apis/club/entity.ts +++ b/src/apis/club/entity.ts @@ -268,18 +268,18 @@ export interface AddPreMemberResponse { name: string; } -export interface preMember { +export interface PreMember { preMemberId: number; studentNumber: string; name: string; clubPosition: 'MANAGER' | 'MEMBER'; } -export interface preMembersList { - preMembers: preMember[]; +export interface PreMembersList { + preMembers: PreMember[]; } -export interface preMemberDeleteRequest { +export interface PreMemberDeleteRequest { clubId: number; preMemberId: number; } From 52bf50fa08bd8d2aaec4e2be8aeb446b56ada0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=EC=98=81?= Date: Tue, 17 Feb 2026 16:10:36 +0900 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/club/index.ts | 4 ++-- src/pages/Manager/ManagedMemberList/index.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/apis/club/index.ts b/src/apis/club/index.ts index a12cee98..4e44ecbe 100644 --- a/src/apis/club/index.ts +++ b/src/apis/club/index.ts @@ -27,7 +27,7 @@ import { type AddPreMemberRequest, type AddPreMemberResponse, type PositionType, - type preMembersList, + type PreMembersList, } from './entity'; export type { Bank, ClubFeeRequest }; @@ -198,7 +198,7 @@ export const postAddPreMember = async (clubId: number, data: AddPreMemberRequest }; export const getPreMembers = async (clubId: number) => { - const response = await apiClient.get(`clubs/${clubId}/pre-members`, { requiresAuth: true }); + const response = await apiClient.get(`clubs/${clubId}/pre-members`, { requiresAuth: true }); return response; }; diff --git a/src/pages/Manager/ManagedMemberList/index.tsx b/src/pages/Manager/ManagedMemberList/index.tsx index 90b7baaa..a9e338c3 100644 --- a/src/pages/Manager/ManagedMemberList/index.tsx +++ b/src/pages/Manager/ManagedMemberList/index.tsx @@ -1,6 +1,6 @@ import { useState, useMemo } from 'react'; import { useParams } from 'react-router-dom'; -import type { ClubMember, PositionType, preMember } from '@/apis/club/entity'; +import type { ClubMember, PositionType, PreMember } from '@/apis/club/entity'; import CheckIcon from '@/assets/svg/check.svg'; import BottomModal from '@/components/common/BottomModal'; import Card from '@/components/common/Card'; @@ -80,7 +80,7 @@ function ManagedMemberList() { isTransferring || isChangingVP || isChangingPosition || isRemoving || isAdding || isDeletingPreMember; const [selectedMember, setSelectedMember] = useState(null); - const [selectedPreMember, setSelectedPreMember] = useState(null); + const [selectedPreMember, setSelectedPreMember] = useState(null); const { value: isActionOpen, setTrue: openAction, setFalse: closeAction } = useBooleanState(); const { value: isTransferOpen, setTrue: openTransfer, setFalse: closeTransfer } = useBooleanState(); @@ -164,7 +164,7 @@ function ManagedMemberList() { setNewMemberName(''); }; - const handlePreMemberAction = (member: preMember) => { + const handlePreMemberAction = (member: PreMember) => { setSelectedPreMember(member); openPreMemberAction(); }; From d34e53a2c0c5cdb64940bb8b42b42a07f0cb5f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=EC=98=81?= Date: Tue, 17 Feb 2026 16:11:54 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=ED=95=99=EB=B2=88=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Manager/ManagedMemberList/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Manager/ManagedMemberList/index.tsx b/src/pages/Manager/ManagedMemberList/index.tsx index a9e338c3..55606c6e 100644 --- a/src/pages/Manager/ManagedMemberList/index.tsx +++ b/src/pages/Manager/ManagedMemberList/index.tsx @@ -455,7 +455,7 @@ function ManagedMemberList() { setNewStudentNumber(e.target.value)} + onChange={(e) => setNewStudentNumber(e.target.value.replace(/\D/g, ''))} placeholder="학번을 입력해주세요" className="border-indigo-25 rounded-lg border px-3 py-2 text-sm outline-none focus:border-blue-500" />