ERP-node/frontend/components/screen/templates/DataTableTemplate.tsx

480 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React 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";
/**
* 데이터 테이블 템플릿 컴포넌트
* 기존 하드코딩된 데이터 테이블 기능을 독립적인 컴포넌트로 분리
*/
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<DataTableTemplateProps> = ({
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 defaultColumns = React.useMemo(() => {
if (columns.length > 0) return columns;
return [
{ 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,
},
];
}, [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);
return (
<Card className={`h-full w-full ${className}`} style={style}>
{/* 헤더 영역 */}
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center space-x-2">
<Table className="h-5 w-5" />
<span>{title}</span>
</CardTitle>
{description && <p className="text-muted-foreground mt-1 text-sm">{description}</p>}
</div>
{/* 액션 버튼들 */}
<div className="flex items-center space-x-2">
{actions.enableRefresh && (
<Button variant="outline" size="sm">
<RefreshCw className="h-4 w-4" />
</Button>
)}
{actions.enableExport && (
<Button variant="outline" size="sm">
<Download className="mr-1 h-4 w-4" />
</Button>
)}
{actions.enableAdd && (
<Button size="sm">
<Plus className="mr-1 h-4 w-4" />
{actions.addButtonText}
</Button>
)}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* 검색 및 필터 영역 */}
<div className="flex flex-col space-y-4 lg:flex-row lg:items-center lg:justify-between lg:space-y-0">
{/* 검색 입력 */}
<div className="flex items-center space-x-2">
<div className="relative min-w-[200px] flex-1">
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
<Input placeholder="검색어를 입력하세요..." className="pl-10" disabled={isPreview} />
</div>
{actions.showSearchButton && (
<Button variant="outline" disabled={isPreview}>
{actions.searchButtonText}
</Button>
)}
</div>
{/* 필터 영역 */}
{filters.length > 0 && (
<div className="flex items-center space-x-2">
<Filter className="text-muted-foreground h-4 w-4" />
{filters.slice(0, 3).map((filter, index) => (
<Select key={filter.id || `filter-${index}`} disabled={isPreview}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder={filter.label} />
</SelectTrigger>
<SelectContent>
{filter.options?.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
))}
</div>
)}
</div>
{/* 데이터 테이블 */}
<div className="rounded-md border">
<div className="overflow-x-auto">
<table className="w-full">
{/* 테이블 헤더 */}
<thead className="bg-muted/50 border-b">
<tr>
{/* 선택 체크박스 */}
<th className="w-12 p-3">
<Checkbox disabled={isPreview} />
</th>
{/* 컬럼 헤더 */}
{visibleColumns.map((column) => (
<th key={column.id} className="p-3 text-left font-medium" style={{ width: column.width }}>
<div className="flex items-center space-x-1">
<span>{column.label}</span>
{column.sortable && (
<div className="flex flex-col">
<div className="bg-muted-foreground h-1 w-1"></div>
<div className="bg-muted-foreground mt-0.5 h-1 w-1"></div>
</div>
)}
</div>
</th>
))}
{/* 액션 컬럼 */}
{(actions.enableEdit || actions.enableDelete) && <th className="w-24 p-3 text-center"></th>}
</tr>
</thead>
{/* 테이블 바디 */}
<tbody>
{isPreview ? (
// 미리보기 데이터
sampleData.map((row, index) => (
<tr key={index} className="hover:bg-muted/30 border-b">
<td className="p-3">
<Checkbox disabled />
</td>
{visibleColumns.map((column) => (
<td key={column.id} className="p-3">
{column.type === "select" && column.id === "status" ? (
<Badge variant={row[column.id] === "활성" ? "default" : "secondary"}>
{row[column.id]}
</Badge>
) : (
<span className="text-sm">{row[column.id]}</span>
)}
</td>
))}
{(actions.enableEdit || actions.enableDelete) && (
<td className="p-3">
<div className="flex items-center justify-center space-x-1">
{actions.enableEdit && (
<Button variant="ghost" size="sm" disabled>
<Edit className="h-3 w-3" />
</Button>
)}
{actions.enableDelete && (
<Button variant="ghost" size="sm" disabled>
<Trash2 className="h-3 w-3 text-red-500" />
</Button>
)}
</div>
</td>
)}
</tr>
))
) : (
// 실제 데이터가 없는 경우 플레이스홀더
<tr>
<td colSpan={visibleColumns.length + 2} className="text-muted-foreground p-8 text-center">
.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{/* 페이징 영역 */}
{pagination.enabled && (
<div className="flex items-center justify-between">
<div className="text-muted-foreground flex items-center space-x-2 text-sm">
{pagination.showPageInfo && <span>{isPreview ? "1-3 of 3" : "0-0 of 0"} </span>}
</div>
<div className="flex items-center space-x-2">
{/* 페이지 크기 선택 */}
{pagination.showPageSizeSelector && (
<div className="flex items-center space-x-2">
<span className="text-muted-foreground text-sm">:</span>
<Select disabled={isPreview}>
<SelectTrigger className="w-16">
<SelectValue placeholder={pagination.pageSize.toString()} />
</SelectTrigger>
<SelectContent>
{pagination.pageSizeOptions.map((size) => (
<SelectItem key={size} value={size.toString()}>
{size}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* 페이지 네비게이션 */}
<div className="flex items-center space-x-1">
{pagination.showFirstLast && (
<Button variant="outline" size="sm" disabled>
«
</Button>
)}
<Button variant="outline" size="sm" disabled>
</Button>
<Button variant="outline" size="sm" className="bg-primary text-primary-foreground">
1
</Button>
<Button variant="outline" size="sm" disabled>
</Button>
{pagination.showFirstLast && (
<Button variant="outline" size="sm" disabled>
»
</Button>
)}
</div>
</div>
</div>
)}
</CardContent>
</Card>
);
};
/**
* 데이터 테이블 템플릿 기본 설정
*/
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;