"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Plus, Pencil, Trash2, Search, RefreshCw } from "lucide-react"; import { ScrollToTop } from "@/components/common/ScrollToTop"; import { SystemNotice, CreateSystemNoticePayload, getSystemNotices, createSystemNotice, updateSystemNotice, deleteSystemNotice, } from "@/lib/api/systemNotice"; import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; function getPriorityLabel(priority: number): { label: string; variant: "default" | "secondary" | "destructive" | "outline" } { if (priority >= 3) return { label: "높음", variant: "destructive" }; if (priority === 2) return { label: "보통", variant: "default" }; return { label: "낮음", variant: "secondary" }; } function formatDate(dateStr: string): string { if (!dateStr) return "-"; return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", }); } const EMPTY_FORM: CreateSystemNoticePayload = { title: "", content: "", is_active: true, priority: 1, }; export default function SystemNoticesPage() { const [notices, setNotices] = useState([]); const [filteredNotices, setFilteredNotices] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMsg, setErrorMsg] = useState(null); const [searchText, setSearchText] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [isFormOpen, setIsFormOpen] = useState(false); const [editTarget, setEditTarget] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [isSaving, setIsSaving] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const loadNotices = useCallback(async () => { setIsLoading(true); setErrorMsg(null); const result = await getSystemNotices(); if (result.success && result.data) { setNotices(result.data); } else { setErrorMsg(result.message || "공지사항 목록을 불러오는 데 실패했습니다."); } setIsLoading(false); }, []); useEffect(() => { loadNotices(); }, [loadNotices]); useEffect(() => { let result = [...notices]; if (statusFilter !== "all") { const isActive = statusFilter === "active"; result = result.filter((n) => n.is_active === isActive); } if (searchText.trim()) { const keyword = searchText.toLowerCase(); result = result.filter( (n) => n.title.toLowerCase().includes(keyword) || n.content.toLowerCase().includes(keyword) ); } setFilteredNotices(result); }, [notices, searchText, statusFilter]); const handleOpenCreate = () => { setEditTarget(null); setFormData(EMPTY_FORM); setIsFormOpen(true); }; const handleOpenEdit = (notice: SystemNotice) => { setEditTarget(notice); setFormData({ title: notice.title, content: notice.content, is_active: notice.is_active, priority: notice.priority, }); setIsFormOpen(true); }; const handleSave = async () => { if (!formData.title.trim()) { alert("제목을 입력해주세요."); return; } if (!formData.content.trim()) { alert("내용을 입력해주세요."); return; } setIsSaving(true); let result; if (editTarget) { result = await updateSystemNotice(editTarget.id, formData); } else { result = await createSystemNotice(formData); } if (result.success) { setIsFormOpen(false); await loadNotices(); } else { alert(result.message || "저장에 실패했습니다."); } setIsSaving(false); }; const handleDelete = async () => { if (!deleteTarget) return; setIsDeleting(true); const result = await deleteSystemNotice(deleteTarget.id); if (result.success) { setDeleteTarget(null); await loadNotices(); } else { alert(result.message || "삭제에 실패했습니다."); } setIsDeleting(false); }; const columns: RDVColumn[] = [ { key: "title", label: "제목", render: (_val, notice) => ( {notice.title} ), }, { key: "is_active", label: "상태", width: "100px", render: (_val, notice) => ( {notice.is_active ? "활성" : "비활성"} ), }, { key: "priority", label: "우선순위", width: "100px", render: (_val, notice) => { const p = getPriorityLabel(notice.priority); return {p.label}; }, }, { key: "created_by", label: "작성자", width: "120px", hideOnMobile: true, render: (_val, notice) => ( {notice.created_by || "-"} ), }, { key: "created_at", label: "작성일", width: "120px", hideOnMobile: true, render: (_val, notice) => ( {formatDate(notice.created_at)} ), }, ]; const cardFields: RDVCardField[] = [ { label: "작성자", render: (notice) => notice.created_by || "-", }, { label: "작성일", render: (notice) => formatDate(notice.created_at), }, ]; return (
{/* 페이지 헤더 */}

시스템 공지사항

시스템 사용자에게 전달할 공지사항을 관리합니다.

{/* 에러 메시지 */} {errorMsg && (

오류가 발생했습니다

{errorMsg}

)} {/* 검색 툴바 */}
setSearchText(e.target.value)} className="h-10 pl-10 text-sm" />
{filteredNotices.length}
data={filteredNotices} columns={columns} keyExtractor={(n) => String(n.id)} isLoading={isLoading} emptyMessage="공지사항이 없습니다." skeletonCount={5} cardTitle={(n) => n.title} cardHeaderRight={(n) => (
)} cardSubtitle={(n) => { const p = getPriorityLabel(n.priority); return ( {n.is_active ? "활성" : "비활성"} {p.label} ); }} cardFields={cardFields} actionsLabel="관리" actionsWidth="120px" renderActions={(notice) => ( <> )} />
{/* 등록/수정 모달 */} {editTarget ? "공지사항 수정" : "공지사항 등록"} {editTarget ? "공지사항 내용을 수정합니다." : "새로운 공지사항을 등록합니다."}
setFormData((prev) => ({ ...prev, title: e.target.value }))} placeholder="공지사항 제목을 입력하세요" className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" />