2025-09-02 16:18:38 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-10-28 16:26:55 +09:00
|
|
|
import React from "react";
|
2025-09-02 16:18:38 +09:00
|
|
|
import { Badge } from "@/components/ui/badge";
|
2025-10-28 16:26:55 +09:00
|
|
|
import { Database, Type, Hash, Calendar, CheckSquare, List, AlignLeft, Code, Building, File } from "lucide-react";
|
2025-09-02 16:18:38 +09:00
|
|
|
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;
|
2025-10-23 10:07:55 +09:00
|
|
|
placedColumns?: Set<string>; // 이미 배치된 컬럼명 집합 (tableName.columnName 형식)
|
2025-09-02 16:18:38 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 위젯 타입별 아이콘
|
|
|
|
|
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,
|
|
|
|
|
onDragStart,
|
2025-10-23 10:07:55 +09:00
|
|
|
placedColumns = new Set(),
|
2025-09-02 16:18:38 +09:00
|
|
|
}) => {
|
feat: 테이블 탭 드래그앤드롭 개선 및 AI-개발자 협업 규칙 수립
주요 변경사항:
- 드래그앤드롭 컬럼의 라벨 숨김 및 placeholder로 라벨명 표시
- 기본 높이 30px로 변경
- 5개 시스템 컬럼(id, created_date, updated_date, writer, company_code) 숨김
- AI-개발자 협업 작업 수칙 문서 작성 및 .cursorrules에 통합
파일 변경:
- frontend/components/screen/ScreenDesigner.tsx
* getDefaultHeight(): 기본 높이를 30px로 변경
* handleDrop(): labelDisplay false, placeholder 추가
- frontend/components/screen/panels/TablesPanel.tsx
* hiddenColumns Set으로 시스템 컬럼 필터링
- .cursor/rules/ai-developer-collaboration-rules.mdc (신규)
* 확인 우선, 한 번에 하나, 철저한 마무리 원칙
* 데이터베이스 검증, 코드 수정, 테스트, 커뮤니케이션 규칙
- .cursorrules
* 필수 확인 규칙 섹션 추가
* 모든 작업 시작/완료 시 협업 규칙 확인 강제화
2025-11-07 17:12:01 +09:00
|
|
|
// 숨길 기본 컬럼 목록 (id, created_date, updated_date, writer, company_code)
|
|
|
|
|
const hiddenColumns = new Set(['id', 'created_date', 'updated_date', 'writer', 'company_code']);
|
|
|
|
|
|
|
|
|
|
// 이미 배치된 컬럼 + 기본 컬럼을 제외한 테이블 정보 생성
|
2025-10-23 10:07:55 +09:00
|
|
|
const tablesWithAvailableColumns = tables.map((table) => ({
|
|
|
|
|
...table,
|
|
|
|
|
columns: table.columns.filter((col) => {
|
|
|
|
|
const columnKey = `${table.tableName}.${col.columnName}`;
|
feat: 테이블 탭 드래그앤드롭 개선 및 AI-개발자 협업 규칙 수립
주요 변경사항:
- 드래그앤드롭 컬럼의 라벨 숨김 및 placeholder로 라벨명 표시
- 기본 높이 30px로 변경
- 5개 시스템 컬럼(id, created_date, updated_date, writer, company_code) 숨김
- AI-개발자 협업 작업 수칙 문서 작성 및 .cursorrules에 통합
파일 변경:
- frontend/components/screen/ScreenDesigner.tsx
* getDefaultHeight(): 기본 높이를 30px로 변경
* handleDrop(): labelDisplay false, placeholder 추가
- frontend/components/screen/panels/TablesPanel.tsx
* hiddenColumns Set으로 시스템 컬럼 필터링
- .cursor/rules/ai-developer-collaboration-rules.mdc (신규)
* 확인 우선, 한 번에 하나, 철저한 마무리 원칙
* 데이터베이스 검증, 코드 수정, 테스트, 커뮤니케이션 규칙
- .cursorrules
* 필수 확인 규칙 섹션 추가
* 모든 작업 시작/완료 시 협업 규칙 확인 강제화
2025-11-07 17:12:01 +09:00
|
|
|
// 기본 컬럼 또는 이미 배치된 컬럼은 제외
|
|
|
|
|
return !hiddenColumns.has(col.columnName) && !placedColumns.has(columnKey);
|
2025-10-23 10:07:55 +09:00
|
|
|
}),
|
|
|
|
|
}));
|
|
|
|
|
|
2025-10-28 16:26:55 +09:00
|
|
|
// 검색어가 있으면 컬럼 필터링
|
2025-10-23 10:07:55 +09:00
|
|
|
const filteredTables = tablesWithAvailableColumns
|
2025-10-28 16:26:55 +09:00
|
|
|
.map((table) => {
|
|
|
|
|
if (!searchTerm) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const searchLower = searchTerm.toLowerCase();
|
|
|
|
|
|
|
|
|
|
// 테이블명이 검색어와 일치하면 모든 컬럼 표시
|
|
|
|
|
if (
|
|
|
|
|
table.tableName.toLowerCase().includes(searchLower) ||
|
|
|
|
|
(table.tableLabel && table.tableLabel.toLowerCase().includes(searchLower))
|
|
|
|
|
) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 그렇지 않으면 컬럼명/라벨이 검색어와 일치하는 컬럼만 필터링
|
|
|
|
|
const filteredColumns = table.columns.filter(
|
|
|
|
|
(col) =>
|
|
|
|
|
col.columnName.toLowerCase().includes(searchLower) ||
|
|
|
|
|
(col.columnLabel && col.columnLabel.toLowerCase().includes(searchLower)),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...table,
|
|
|
|
|
columns: filteredColumns,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((table) => table.columns.length > 0); // 컬럼이 있는 테이블만 표시
|
2025-09-02 16:18:38 +09:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-full flex-col">
|
2025-10-28 16:26:55 +09:00
|
|
|
{/* 테이블과 컬럼 평면 목록 */}
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-3">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
{filteredTables.map((table) => (
|
|
|
|
|
<div key={table.tableName} className="space-y-1">
|
|
|
|
|
{/* 테이블 헤더 */}
|
|
|
|
|
<div className="bg-muted/50 flex items-center justify-between rounded-md p-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Database className="text-primary h-3.5 w-3.5" />
|
|
|
|
|
<span className="text-xs font-semibold">{table.tableLabel || table.tableName}</span>
|
|
|
|
|
<Badge variant="secondary" className="h-4 px-1.5 text-[10px]">
|
|
|
|
|
{table.columns.length}개
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-02 16:18:38 +09:00
|
|
|
|
2025-10-28 16:26:55 +09:00
|
|
|
{/* 컬럼 목록 (항상 표시) */}
|
|
|
|
|
<div className="space-y-1 pl-2">
|
|
|
|
|
{table.columns.map((column) => (
|
|
|
|
|
<div
|
|
|
|
|
key={column.columnName}
|
|
|
|
|
className="hover:bg-accent/50 flex cursor-grab items-center justify-between rounded-md p-2 transition-colors"
|
2025-09-02 16:18:38 +09:00
|
|
|
draggable
|
2025-10-28 16:26:55 +09:00
|
|
|
onDragStart={(e) => onDragStart(e, table, column)}
|
2025-09-02 16:18:38 +09:00
|
|
|
>
|
2025-10-28 16:26:55 +09:00
|
|
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
|
|
|
{getWidgetIcon(column.widgetType)}
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
<div className="truncate text-xs font-medium">{column.columnLabel || column.columnName}</div>
|
|
|
|
|
<div className="text-muted-foreground truncate text-[10px]">{column.dataType}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-02 16:18:38 +09:00
|
|
|
|
2025-10-28 16:26:55 +09:00
|
|
|
<div className="flex flex-shrink-0 items-center gap-1">
|
|
|
|
|
<Badge variant="secondary" className="h-4 px-1.5 text-[10px]">
|
|
|
|
|
{column.widgetType}
|
|
|
|
|
</Badge>
|
|
|
|
|
{column.required && (
|
|
|
|
|
<Badge variant="destructive" className="h-4 px-1 text-[10px]">
|
|
|
|
|
필수
|
|
|
|
|
</Badge>
|
2025-09-02 16:18:38 +09:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-10-28 16:26:55 +09:00
|
|
|
))}
|
2025-09-02 16:18:38 +09:00
|
|
|
</div>
|
2025-10-28 16:26:55 +09:00
|
|
|
</div>
|
|
|
|
|
))}
|
2025-09-02 16:18:38 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default TablesPanel;
|