"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Table, Filter, Search, Download, RefreshCw, Plus, Edit, Trash2 } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, DialogFooter, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog"; import { toast } from "sonner"; import { useAuth } from "@/hooks/useAuth"; /** * 데이터 테이블 템플릿 컴포넌트 * 기존 하드코딩된 데이터 테이블 기능을 독립적인 컴포넌트로 분리 */ export interface DataTableTemplateProps { /** * 테이블 제목 */ title?: string; /** * 테이블 설명 */ description?: string; /** * 컬럼 정의 */ columns?: Array<{ id: string; label: string; type: string; visible: boolean; sortable: boolean; filterable: boolean; width?: number; }>; /** * 필터 설정 */ filters?: Array<{ id: string; label: string; type: "text" | "select" | "date" | "number"; options?: Array<{ label: string; value: string }>; }>; /** * 페이징 설정 */ pagination?: { enabled: boolean; pageSize: number; pageSizeOptions: number[]; showPageSizeSelector: boolean; showPageInfo: boolean; showFirstLast: boolean; }; /** * 액션 버튼 설정 */ actions?: { showSearchButton: boolean; searchButtonText: string; enableExport: boolean; enableRefresh: boolean; enableAdd: boolean; enableEdit: boolean; enableDelete: boolean; addButtonText: string; editButtonText: string; deleteButtonText: string; }; /** * 스타일 설정 */ style?: React.CSSProperties; /** * 클래스명 */ className?: string; /** * 미리보기 모드 (실제 데이터 없이 구조만 표시) */ isPreview?: boolean; } export const DataTableTemplate: React.FC = ({ title = "데이터 테이블", description = "데이터를 표시하고 관리하는 테이블", columns = [], filters = [], pagination = { enabled: true, pageSize: 10, pageSizeOptions: [5, 10, 20, 50], showPageSizeSelector: true, showPageInfo: true, showFirstLast: true, }, actions = { showSearchButton: true, searchButtonText: "검색", enableExport: true, enableRefresh: true, enableAdd: true, enableEdit: true, enableDelete: true, addButtonText: "추가", editButtonText: "수정", deleteButtonText: "삭제", }, style, className = "", isPreview = true, }) => { const { user } = useAuth(); // 🆕 검색 필터 관련 상태 const [searchFilterColumns, setSearchFilterColumns] = useState>(new Set()); const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false); const [searchValues, setSearchValues] = useState>({}); // 설정된 컬럼만 사용 (자동 생성 안함) const defaultColumns = React.useMemo(() => { return columns || []; }, [columns]); // 미리보기용 샘플 데이터 const sampleData = React.useMemo(() => { if (!isPreview) return []; return [ { id: 1, name: "홍길동", email: "hong@example.com", status: "활성", created_date: "2024-01-15" }, { id: 2, name: "김철수", email: "kim@example.com", status: "비활성", created_date: "2024-01-14" }, { id: 3, name: "이영희", email: "lee@example.com", status: "활성", created_date: "2024-01-13" }, ]; }, [isPreview]); const visibleColumns = defaultColumns.filter((col) => col.visible); // 🆕 컬럼명 -> 라벨 매핑 const columnLabels = React.useMemo(() => { const labels: Record = {}; defaultColumns.forEach(col => { labels[col.id] = col.label; }); return labels; }, [defaultColumns]); // 🆕 localStorage에서 필터 설정 복원 useEffect(() => { if (user?.userId && title) { const storageKey = `datatable-search-filter-${user.userId}-${title}`; const savedFilter = localStorage.getItem(storageKey); if (savedFilter) { try { const parsed = JSON.parse(savedFilter); setSearchFilterColumns(new Set(parsed)); } catch (e) { console.error("필터 설정 복원 실패:", e); } } } }, [user?.userId, title]); // 🆕 필터 저장 함수 const handleSaveSearchFilter = useCallback(() => { if (user?.userId && title) { const storageKey = `datatable-search-filter-${user.userId}-${title}`; const filterArray = Array.from(searchFilterColumns); localStorage.setItem(storageKey, JSON.stringify(filterArray)); toast.success("검색 필터 설정이 저장되었습니다."); } }, [user?.userId, title, searchFilterColumns]); // 🆕 필터 토글 함수 const handleToggleFilterColumn = useCallback((columnId: string) => { setSearchFilterColumns((prev) => { const newSet = new Set(prev); if (newSet.has(columnId)) { newSet.delete(columnId); } else { newSet.add(columnId); } return newSet; }); }, []); return ( {/* 헤더 영역 */}
{title} {description &&

{description}

} {/* 액션 버튼들 */}
{actions.enableRefresh && ( )} {actions.enableExport && ( )} {actions.enableAdd && ( )}
{/* 🆕 검색 필터 설정 버튼 영역 */} {defaultColumns.length > 0 && (
)} {/* 🆕 선택된 컬럼의 검색 입력 필드 */} {searchFilterColumns.size > 0 && (
{Array.from(searchFilterColumns).map((columnId) => { const column = defaultColumns.find(col => col.id === columnId); if (!column) return null; return (
setSearchValues(prev => ({...prev, [columnId]: e.target.value}))} disabled={isPreview} className="h-9 text-sm" />
); })}
)} {/* 검색 및 필터 영역 */}
{/* 검색 입력 */}
{actions.showSearchButton && ( )}
{/* 기존 필터 영역 (이제는 사용하지 않음) */} {filters.length > 0 && (
{filters.slice(0, 3).map((filter, index) => ( ))}
)}
{/* 데이터 테이블 */}
{/* 테이블 헤더 */} {/* 선택 체크박스 */} {/* 컬럼 헤더 */} {visibleColumns.map((column) => ( ))} {/* 액션 컬럼 */} {(actions.enableEdit || actions.enableDelete) && } {/* 테이블 바디 */} {isPreview ? ( // 미리보기 데이터 sampleData.map((row, index) => ( {visibleColumns.map((column) => ( ))} {(actions.enableEdit || actions.enableDelete) && ( )} )) ) : ( // 실제 데이터가 없는 경우 플레이스홀더 )}
{column.label} {column.sortable && (
)}
액션
{column.type === "select" && column.id === "status" ? ( {row[column.id]} ) : ( {row[column.id]} )}
{actions.enableEdit && ( )} {actions.enableDelete && ( )}
데이터가 없습니다.
{/* 페이징 영역 */} {pagination.enabled && (
{pagination.showPageInfo && {isPreview ? "1-3 of 3" : "0-0 of 0"} 항목 표시}
{/* 페이지 크기 선택 */} {pagination.showPageSizeSelector && (
표시:
)} {/* 페이지 네비게이션 */}
{pagination.showFirstLast && ( )} {pagination.showFirstLast && ( )}
)} {/* 🆕 검색 필터 설정 다이얼로그 */} 검색 필터 설정 표시할 검색 필터를 선택하세요. 선택하지 않은 필터는 숨겨집니다.
{defaultColumns.map((column) => (
handleToggleFilterColumn(column.id)} />
))}
); }; /** * 데이터 테이블 템플릿 기본 설정 */ export const getDefaultDataTableConfig = () => ({ template_code: "advanced-data-table-v2", template_name: "고급 데이터 테이블 v2", template_name_eng: "Advanced Data Table v2", description: "검색, 필터링, 페이징, CRUD 기능이 포함된 완전한 데이터 테이블 컴포넌트", category: "table", icon_name: "table", default_size: { width: 1000, height: 680, }, layout_config: { components: [ { type: "datatable", label: "고급 데이터 테이블", position: { x: 0, y: 0 }, size: { width: 1000, height: 680 }, style: { border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#ffffff", padding: "0", }, // 데이터 테이블 전용 설정 columns: [ { id: "id", label: "ID", type: "number", visible: true, sortable: true, filterable: false, width: 80 }, { id: "name", label: "이름", type: "text", visible: true, sortable: true, filterable: true, width: 150 }, { id: "email", label: "이메일", type: "email", visible: true, sortable: true, filterable: true, width: 200 }, { id: "status", label: "상태", type: "select", visible: true, sortable: true, filterable: true, width: 100 }, { id: "created_date", label: "생성일", type: "date", visible: true, sortable: true, filterable: true, width: 120, }, ], filters: [ { id: "status", label: "상태", type: "select", options: [ { label: "전체", value: "" }, { label: "활성", value: "active" }, { label: "비활성", value: "inactive" }, ], }, { id: "name", label: "이름", type: "text" }, { id: "email", label: "이메일", type: "text" }, ], pagination: { enabled: true, pageSize: 10, pageSizeOptions: [5, 10, 20, 50, 100], showPageSizeSelector: true, showPageInfo: true, showFirstLast: true, }, actions: { showSearchButton: true, searchButtonText: "검색", enableExport: true, enableRefresh: true, enableAdd: true, enableEdit: true, enableDelete: true, addButtonText: "추가", editButtonText: "수정", deleteButtonText: "삭제", }, // 모달 설정 addModalConfig: { title: "새 데이터 추가", description: "테이블에 새로운 데이터를 추가합니다.", width: "lg", layout: "two-column", gridColumns: 2, fieldOrder: ["name", "email", "status"], requiredFields: ["name", "email"], hiddenFields: ["id", "created_date"], advancedFieldConfigs: { status: { type: "select", options: [ { label: "활성", value: "active" }, { label: "비활성", value: "inactive" }, ], }, }, submitButtonText: "추가", cancelButtonText: "취소", }, }, ], }, }); export default DataTableTemplate;