"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { LayoutFormModal } from "@/components/admin/LayoutFormModal"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Plus, Search, MoreHorizontal, Edit, Copy, Trash2, Eye, Grid, Layout, LayoutDashboard, Table, Navigation, FileText, Building, } from "lucide-react"; import { LayoutStandard, LAYOUT_CATEGORIES, LayoutCategory } from "@/types/layout"; import { layoutApi } from "@/lib/api/layout"; import { toast } from "sonner"; // 코드 레벨 레이아웃 타입 interface CodeLayout { id: string; name: string; nameEng?: string; description?: string; category: string; type: "code"; isActive: boolean; tags: string[]; metadata?: any; zones: number; } // 카테고리 아이콘 매핑 const CATEGORY_ICONS = { basic: Grid, form: FileText, table: Table, dashboard: LayoutDashboard, navigation: Navigation, content: Layout, business: Building, }; // 카테고리 이름 매핑 const CATEGORY_NAMES = { basic: "기본", form: "폼", table: "테이블", dashboard: "대시보드", navigation: "네비게이션", content: "컨텐츠", business: "업무용", }; export default function LayoutManagementPage() { const [layouts, setLayouts] = useState([]); const [codeLayouts, setCodeLayouts] = useState([]); const [loading, setLoading] = useState(true); const [codeLoading, setCodeLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [total, setTotal] = useState(0); const [activeTab, setActiveTab] = useState("db"); // 모달 상태 const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [layoutToDelete, setLayoutToDelete] = useState(null); const [createModalOpen, setCreateModalOpen] = useState(false); // 카테고리별 개수 const [categoryCounts, setCategoryCounts] = useState>({}); // 레이아웃 목록 로드 const loadLayouts = async () => { try { setLoading(true); const params = { page: currentPage, size: 20, searchTerm: searchTerm || undefined, category: selectedCategory !== "all" ? (selectedCategory as LayoutCategory) : undefined, }; const response = await layoutApi.getLayouts(params); setLayouts(response.data); setTotalPages(response.totalPages); setTotal(response.total); } catch (error) { console.error("레이아웃 목록 조회 실패:", error); toast.error("레이아웃 목록을 불러오는데 실패했습니다."); setLayouts([]); } finally { setLoading(false); } }; // 카테고리별 개수 로드 const loadCategoryCounts = async () => { try { const counts = await layoutApi.getLayoutCountsByCategory(); setCategoryCounts(counts); } catch (error) { console.error("카테고리 개수 조회 실패:", error); } }; // 코드 레벨 레이아웃 로드 const loadCodeLayouts = async () => { try { setCodeLoading(true); const response = await fetch("/api/admin/layouts/list"); const result = await response.json(); if (result.success) { setCodeLayouts(result.data.codeLayouts); } else { toast.error("코드 레이아웃 목록을 불러오는데 실패했습니다."); setCodeLayouts([]); } } catch (error) { console.error("코드 레이아웃 조회 실패:", error); toast.error("코드 레이아웃 목록을 불러오는데 실패했습니다."); setCodeLayouts([]); } finally { setCodeLoading(false); } }; useEffect(() => { loadLayouts(); }, [currentPage, selectedCategory]); useEffect(() => { loadCategoryCounts(); loadCodeLayouts(); }, []); // 검색 const handleSearch = () => { setCurrentPage(1); loadLayouts(); }; // 엔터키 검색 const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { handleSearch(); } }; // 레이아웃 삭제 const handleDelete = async (layout: LayoutStandard) => { setLayoutToDelete(layout); setDeleteDialogOpen(true); }; const confirmDelete = async () => { if (!layoutToDelete) return; try { await layoutApi.deleteLayout(layoutToDelete.layoutCode); toast.success("레이아웃이 삭제되었습니다."); loadLayouts(); loadCategoryCounts(); } catch (error) { console.error("레이아웃 삭제 실패:", error); toast.error("레이아웃 삭제에 실패했습니다."); } finally { setDeleteDialogOpen(false); setLayoutToDelete(null); } }; // 레이아웃 복제 const handleDuplicate = async (layout: LayoutStandard) => { try { const newName = `${layout.layoutName} (복사)`; await layoutApi.duplicateLayout(layout.layoutCode, { newName }); toast.success("레이아웃이 복제되었습니다."); loadLayouts(); loadCategoryCounts(); } catch (error) { console.error("레이아웃 복제 실패:", error); toast.error("레이아웃 복제에 실패했습니다."); } }; // 페이지네이션 const handlePageChange = (page: number) => { setCurrentPage(page); }; return (
{/* 페이지 제목 */}

레이아웃 관리

화면 레이아웃을 생성하고 관리합니다

{/* 검색 및 필터 */}
setSearchTerm(e.target.value)} onKeyPress={handleKeyPress} className="pl-10" />
{/* 카테고리 탭 */} 전체 ({total}) {Object.entries(LAYOUT_CATEGORIES).map(([key, value]) => { const Icon = CATEGORY_ICONS[value as keyof typeof CATEGORY_ICONS]; const count = categoryCounts[value] || 0; return ( {CATEGORY_NAMES[value as keyof typeof CATEGORY_NAMES]} ({count}) ); })}
{loading ? (
로딩 중...
) : layouts.length === 0 ? (
레이아웃이 없습니다.
) : ( <> {/* 레이아웃 그리드 */}
{layouts.map((layout) => { const CategoryIcon = CATEGORY_ICONS[layout.category as keyof typeof CATEGORY_ICONS]; return (
{CATEGORY_NAMES[layout.category as keyof typeof CATEGORY_NAMES]}
미리보기 편집 handleDuplicate(layout)}> 복제 handleDelete(layout)} className="text-red-600"> 삭제
{layout.layoutName} {layout.description && (

{layout.description}

)}
타입: {layout.layoutType}
존 개수: {layout.zonesConfig.length}개
{layout.isPublic === "Y" && ( 공개 레이아웃 )}
); })}
{/* 페이지네이션 */} {totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( ))}
)} )}
{/* 삭제 확인 다이얼로그 */} 레이아웃 삭제 정말로 "{layoutToDelete?.layoutName}" 레이아웃을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. 취소 삭제 {/* 새 레이아웃 생성 모달 */} { loadLayouts(); loadCategoryCounts(); }} />
); }