ERP-node/frontend/components/screen/panels/TablesPanel.tsx

217 lines
7.9 KiB
TypeScript
Raw Normal View History

2025-09-02 16:18:38 +09:00
"use client";
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Database,
ChevronDown,
ChevronRight,
Type,
Hash,
Calendar,
CheckSquare,
List,
AlignLeft,
Code,
Building,
File,
Search,
} from "lucide-react";
import { TableInfo, WebType } from "@/types/screen";
interface TablesPanelProps {
tables: TableInfo[];
searchTerm: string;
onSearchChange: (term: string) => void;
onDragStart: (e: React.DragEvent, table: TableInfo, column?: any) => void;
selectedTableName?: string;
}
// 위젯 타입별 아이콘
const getWidgetIcon = (widgetType: WebType) => {
switch (widgetType) {
case "text":
case "email":
case "tel":
2025-10-17 16:21:08 +09:00
return <Type className="text-primary h-3 w-3" />;
2025-09-02 16:18:38 +09:00
case "number":
case "decimal":
return <Hash className="h-3 w-3 text-green-600" />;
case "date":
case "datetime":
return <Calendar className="h-3 w-3 text-purple-600" />;
case "select":
case "dropdown":
return <List className="h-3 w-3 text-orange-600" />;
case "textarea":
case "text_area":
return <AlignLeft className="h-3 w-3 text-indigo-600" />;
case "boolean":
case "checkbox":
2025-10-17 16:21:08 +09:00
return <CheckSquare className="text-primary h-3 w-3" />;
2025-09-02 16:18:38 +09:00
case "code":
2025-10-17 16:21:08 +09:00
return <Code className="text-muted-foreground h-3 w-3" />;
2025-09-02 16:18:38 +09:00
case "entity":
return <Building className="h-3 w-3 text-cyan-600" />;
case "file":
return <File className="h-3 w-3 text-yellow-600" />;
default:
return <Type className="h-3 w-3 text-gray-500" />;
}
};
export const TablesPanel: React.FC<TablesPanelProps> = ({
tables,
searchTerm,
onSearchChange,
onDragStart,
selectedTableName,
}) => {
const [expandedTables, setExpandedTables] = useState<Set<string>>(new Set());
const toggleTable = (tableName: string) => {
const newExpanded = new Set(expandedTables);
if (newExpanded.has(tableName)) {
newExpanded.delete(tableName);
} else {
newExpanded.add(tableName);
}
setExpandedTables(newExpanded);
};
const filteredTables = tables.filter(
(table) =>
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
table.columns.some((col) => col.columnName.toLowerCase().includes(searchTerm.toLowerCase())),
);
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
2025-10-17 16:21:08 +09:00
<div className="border-b p-4">
2025-09-02 16:18:38 +09:00
{selectedTableName && (
2025-10-17 16:21:08 +09:00
<div className="border-primary/20 bg-primary/5 mb-3 rounded-lg border p-3">
<div className="text-xs font-semibold"> </div>
<div className="mt-1.5 flex items-center gap-2">
<Database className="text-primary h-3 w-3" />
<span className="font-mono text-xs font-medium">{selectedTableName}</span>
2025-09-02 16:18:38 +09:00
</div>
</div>
)}
{/* 검색 */}
<div className="relative">
2025-10-17 16:21:08 +09:00
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
2025-09-02 16:18:38 +09:00
<input
type="text"
2025-10-17 16:21:08 +09:00
placeholder="테이블명, 컬럼명 검색..."
2025-09-02 16:18:38 +09:00
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
2025-10-17 16:21:08 +09:00
className="border-input bg-background focus-visible:ring-ring h-8 w-full rounded-md border px-3 pl-8 text-xs focus-visible:ring-1 focus-visible:outline-none"
2025-09-02 16:18:38 +09:00
/>
</div>
2025-10-17 16:21:08 +09:00
<div className="text-muted-foreground mt-2 text-xs"> {filteredTables.length}</div>
2025-09-02 16:18:38 +09:00
</div>
{/* 테이블 목록 */}
2025-10-17 16:21:08 +09:00
<div className="flex-1 overflow-y-auto">
<div className="space-y-1.5 p-3">
2025-09-02 16:18:38 +09:00
{filteredTables.map((table) => {
const isExpanded = expandedTables.has(table.tableName);
return (
2025-10-17 16:21:08 +09:00
<div key={table.tableName} className="bg-card rounded-lg border">
2025-09-02 16:18:38 +09:00
{/* 테이블 헤더 */}
<div
2025-10-17 16:21:08 +09:00
className="hover:bg-accent/50 flex cursor-pointer items-center justify-between p-2.5 transition-colors"
2025-09-02 16:18:38 +09:00
onClick={() => toggleTable(table.tableName)}
>
2025-10-17 16:21:08 +09:00
<div className="flex flex-1 items-center gap-2">
2025-09-02 16:18:38 +09:00
{isExpanded ? (
2025-10-17 16:21:08 +09:00
<ChevronDown className="text-muted-foreground h-3.5 w-3.5" />
2025-09-02 16:18:38 +09:00
) : (
2025-10-17 16:21:08 +09:00
<ChevronRight className="text-muted-foreground h-3.5 w-3.5" />
2025-09-02 16:18:38 +09:00
)}
2025-10-17 16:21:08 +09:00
<Database className="text-primary h-3.5 w-3.5" />
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-semibold">{table.tableLabel || table.tableName}</div>
<div className="text-muted-foreground text-xs">{table.columns.length}</div>
2025-09-02 16:18:38 +09:00
</div>
</div>
<Button
size="sm"
variant="ghost"
draggable
onDragStart={(e) => onDragStart(e, table)}
2025-10-17 16:21:08 +09:00
className="h-6 px-2 text-xs"
2025-09-02 16:18:38 +09:00
>
</Button>
</div>
{/* 컬럼 목록 */}
{isExpanded && (
2025-10-17 16:21:08 +09:00
<div className="bg-muted/30 border-t">
<div className={`${table.columns.length > 8 ? "max-h-64 overflow-y-auto" : ""}`}>
2025-09-02 16:18:38 +09:00
{table.columns.map((column, index) => (
<div
key={column.columnName}
2025-10-17 16:21:08 +09:00
className={`hover:bg-accent/50 flex cursor-grab items-center justify-between p-2 transition-colors ${
index < table.columns.length - 1 ? "border-border/50 border-b" : ""
2025-09-02 16:18:38 +09:00
}`}
draggable
onDragStart={(e) => onDragStart(e, table, column)}
>
2025-10-17 16:21:08 +09:00
<div className="flex min-w-0 flex-1 items-center gap-2">
2025-09-02 16:18:38 +09:00
{getWidgetIcon(column.widgetType)}
<div className="min-w-0 flex-1">
2025-10-17 16:21:08 +09:00
<div className="truncate text-xs font-semibold">
2025-09-08 14:20:01 +09:00
{column.columnLabel || column.columnName}
</div>
2025-10-17 16:21:08 +09:00
<div className="text-muted-foreground truncate text-xs">{column.dataType}</div>
2025-09-02 16:18:38 +09:00
</div>
</div>
2025-10-17 16:21:08 +09:00
<div className="flex flex-shrink-0 items-center gap-1">
<Badge variant="secondary" className="h-4 px-1.5 text-xs">
2025-09-02 16:18:38 +09:00
{column.widgetType}
</Badge>
{column.required && (
2025-10-17 16:21:08 +09:00
<Badge variant="destructive" className="h-4 px-1.5 text-xs">
2025-09-02 16:18:38 +09:00
</Badge>
)}
</div>
</div>
))}
{/* 컬럼 수가 많을 때 안내 메시지 */}
{table.columns.length > 8 && (
2025-10-17 16:21:08 +09:00
<div className="bg-muted sticky bottom-0 p-2 text-center">
<div className="text-muted-foreground text-xs">
2025-09-02 16:18:38 +09:00
📜 {table.columns.length} ( )
</div>
</div>
)}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
{/* 푸터 */}
<div className="border-t border-gray-200 bg-gray-50 p-3">
2025-10-17 16:21:08 +09:00
<div className="text-muted-foreground text-xs">💡 </div>
2025-09-02 16:18:38 +09:00
</div>
</div>
);
};
export default TablesPanel;