diff --git a/src/components/admin/attendance/AttendanceCard.tsx b/src/components/admin/attendance/AttendanceCard.tsx index b6b7a412..bd2cb16c 100644 --- a/src/components/admin/attendance/AttendanceCard.tsx +++ b/src/components/admin/attendance/AttendanceCard.tsx @@ -10,6 +10,7 @@ import { Button, Icon, Badge, + Skeleton, } from '@/components/ui'; import type { AttendanceMember } from '@/types/admin/attendance'; import { cn } from '@/lib/cn'; @@ -27,6 +28,7 @@ interface AttendanceCardProps extends React.HTMLAttributes { onDirtyChange?: (dirty: boolean) => void; onExpand?: () => void; isSaving?: boolean; + isMembersLoading?: boolean; defaultExpanded?: boolean; } @@ -40,6 +42,7 @@ function AttendanceCard({ onDirtyChange, onExpand, isSaving = false, + isMembersLoading = false, defaultExpanded = false, ...props }: AttendanceCardProps) { @@ -156,7 +159,19 @@ function AttendanceCard({ {/* Member rows */} - {filteredMembers.length > 0 ? ( + {isMembersLoading ? ( + Array.from({ length: 5 }, (_, i) => ( +
+
+ + +
+
+ +
+
+ )) + ) : filteredMembers.length > 0 ? ( filteredMembers.map((member) => ( >(new Set()); const [pendingCardinalId, setPendingCardinalId] = useState(null); const [cardinalDialogOpen, setCardinalDialogOpen] = useState(false); @@ -32,7 +30,7 @@ function AttendancePageContent() { }, []); const handleCardinalSelect = useCallback( - (id: number) => { + (id: number | null) => { if (isDirty) { setPendingCardinalId(id); setCardinalDialogOpen(true); @@ -53,7 +51,7 @@ function AttendancePageContent() { }; const cardinalNumber = activeCardinal?.cardinalNumber ?? null; - const { sessions } = useFlattenedSessions(cardinalNumber); + const { sessions, isLoading } = useFlattenedSessions(cardinalNumber); const searchParams = useSearchParams(); const targetSessionIdParam = searchParams.get('sessionId'); @@ -73,36 +71,42 @@ function AttendancePageContent() { }, [sessions, targetSessionId]); return ( -
+
handleCardinalSelect(null)} /> - {sessions.length > 0 ? ( - - {sessions.map((session) => { - const isTarget = session.id === targetSessionId; - return ( -
- -
- ); - })} + {isLoading ? ( + +
+ {Array.from({ length: 4 }, (_, i) => ( + + ))} +
+
+ ) : sessions.length > 0 ? ( + +
+ {sessions.map((session) => ( + + ))} +
) : ( - - - {activeCardinal ? '등록된 정기모임이 없습니다.' : '기수를 선택해 주세요.'} - + +
+ 등록된 정기모임이 없습니다. +
)} diff --git a/src/components/admin/attendance/AttendanceSessionCard.tsx b/src/components/admin/attendance/AttendanceSessionCard.tsx index bb45c49f..da5743b0 100644 --- a/src/components/admin/attendance/AttendanceSessionCard.tsx +++ b/src/components/admin/attendance/AttendanceSessionCard.tsx @@ -24,7 +24,7 @@ function AttendanceSessionCard({ onDirtyChange, }: AttendanceSessionCardProps) { const [expanded, setExpanded] = useState(defaultExpanded); - const { data: members = [] } = useAdminAttendance(sessionId, { enabled: expanded }); + const { data: members = [], isLoading } = useAdminAttendance(sessionId, { enabled: expanded }); const { mutateAsync, isPending } = useUpdateAttendanceStatus(sessionId); const handleDirtyChange = (dirty: boolean) => onDirtyChange?.(sessionId, dirty); @@ -40,6 +40,7 @@ function AttendanceSessionCard({ isCurrentWeek={isCurrentWeek} defaultExpanded={defaultExpanded} members={members} + isMembersLoading={isLoading} onSave={handleSave} onDirtyChange={handleDirtyChange} onExpand={() => setExpanded(true)} diff --git a/src/hooks/queries/admin/useAdminAttendanceQueries.ts b/src/hooks/queries/admin/useAdminAttendanceQueries.ts index e1c2d5bc..d9d00bb8 100644 --- a/src/hooks/queries/admin/useAdminAttendanceQueries.ts +++ b/src/hooks/queries/admin/useAdminAttendanceQueries.ts @@ -13,10 +13,10 @@ export function useAdminSessions(cardinal: number | null) { return useQuery({ queryKey: ['admin', 'sessions', clubId, cardinal], queryFn: async () => { - const res = await adminAttendanceApi.getSessions(clubId!, cardinal!); + const res = await adminAttendanceApi.getSessions(clubId!, cardinal ?? undefined); return res.data.data; }, - enabled: !!clubId && cardinal !== null, + enabled: !!clubId, }); } diff --git a/src/lib/apis/adminAttendance.ts b/src/lib/apis/adminAttendance.ts index 2d0d4ce4..39d98466 100644 --- a/src/lib/apis/adminAttendance.ts +++ b/src/lib/apis/adminAttendance.ts @@ -7,9 +7,9 @@ import type { } from '@/types/admin/attendance'; export const adminAttendanceApi = { - getSessions: (clubId: string, cardinal: number) => + getSessions: (clubId: string, cardinal?: number) => apiClient.get>(`/admin/clubs/${clubId}/sessions`, { - params: { cardinal }, + params: cardinal != null ? { cardinal } : undefined, }), getAttendanceBySession: (clubId: string, sessionId: number) =>