'use client'; import { useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlus, faImage, faPlay, faStop } from '@fortawesome/free-solid-svg-icons'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; import type { AlertConfigItem } from '@/types/response/donation/alertConfig'; import { PER_PAGE_OPTIONS } from '@/constants/donation'; type Props = { items: AlertConfigItem[]; loading: boolean; saving: boolean; checkedIDs: Set; setCheckedIDs: React.Dispatch>>; page: number; setPage: React.Dispatch>; perPage: number; setPerPage: React.Dispatch>; onNew: () => void; onEdit: (item: AlertConfigItem) => void; onBatchDelete: () => void; }; export default function AlertListPanel({ items, loading, saving, checkedIDs, setCheckedIDs, page, setPage, perPage, setPerPage, onNew, onEdit, onBatchDelete }: Props) { const [playingAudio, setPlayingAudio] = useState(null); const [previewImageUrl, setPreviewImageUrl] = useState(null); const [thumbErrors, setThumbErrors] = useState>(new Set()); const audioRef = useRef(null); // ── 페이징 ─────────────────────────────────────── const totalPages = Math.max(1, Math.ceil(items.length / perPage)); const pagedItems = items.slice((page - 1) * perPage, page * perPage); const handlePerPageChange = (value: number) => { setPerPage(value); setPage(1); }; // ── 전체선택 ───────────────────────────────────── const visibleIDs = pagedItems.map(i => i.id); const checkedCount = visibleIDs.filter(id => checkedIDs.has(id)).length; const allChecked = pagedItems.length > 0 && checkedCount === visibleIDs.length; const isIndeterminate = checkedCount > 0 && checkedCount < visibleIDs.length; const handleSelectAll = () => { setCheckedIDs(prev => { const next = new Set(prev); if (allChecked) { visibleIDs.forEach(id => next.delete(id)); } else { visibleIDs.forEach(id => next.add(id)); } return next; }); }; const handleToggleCheck = (id: number) => { setCheckedIDs(prev => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; // ── 사운드 재생/정지 ───────────────────────────── const handlePlaySound = (itemId: number, soundUrl: string) => { // 이미 재생 중이면 정지 if (playingAudio === itemId && audioRef.current) { audioRef.current.pause(); audioRef.current = null; setPlayingAudio(null); return; } // 기존 재생 정지 if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } const audio = new Audio(soundUrl); audioRef.current = audio; setPlayingAudio(itemId); audio.play().catch(() => {}); audio.addEventListener('ended', () => { setPlayingAudio(null); audioRef.current = null; }); }; // unmount 시 오디오 정리 useEffect(() => { return () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } }; }, []); return (
총 {items.length}개 {checkedIDs.size > 0 && ( ({checkedIDs.size}개 선택) )}
{loading ? (
준비 중...
) : items.length === 0 ? (
등록된 알림 설정이 없습니다.
) : ( {pagedItems.map(item => { const isChecked = checkedIDs.has(item.id); const hasImage = item.enableImage && item.imageUrl; const hasSound = item.enableSound && item.soundUrl; return ( ); })}
조건 금액 제목 보낼 내용 노출(초) 활성 미디어 비고
handleToggleCheck(item.id)} aria-label={`${item.id} 선택`} /> {item.matchType === 1 ? '정확히' : '이상'} {item.amount.toLocaleString()}원 {item.title || 미입력} {item.message} {item.displayDurationSec}초 {item.isActive ? '활성' : '비활성'}
{hasImage && ( thumbErrors.has(item.id) ? ( ) : ( ) )} {hasSound && ( )} {!hasImage && !hasSound && ( - )}
)}
{totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => i + 1).map(p => ( ))}
)} {/* 이미지 확대 모달 */} { if (!open) { setPreviewImageUrl(null); } }}> 이미지 미리보기 {previewImageUrl && ( 알림 이미지 )}
); }