From 111023191e6494fcddee883a64b9807f59c7dcdf Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Thu, 12 Mar 2026 04:12:04 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260311185722-je7c round-3 --- .../V2TimelineSchedulerConfigPanel.tsx | 534 +++++++++++++++++- 1 file changed, 524 insertions(+), 10 deletions(-) diff --git a/frontend/components/v2/config-panels/V2TimelineSchedulerConfigPanel.tsx b/frontend/components/v2/config-panels/V2TimelineSchedulerConfigPanel.tsx index b88677e2..9d7380e5 100644 --- a/frontend/components/v2/config-panels/V2TimelineSchedulerConfigPanel.tsx +++ b/frontend/components/v2/config-panels/V2TimelineSchedulerConfigPanel.tsx @@ -2,7 +2,7 @@ /** * V2 타임라인 스케줄러 설정 패널 - * 토스식 단계별 UX: 스케줄 생성 설정 -> 리소스 설정 -> 표시 설정(접힘) + * 토스식 단계별 UX: 스케줄 데이터 설정 -> 소스 데이터 설정 -> 리소스 설정 -> 표시 설정(접힘) -> 고급 설정(접힘) */ import React, { useState, useEffect } from "react"; @@ -14,10 +14,10 @@ import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { Settings, ChevronDown, Check, ChevronsUpDown, Database, Users } from "lucide-react"; +import { Settings, ChevronDown, Check, ChevronsUpDown, Database, Users, Layers } from "lucide-react"; import { cn } from "@/lib/utils"; import { tableTypeApi } from "@/lib/api/screen"; -import type { TimelineSchedulerConfig, ScheduleType, SourceDataConfig, ResourceFieldMapping } from "@/lib/registry/components/v2-timeline-scheduler/types"; +import type { TimelineSchedulerConfig, ScheduleType, SourceDataConfig, ResourceFieldMapping, FieldMapping, ZoomLevel } from "@/lib/registry/components/v2-timeline-scheduler/types"; import { zoomLevelOptions, scheduleTypeOptions } from "@/lib/registry/components/v2-timeline-scheduler/config"; interface V2TimelineSchedulerConfigPanelProps { @@ -42,10 +42,13 @@ export const V2TimelineSchedulerConfigPanel: React.FC([]); const [sourceColumns, setSourceColumns] = useState([]); const [resourceColumns, setResourceColumns] = useState([]); + const [scheduleColumns, setScheduleColumns] = useState([]); const [loading, setLoading] = useState(false); const [sourceTableOpen, setSourceTableOpen] = useState(false); const [resourceTableOpen, setResourceTableOpen] = useState(false); + const [customTableOpen, setCustomTableOpen] = useState(false); const [displayOpen, setDisplayOpen] = useState(false); + const [advancedOpen, setAdvancedOpen] = useState(false); useEffect(() => { const loadTables = async () => { @@ -117,6 +120,30 @@ export const V2TimelineSchedulerConfigPanel: React.FC { + const loadScheduleColumns = async () => { + const tableName = config.useCustomTable && config.customTableName + ? config.customTableName + : "schedule_mng"; + try { + const columns = await tableTypeApi.getColumns(tableName); + if (Array.isArray(columns)) { + setScheduleColumns( + columns.map((col: any) => ({ + columnName: col.column_name || col.columnName, + displayName: col.display_name || col.displayName || col.column_name || col.columnName, + })) + ); + } + } catch (err) { + console.error("스케줄 테이블 컬럼 로드 오류:", err); + setScheduleColumns([]); + } + }; + loadScheduleColumns(); + }, [config.useCustomTable, config.customTableName]); + const updateConfig = (updates: Partial) => { onChange({ ...config, ...updates }); }; @@ -130,6 +157,20 @@ export const V2TimelineSchedulerConfigPanel: React.FC { + updateConfig({ + fieldMapping: { + ...config.fieldMapping, + id: config.fieldMapping?.id || "id", + resourceId: config.fieldMapping?.resourceId || "resource_id", + title: config.fieldMapping?.title || "title", + startDate: config.fieldMapping?.startDate || "start_date", + endDate: config.fieldMapping?.endDate || "end_date", + [field]: value, + }, + }); + }; + const updateResourceFieldMapping = (field: string, value: string) => { updateConfig({ resourceFieldMapping: { @@ -143,13 +184,13 @@ export const V2TimelineSchedulerConfigPanel: React.FC - {/* ─── 1단계: 스케줄 생성 설정 ─── */} + {/* ─── 1단계: 스케줄 데이터 테이블 설정 ─── */}
- -

스케줄 생성 설정

+ +

스케줄 데이터 테이블

-

스케줄 자동 생성 시 참조할 원본 데이터를 설정해요 (저장: schedule_mng)

+

스케줄 데이터를 저장/조회할 테이블을 설정해요

@@ -173,6 +214,259 @@ export const V2TimelineSchedulerConfigPanel: React.FC
+ {/* 커스텀 테이블 사용 여부 */} +
+
+

커스텀 테이블 사용

+

OFF: schedule_mng 사용

+
+ updateConfig({ useCustomTable: v })} + /> +
+ + {/* 커스텀 테이블 선택 (Combobox) */} + {config.useCustomTable && ( +
+ 커스텀 테이블명 + + + + + + { + return value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0; + }} + > + + + 테이블을 찾을 수 없습니다. + + {tables.map((table) => ( + { + updateConfig({ customTableName: table.tableName, selectedTable: table.tableName }); + setCustomTableOpen(false); + }} + className="text-xs" + > + +
+ {table.displayName} + {table.tableName} +
+
+ ))} +
+
+
+
+
+
+ )} + + {/* 디자인 모드 표시용 테이블명 (selectedTable) */} + {!config.useCustomTable && ( +
+ 디자인 모드 표시 테이블 + updateConfig({ selectedTable: e.target.value })} + placeholder="schedule_mng" + className="h-7 w-[140px] text-xs" + /> +
+ )} + + {/* 스케줄 필드 매핑 */} +
+

스케줄 필드 매핑

+ +
+ ID 필드 * + +
+ +
+ 리소스 ID 필드 * + +
+ +
+ 제목 필드 * + +
+ +
+ 시작일 필드 * + +
+ +
+ 종료일 필드 * + +
+ +
+ 상태 필드 + +
+ +
+ 진행률 필드 + +
+ +
+ 색상 필드 + +
+
+ + + {/* ─── 2단계: 소스 데이터 설정 ─── */} +
+
+ +

소스 데이터 설정

+
+

스케줄 자동 생성 시 참조할 원본 데이터를 설정해요

+
+ +
{/* 소스 테이블 Combobox */}
소스 테이블 (수주/작업요청 등) @@ -318,7 +612,7 @@ export const V2TimelineSchedulerConfigPanel: React.FC - {/* ─── 2단계: 리소스 설정 ─── */} + {/* ─── 3단계: 리소스 설정 ─── */}
@@ -427,11 +721,30 @@ export const V2TimelineSchedulerConfigPanel: React.FC
+ +
+ 그룹 필드 + +
)}
- {/* ─── 3단계: 표시 설정 (Collapsible) ─── */} + {/* ─── 4단계: 표시 설정 (Collapsible) ─── */}
+ {/* 높이 */}
높이 (px) @@ -483,6 +810,21 @@ export const V2TimelineSchedulerConfigPanel: React.FC
+ {/* 최대 높이 */} +
+ 최대 높이 (px) + { + const val = e.target.value ? parseInt(e.target.value) : undefined; + updateConfig({ maxHeight: val }); + }} + placeholder="제한 없음" + className="h-7 w-[100px] text-xs" + /> +
+ {/* 행 높이 */}
행 높이 (px) @@ -494,6 +836,62 @@ export const V2TimelineSchedulerConfigPanel: React.FC
+ {/* 헤더 높이 */} +
+ 헤더 높이 (px) + updateConfig({ headerHeight: parseInt(e.target.value) || 60 })} + className="h-7 w-[100px] text-xs" + /> +
+ + {/* 리소스 컬럼 너비 */} +
+ 리소스 컬럼 너비 (px) + updateConfig({ resourceColumnWidth: parseInt(e.target.value) || 150 })} + className="h-7 w-[100px] text-xs" + /> +
+ + {/* 셀 너비 (줌 레벨별) */} +
+ 셀 너비 (줌 레벨별, px) +
+
+ + updateConfig({ cellWidth: { ...config.cellWidth, day: parseInt(e.target.value) || 60 } })} + className="h-7 text-xs" + /> +
+
+ + updateConfig({ cellWidth: { ...config.cellWidth, week: parseInt(e.target.value) || 120 } })} + className="h-7 text-xs" + /> +
+
+ + updateConfig({ cellWidth: { ...config.cellWidth, month: parseInt(e.target.value) || 40 } })} + className="h-7 text-xs" + /> +
+
+
+ {/* Switch 토글들 */}
@@ -550,6 +948,17 @@ export const V2TimelineSchedulerConfigPanel: React.FC
+
+
+

충돌 표시

+

스케줄 겹침 시 경고를 표시해요

+
+ updateConfig({ showConflicts: v })} + /> +
+

툴바 표시

@@ -560,6 +969,111 @@ export const V2TimelineSchedulerConfigPanel: React.FC updateConfig({ showToolbar: v })} />
+ +
+
+

줌 컨트롤 표시

+

줌 레벨 변경 버튼을 보여줘요

+
+ updateConfig({ showZoomControls: v })} + /> +
+ +
+
+

네비게이션 표시

+

이전/다음/오늘 버튼을 보여줘요

+
+ updateConfig({ showNavigation: v })} + /> +
+ +
+
+

추가 버튼 표시

+

스케줄 추가 버튼을 보여줘요

+
+ updateConfig({ showAddButton: v })} + /> +
+
+ + + + {/* ─── 5단계: 고급 설정 - 상태별 색상 (Collapsible) ─── */} + + + + + +
+ {[ + { key: "planned", label: "계획됨", defaultColor: "#3b82f6" }, + { key: "in_progress", label: "진행중", defaultColor: "#f59e0b" }, + { key: "completed", label: "완료", defaultColor: "#10b981" }, + { key: "delayed", label: "지연", defaultColor: "#ef4444" }, + { key: "cancelled", label: "취소", defaultColor: "#6b7280" }, + ].map((status) => ( +
+
+
+ {status.label} +
+
+ + updateConfig({ + statusColors: { + ...config.statusColors, + [status.key]: e.target.value, + }, + }) + } + className="h-7 w-7 cursor-pointer rounded border" + /> + + updateConfig({ + statusColors: { + ...config.statusColors, + [status.key]: e.target.value, + }, + }) + } + className="h-7 w-[80px] text-xs" + /> +
+
+ ))}