Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions src/apis/club/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
11 changes: 11 additions & 0 deletions src/apis/club/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
type AddPreMemberRequest,
type AddPreMemberResponse,
type PositionType,
type PreMembersList,
} from './entity';

export type { Bank, ClubFeeRequest };
Expand Down Expand Up @@ -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<PreMembersList>(`clubs/${clubId}/pre-members`, { requiresAuth: true });
return response;
};

export const deletePreMember = async (clubId: number, preMemberId: number) => {
const response = await apiClient.delete<void>(`clubs/${clubId}/pre-members/${preMemberId}`, { requiresAuth: true });
return response;
};
112 changes: 109 additions & 3 deletions src/pages/Manager/ManagedMemberList/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,6 +12,8 @@ import {
useAddPreMember,
useChangeMemberPosition,
useChangeVicePresident,
useDeletePreMember,
useGetPreMemberList,
useManagedMembers,
useRemoveMember,
useTransferPresident,
Expand Down Expand Up @@ -69,16 +71,33 @@ 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<ClubMember | null>(null);
const [selectedPreMember, setSelectedPreMember] = useState<PreMember | null>(null);

const { value: isActionOpen, setTrue: openAction, setFalse: closeAction } = useBooleanState();
const { value: isTransferOpen, setTrue: openTransfer, setFalse: closeTransfer } = useBooleanState();
const { value: isVPOpen, setTrue: openVP, setFalse: closeVP } = useBooleanState();
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<number | null>(null);
const [vpTarget, setVPTarget] = useState<number | null>(null);
Expand Down Expand Up @@ -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 (
<div className="flex h-full flex-col bg-white">
<div className="flex flex-1 flex-col gap-2 overflow-auto p-3">
Expand Down Expand Up @@ -209,6 +245,35 @@ function ManagedMemberList() {
))}
</div>
))}

{preMembersList.preMembers.length > 0 && (
<div className="flex flex-col gap-1">
<div className="text-body2 px-1 py-1 font-semibold text-indigo-700">사전 등록 회원</div>
{preMembersList.preMembers.map((member) => (
<Card key={member.preMemberId} className="flex-row items-center gap-2">
<div className="flex flex-1 items-center gap-2">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-indigo-100 text-indigo-400">
{member.name.charAt(0)}
</div>
<div>
<div className="text-body2 text-indigo-700">
{member.name} <span className="text-body3 text-indigo-400">({member.studentNumber})</span>
</div>
<div className="text-cap1 text-indigo-300">사전 등록</div>
</div>
</div>
<button
type="button"
onClick={() => handlePreMemberAction(member)}
disabled={isPending}
className="hover:bg-indigo-5 rounded-full p-2 text-indigo-400 disabled:opacity-50"
>
</button>
</Card>
))}
</div>
)}
</div>

{/* Member Action Modal */}
Expand Down Expand Up @@ -390,7 +455,7 @@ function ManagedMemberList() {
<input
type="text"
value={newStudentNumber}
onChange={(e) => 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"
/>
Expand All @@ -416,6 +481,47 @@ function ManagedMemberList() {
</div>
</BottomModal>

{/* Pre-Member Action Modal */}
<BottomModal isOpen={isPreMemberActionOpen} onClose={closePreMemberAction}>
<div className="flex flex-col gap-2 p-5">
<div className="text-body2 pb-2 font-semibold text-indigo-700">{selectedPreMember?.name} 관리</div>
<button
type="button"
onClick={handleOpenPreMemberDelete}
className="text-body3 active:bg-indigo-5 rounded-lg py-3 text-left text-red-500"
>
사전 등록 삭제
</button>
</div>
</BottomModal>

{/* Pre-Member Delete Confirm Modal */}
<BottomModal isOpen={isPreMemberDeleteOpen} onClose={closePreMemberDelete}>
<div className="flex flex-col gap-3 p-5">
<div className="text-body2 font-semibold text-indigo-700">사전 등록 삭제</div>
<div className="text-body3 text-indigo-400">
정말 {selectedPreMember?.name}님의 사전 등록을 삭제하시겠어요?
</div>
<div className="flex gap-2">
<button
type="button"
onClick={closePreMemberDelete}
className="border-indigo-25 flex-1 rounded-lg border py-3 text-center font-bold"
>
취소
</button>
<button
type="button"
onClick={handleDeletePreMember}
disabled={isPending}
className="flex-1 rounded-lg bg-red-500 py-3 text-center font-bold text-white disabled:opacity-50"
>
{isDeletingPreMember ? '삭제 중...' : '삭제'}
</button>
</div>
</div>
</BottomModal>

<Toast toast={toast} onClose={hideToast} />
</div>
);
Expand Down
25 changes: 25 additions & 0 deletions src/pages/Manager/hooks/useManagerQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
patchVicePresident,
patchMemberPosition,
deleteMember,
getPreMembers,
deletePreMember,
} from '@/apis/club';
import type {
AddPreMemberRequest,
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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?.();
},
});
Expand Down