diff --git a/src/components/attendance/AttendanceCodeModal.tsx b/src/components/attendance/AttendanceCodeModal.tsx index 3c2229b2..ea9ea04f 100644 --- a/src/components/attendance/AttendanceCodeModal.tsx +++ b/src/components/attendance/AttendanceCodeModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useRef } from 'react'; +import { useState } from 'react'; import { CheckRoundIcon } from '@/assets/icons'; import { @@ -13,10 +13,12 @@ import { Icon, } from '@/components/ui'; import { InputOTP } from '@/components/attendance/InputOTP'; -import { useAttendanceSSE } from '@/hooks/attendance'; -import { useRemainingTime } from '@/hooks'; +// TODO: SSE 연결 안정화 후 복원 +// import { useEffect, useRef } from 'react'; +// import { useAttendanceSSE } from '@/hooks/attendance'; +// import { useRemainingTime } from '@/hooks'; +// import { toastError } from '@/stores/useToastStore'; import { formatModalDescription } from '@/lib/formatTime'; -import { toastError } from '@/stores/useToastStore'; interface AttendanceCodeModalProps { open: boolean; @@ -36,36 +38,39 @@ function AttendanceCodeModal({ location, }: AttendanceCodeModalProps) { const [code, setCode] = useState(''); - const { status, expiredAt: sseExpiredAt } = useAttendanceSSE(); - const isLoading = status === null; - const { minutes, seconds, isExpired } = useRemainingTime(sseExpiredAt ?? ''); + // TODO: SSE 연결 안정화 후 복원 + // const { status, expiredAt: sseExpiredAt } = useAttendanceSSE(); + // const isLoading = status === null; + // const { minutes, seconds, isExpired } = useRemainingTime(sseExpiredAt ?? ''); const isComplete = code.length === 6; const description = formatModalDescription(start, location); - const hasShownRef = useRef(false); + // TODO: SSE 연결 안정화 후 복원 + // const hasShownRef = useRef(false); function handleOpenChange(nextOpen: boolean) { if (!nextOpen) setCode(''); onOpenChange(nextOpen); } - useEffect(() => { - if (!open) return; - if (status === null) return; - - if ((status === 'qr-none' || status === 'qr-close') && !hasShownRef.current) { - hasShownRef.current = true; - toastError('현재 출석이 진행 중이 아닙니다.'); - - onOpenChange(false); - } - }, [open, status, onOpenChange]); - - useEffect(() => { - if (!open) { - hasShownRef.current = false; - } - }, [open]); + // TODO: SSE 연결 안정화 후 복원 + // useEffect(() => { + // if (!open) return; + // if (status === null) return; + // + // if ((status === 'qr-none' || status === 'qr-close') && !hasShownRef.current) { + // hasShownRef.current = true; + // toastError('현재 출석이 진행 중이 아닙니다.'); + // + // onOpenChange(false); + // } + // }, [open, status, onOpenChange]); + + // useEffect(() => { + // if (!open) { + // hasShownRef.current = false; + // } + // }, [open]); return ( @@ -96,7 +101,7 @@ function AttendanceCodeModal({ - {isLoading ? ( + {/* {isLoading ? (

출석 정보를 불러오는 중...

@@ -111,7 +116,9 @@ function AttendanceCodeModal({

출석 가능 시간이 만료되었습니다

- )} + )} */} + + {/* TODO: SSE 연결 안정화 후 출석 가능 시간 표시 복원 */} { onConfirm?.(code); handleOpenChange(false); diff --git a/src/components/attendance/AttendanceContent.tsx b/src/components/attendance/AttendanceContent.tsx index 5c0afa3a..2e653653 100644 --- a/src/components/attendance/AttendanceContent.tsx +++ b/src/components/attendance/AttendanceContent.tsx @@ -77,7 +77,7 @@ function AttendanceContent({ location = null, } = attendance ?? {}; const attendanceRate = attendanceData?.attendanceRate ?? attendance?.attendanceRate ?? 0; - const description = formatAttendanceDescription(start ?? '', end ?? '', location ?? ''); + const description = formatAttendanceDescription(start ?? '', end ?? '', location); return ( <> diff --git a/src/components/attendance/AttendanceQRContent.tsx b/src/components/attendance/AttendanceQRContent.tsx index d322a6bf..5a9c5682 100644 --- a/src/components/attendance/AttendanceQRContent.tsx +++ b/src/components/attendance/AttendanceQRContent.tsx @@ -11,7 +11,9 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui'; -import { useAttendanceSSE, useAttendanceQR } from '@/hooks/attendance'; +// TODO: SSE 연결 안정화 후 복원 +// import { useAttendanceSSE } from '@/hooks/attendance'; +import { useAttendanceQR } from '@/hooks/attendance'; import { useRemainingTime } from '@/hooks/useRemainingTime'; import { useClubId } from '@/stores/useClubStore'; @@ -23,8 +25,9 @@ function AttendanceQRContent({ sessionId }: AttendanceQRContentProps) { const { clubId: clubIdParam } = useParams<{ clubId: string }>(); const clubId = useClubId(); const { qrRef, qrData, isLoading } = useAttendanceQR(clubId, sessionId); - const { expiredAt: sseExpiredAt } = useAttendanceSSE(); - const { minutes, seconds, isExpired } = useRemainingTime(sseExpiredAt ?? ''); + // TODO: SSE 연결 안정화 후 복원 + // const { expiredAt: sseExpiredAt } = useAttendanceSSE(); + const { minutes, seconds, isExpired } = useRemainingTime(qrData?.expiredAt ?? ''); return (
@@ -73,7 +76,11 @@ function AttendanceQRContent({ sessionId }: AttendanceQRContentProps) {
출석 가능 시간 - {!sseExpiredAt ? '연결 중...' : isExpired ? '마감' : `${minutes}:${seconds}`} + {!qrData?.expiredAt + ? '로딩 중...' + : isExpired + ? '마감' + : `${minutes}:${seconds}`}

QR코드는 모바일만 제공하고 있어요.

diff --git a/src/components/attendance/AttendanceTodayCard.tsx b/src/components/attendance/AttendanceTodayCard.tsx index e8cbe606..28f2b03a 100644 --- a/src/components/attendance/AttendanceTodayCard.tsx +++ b/src/components/attendance/AttendanceTodayCard.tsx @@ -7,7 +7,7 @@ import { useState } from 'react'; import { CompleteIcon, RushIcon } from '@/assets/icons'; import { Card } from '@/components/ui'; import { AttendanceCodeModal } from '@/components/attendance/AttendanceCodeModal'; -import { toastError } from '@/stores/useToastStore'; +import { useIsAdmin } from '@/hooks/shared'; interface AttendanceTodayCardProps { overline: string; @@ -55,13 +55,10 @@ function AttendanceTodayCard({ }: AttendanceTodayCardProps) { const router = useRouter(); const { clubId } = useParams<{ clubId: string }>(); + const { isAdmin: isAdminUser } = useIsAdmin(); const [codeModalOpen, setCodeModalOpen] = useState(false); function handleSecondaryClick() { - if (!isAdmin) { - toastError('운영진만 사용할 수 있는 기능입니다.'); - return; - } if (sessionId == null) return; router.push(`/${clubId}/attendance/qr?sessionId=${sessionId}`); } @@ -77,9 +74,11 @@ function AttendanceTodayCard({ onPrimaryClick={() => setCodeModalOpen(true)} primaryButtonText={isChecked ? '출석 완료' : '출석하기'} primaryButtonDisabled={disabled || isChecked} - onSecondaryClick={handleSecondaryClick} - secondaryButtonText="출석코드 확인" - secondaryButtonDisabled={disabled || sessionId == null} + {...(isAdminUser && { + onSecondaryClick: handleSecondaryClick, + secondaryButtonText: '출석코드 확인', + secondaryButtonDisabled: disabled || sessionId == null, + })} > {isChecked ? ( (null); const [checkInError, setCheckInError] = useState(false); function openCodeModal() { - if (qrStatus === 'qr-none') { - toastError('아직 출석 코드가 생성되지 않았습니다.'); - return; - } - if (qrStatus === 'qr-close') { - toastError('QR 코드가 만료되었거나 존재하지 않습니다.'); - return; - } + // TODO: SSE 연결 안정화 후 복원 + // if (qrStatus === 'qr-none') { + // toastError('아직 출석 코드가 생성되지 않았습니다.'); + // return; + // } + // if (qrStatus === 'qr-close') { + // toastError('QR 코드가 만료되었거나 존재하지 않습니다.'); + // return; + // } setCodeModalOpen(true); } @@ -70,7 +73,8 @@ export function useCheckIn(options?: UseCheckInOptions) { return { isChecked, checkInError, - qrStatus, + // TODO: SSE 연결 안정화 후 복원 + // qrStatus, codeModalOpen, setCodeModalOpen, openCodeModal, diff --git a/src/lib/formatTime.ts b/src/lib/formatTime.ts index f64b8642..664c401c 100644 --- a/src/lib/formatTime.ts +++ b/src/lib/formatTime.ts @@ -16,11 +16,12 @@ function formatKoreanDate(date: Date) { * 출석 카드용 설명 문자열 생성 * "날짜 : 2026년 3월 20일 (7:00 PM~9:00 PM)\n장소 : 동아리방" */ -function formatAttendanceDescription(start: string, end: string, location: string) { +function formatAttendanceDescription(start: string, end: string, location: string | null) { const startDate = new Date(start); const endDate = new Date(end); - return `날짜 : ${formatKoreanDate(startDate)} (${formatTime(startDate)}~${formatTime(endDate)})\n장소 : ${location}`; + const datePart = `날짜 : ${formatKoreanDate(startDate)} (${formatTime(startDate)}~${formatTime(endDate)})`; + return location ? `${datePart}\n장소 : ${location}` : datePart; } /**