From 44ed594dd7d7466c5632b473b96eea3fb44a304b Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Sun, 21 Sep 2025 11:10:18 +0900 Subject: [PATCH] =?UTF-8?q?insert=20=EB=A7=A4=ED=95=91=20=EC=8B=9C=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dataflow/condition/WebTypeInput.tsx | 288 +++++++++++------- .../connection/ColumnTableSection.tsx | 11 +- 2 files changed, 178 insertions(+), 121 deletions(-) diff --git a/frontend/components/dataflow/condition/WebTypeInput.tsx b/frontend/components/dataflow/condition/WebTypeInput.tsx index 6291710b..a8a9ede7 100644 --- a/frontend/components/dataflow/condition/WebTypeInput.tsx +++ b/frontend/components/dataflow/condition/WebTypeInput.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -22,116 +22,146 @@ interface WebTypeInputProps { onChange: (value: string) => void; className?: string; placeholder?: string; + tableName?: string; // 테이블명을 별도로 전달받음 } -export const WebTypeInput: React.FC = ({ column, value, onChange, className = "", placeholder }) => { +export const WebTypeInput: React.FC = ({ + column, + value, + onChange, + className = "", + placeholder, + tableName, +}) => { const webType = column.webType || "text"; const [entityOptions, setEntityOptions] = useState([]); const [codeOptions, setCodeOptions] = useState([]); const [loading, setLoading] = useState(false); - // detailSettings 안전하게 파싱 - let detailSettings: any = {}; - let fallbackCodeCategory = ""; + // detailSettings 안전하게 파싱 (메모이제이션) + const { detailSettings, fallbackCodeCategory } = useMemo(() => { + let parsedSettings: Record = {}; + let fallbackCategory = ""; - if (column.detailSettings && typeof column.detailSettings === "string") { - // JSON 형태인지 확인 ('{' 또는 '[' 로 시작하는지) - const trimmed = column.detailSettings.trim(); - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - detailSettings = JSON.parse(column.detailSettings); - } catch (error) { - console.warn(`detailSettings JSON 파싱 실패 (${column.columnName}):`, column.detailSettings, error); - detailSettings = {}; - } - } else { - // JSON이 아닌 일반 문자열인 경우, code 타입이면 codeCategory로 사용 - if (webType === "code") { - // "공통코드: 상태" 형태에서 실제 코드 추출 시도 - if (column.detailSettings.includes(":")) { - const parts = column.detailSettings.split(":"); - if (parts.length >= 2) { - fallbackCodeCategory = parts[1].trim(); - } else { - fallbackCodeCategory = column.detailSettings; - } - } else { - fallbackCodeCategory = column.detailSettings; + if (column.detailSettings && typeof column.detailSettings === "string") { + // JSON 형태인지 확인 ('{' 또는 '[' 로 시작하는지) + const trimmed = column.detailSettings.trim(); + if (trimmed.startsWith("{") || trimmed.startsWith("[")) { + try { + parsedSettings = JSON.parse(column.detailSettings); + } catch { + parsedSettings = {}; } - console.log(`📝 detailSettings에서 codeCategory 추출: "${column.detailSettings}" -> "${fallbackCodeCategory}"`); + } else { + // JSON이 아닌 일반 문자열인 경우, code 타입이면 codeCategory로 사용 + if (webType === "code") { + // "공통코드: 상태" 형태에서 실제 코드 추출 시도 + if (column.detailSettings.includes(":")) { + const parts = column.detailSettings.split(":"); + if (parts.length >= 2) { + fallbackCategory = parts[1].trim(); + } else { + fallbackCategory = column.detailSettings; + } + } else { + fallbackCategory = column.detailSettings; + } + } + parsedSettings = {}; } - detailSettings = {}; + } else if (column.detailSettings && typeof column.detailSettings === "object") { + parsedSettings = column.detailSettings; } - } else if (column.detailSettings && typeof column.detailSettings === "object") { - detailSettings = column.detailSettings; - } - // Entity 타입일 때 참조 데이터 로드 - useEffect(() => { - console.log("🔍 WebTypeInput useEffect:", { - webType, - columnName: column.columnName, - tableName: column.tableName, - referenceTable: column.referenceTable, - displayColumn: column.displayColumn, - codeCategory: column.codeCategory, - detailSettings, - fallbackCodeCategory, - }); + return { detailSettings: parsedSettings, fallbackCodeCategory: fallbackCategory }; + }, [column.detailSettings, webType]); - // webType이 entity이거나, referenceTable이 있으면 entity로 처리 - if ((webType === "entity" || column.referenceTable) && column.tableName && column.columnName) { - console.log("🚀 Entity 데이터 로드 시작:", column.tableName, column.columnName); - loadEntityData(); - } else if (webType === "code" && (column.codeCategory || detailSettings.codeCategory || fallbackCodeCategory)) { - const codeCategory = column.codeCategory || detailSettings.codeCategory || fallbackCodeCategory; - console.log("🚀 Code 데이터 로드 시작:", codeCategory); - loadCodeData(); - } else { - console.log("❌ 조건 불충족 - API 호출 안함", { - webType, - hasReferenceTable: !!column.referenceTable, - hasTableName: !!column.tableName, - hasColumnName: !!column.columnName, - hasCodeCategory: !!(column.codeCategory || detailSettings.codeCategory || fallbackCodeCategory), - }); - } - }, [webType, column.tableName, column.columnName, column.codeCategory, column.referenceTable, fallbackCodeCategory]); - - const loadEntityData = async () => { + const loadEntityData = useCallback(async () => { try { setLoading(true); - console.log("📡 Entity API 호출:", column.tableName, column.columnName); - const data = await EntityReferenceAPI.getEntityReferenceData(column.tableName, column.columnName, { limit: 100 }); - console.log("✅ Entity API 응답:", data); + const data = await EntityReferenceAPI.getEntityReferenceData(tableName!, column.columnName, { limit: 100 }); setEntityOptions(data.options); - } catch (error) { - console.error("❌ 엔티티 참조 데이터 로드 실패:", error); + } catch { setEntityOptions([]); } finally { setLoading(false); } - }; + }, [tableName, column.columnName]); - const loadCodeData = async () => { + const loadCodeData = useCallback(async () => { try { setLoading(true); - const codeCategory = column.codeCategory || detailSettings.codeCategory || fallbackCodeCategory; + const codeCategory = column.codeCategory || (detailSettings.codeCategory as string) || fallbackCodeCategory; if (codeCategory) { - console.log("📡 Code API 호출:", codeCategory); const data = await EntityReferenceAPI.getCodeReferenceData(codeCategory, { limit: 100 }); - console.log("✅ Code API 응답:", data); setCodeOptions(data.options); - } else { - console.warn("⚠️ codeCategory가 없어서 API 호출 안함"); } - } catch (error) { - console.error("공통 코드 데이터 로드 실패:", error); + } catch { setCodeOptions([]); } finally { setLoading(false); } - }; + }, [column.codeCategory, detailSettings.codeCategory, fallbackCodeCategory]); + + // Entity 타입일 때 참조 데이터 로드 + useEffect(() => { + // webType이 entity이거나, referenceTable이 있으면 entity로 처리 + if ((webType === "entity" || column.referenceTable) && tableName && column.columnName) { + loadEntityData(); + } else if (webType === "code" && (column.codeCategory || detailSettings.codeCategory || fallbackCodeCategory)) { + loadCodeData(); + } + }, [ + webType, + tableName, + column.columnName, + column.codeCategory, + column.referenceTable, + fallbackCodeCategory, + detailSettings.codeCategory, + loadEntityData, + loadCodeData, + ]); + + // 날짜/시간 타입일 때 기본값으로 현재 날짜/시간 설정 + useEffect(() => { + const dateTimeTypes = ["date", "datetime", "timestamp"]; + + // 컬럼명이나 데이터 타입으로 날짜 필드 판단 + const isDateColumn = + dateTimeTypes.includes(webType) || + column.columnName?.toLowerCase().includes("date") || + column.columnName?.toLowerCase().includes("time") || + column.columnName === "regdate" || + column.columnName === "created_at" || + column.columnName === "updated_at"; + + if (isDateColumn && (!value || value === "")) { + const now = new Date(); + let formattedValue = ""; + + if (webType === "date") { + // 데이터베이스 타입이나 컬럼명으로 시간 포함 여부 판단 + const isTimestampType = + column.dataType?.toLowerCase().includes("timestamp") || + column.columnName?.toLowerCase().includes("time") || + column.columnName === "regdate" || + column.columnName === "created_at" || + column.columnName === "updated_at"; + + if (isTimestampType) { + formattedValue = format(now, "yyyy-MM-dd HH:mm:ss"); + } else { + formattedValue = format(now, "yyyy-MM-dd"); + } + } else { + // 컬럼명 기반 판단 시에도 시간 포함 + formattedValue = format(now, "yyyy-MM-dd HH:mm:ss"); + } + + onChange(formattedValue); + } + }, [webType, value, onChange, column.columnName, column.dataType]); // 공통 props const commonProps = { @@ -160,35 +190,63 @@ export const WebTypeInput: React.FC = ({ column, value, onCha type="number" placeholder={placeholder || "숫자 입력"} onChange={(e) => onChange(e.target.value)} - min={detailSettings.min} - max={detailSettings.max} - step={detailSettings.step || "any"} + min={detailSettings.min as number} + max={detailSettings.max as number} + step={(detailSettings.step as string) || "any"} /> ); case "date": - const dateValue = value ? new Date(value) : undefined; - return ( - - - - - - onChange(date ? format(date, "yyyy-MM-dd") : "")} - initialFocus - /> - - - ); + // 데이터베이스 타입이나 컬럼명으로 시간 포함 여부 판단 + const isTimestampType = + column.dataType?.toLowerCase().includes("timestamp") || + column.columnName?.toLowerCase().includes("time") || + column.columnName === "regdate" || + column.columnName === "created_at" || + column.columnName === "updated_at"; + + if (isTimestampType) { + // timestamp 타입이면 datetime-local input 사용 (시간까지 입력 가능) + const datetimeValue = value ? value.replace(" ", "T").substring(0, 16) : ""; + return ( + { + const inputValue = e.target.value; + // datetime-local 형식 (YYYY-MM-DDTHH:mm)을 DB 형식 (YYYY-MM-DD HH:mm:ss)으로 변환 + const formattedValue = inputValue ? `${inputValue.replace("T", " ")}:00` : ""; + onChange(formattedValue); + }} + placeholder={placeholder || "날짜와 시간 선택"} + /> + ); + } else { + // 순수 date 타입이면 달력 팝업 사용 + const dateValue = value ? new Date(value) : undefined; + return ( + + + + + + onChange(date ? format(date, "yyyy-MM-dd") : "")} + initialFocus + /> + + + ); + } case "textarea": return ( @@ -196,19 +254,19 @@ export const WebTypeInput: React.FC = ({ column, value, onCha {...commonProps} placeholder={placeholder || "여러 줄 텍스트 입력"} onChange={(e) => onChange(e.target.value)} - rows={detailSettings.rows || 3} + rows={(detailSettings.rows as number) || 3} /> ); case "select": - const selectOptions = detailSettings.options || []; + const selectOptions = (detailSettings.options as { value: string; label?: string }[]) || []; return ( @@ -273,7 +331,7 @@ export const WebTypeInput: React.FC = ({ column, value, onCha case "entity": // 엔티티 참조 - 실제 참조 테이블에서 데이터 가져옴 - const referenceTable = column.referenceTable || (detailSettings as any).referenceTable; + const referenceTable = column.referenceTable || (detailSettings.referenceTable as string); return ( onDefaultValueChange(column.columnName, e.target.value)} + onChange={(value) => onDefaultValueChange(column.columnName, value)} className="h-6 border-gray-200 text-xs focus:border-green-400 focus:ring-0" - onClick={(e) => e.stopPropagation()} - disabled={isSelected || !!oppositeSelectedColumn} + placeholder="기본값 입력..." /> )}