diff --git a/backend-spring/src/main/java/com/erp/service/NodeFlowService.java b/backend-spring/src/main/java/com/erp/service/NodeFlowService.java index 573c61a8..8832e5cb 100644 --- a/backend-spring/src/main/java/com/erp/service/NodeFlowService.java +++ b/backend-spring/src/main/java/com/erp/service/NodeFlowService.java @@ -253,9 +253,9 @@ public class NodeFlowService extends BaseService { : List.of(); String companyCode = (String) contextData.getOrDefault("company_code", - contextData.getOrDefault("context.companyCode", "*")); + contextData.getOrDefault("context.company_code", "*")); String userId = (String) contextData.getOrDefault("user_id", - contextData.getOrDefault("context.userId", "system")); + contextData.getOrDefault("context.user_id", "system")); // ── 레벨별 순차 실행 (레벨 내부는 독립 실행 — 실패 시 전체 롤백) List failedNodes = new ArrayList<>(); diff --git a/frontend/app/(main)/admin/menu/page.tsx b/frontend/app/(main)/admin/menu/page.tsx index de982319..c09f4459 100644 --- a/frontend/app/(main)/admin/menu/page.tsx +++ b/frontend/app/(main)/admin/menu/page.tsx @@ -32,7 +32,7 @@ import { ScrollToTop } from "@/components/common/ScrollToTop"; type MenuType = "admin" | "user"; export default function MenuPage() { - const { adminMenus, userMenus, refreshMenus } = useMenu(); + const { refreshMenus } = useMenu(); const { user } = useAuth(); // 현재 사용자 정보 가져오기 const [selectedMenuType, setSelectedMenuType] = useState("admin"); const [loading, setLoading] = useState(false); @@ -50,7 +50,7 @@ export default function MenuPage() { // 다국어 텍스트 훅 사용 // getMenuText는 더 이상 사용하지 않음 - getUITextSync만 사용 - const { userLang } = useMultiLang({ companyCode: "*" }); + const { userLang } = useMultiLang({ company_code: "*" }); // SUPER_ADMIN 여부 확인 const isSuperAdmin = user?.user_type === "SUPER_ADMIN"; @@ -69,7 +69,7 @@ export default function MenuPage() { const [formData, setFormData] = useState({ menuId: "", parentId: "", - menuType: "", + menu_type: "", level: 0, parentCompanyCode: "", }); @@ -556,7 +556,7 @@ export default function MenuPage() { // uiTexts에 없으면 getMenuTextSync로 기본 한글 텍스트 가져오기 if (!text) { - text = getMenuTextSync(key, userLang) || fallback || key; + text = getMenuTextSync(key, userLang ?? undefined) || fallback || key; } // 파라미터 치환 @@ -605,7 +605,7 @@ export default function MenuPage() { setFormData({ menuId: "", parentId: "0", // 최상위 메뉴는 parentId가 0 - menuType: getMenuTypeValue(), + menu_type: getMenuTypeValue(), level: 1, // 최상위 메뉴는 level 1 parentCompanyCode: "", // 최상위 메뉴는 상위 회사 정보 없음 }); @@ -620,7 +620,7 @@ export default function MenuPage() { setFormData({ menuId: "", parentId, - menuType, + menu_type: menuType, level: level + 1, parentCompanyCode: parentMenu?.company_code || "", }); @@ -640,7 +640,7 @@ export default function MenuPage() { setFormData({ menuId: menuId, parentId: menuToEdit.parent_obj_id ?? menuToEdit.PARENT_OBJ_ID ?? "", - menuType: selectedMenuType, // 현재 선택된 메뉴 타입 + menu_type: selectedMenuType, // 현재 선택된 메뉴 타입 level: 0, // 기본값 parentCompanyCode: menuToEdit.company_code ?? menuToEdit.COMPANY_CODE ?? "", }); @@ -1114,7 +1114,7 @@ export default function MenuPage() { onSuccess={handleFormSuccess} menuId={formData.menuId} parentId={formData.parentId} - menuType={formData.menuType} + menuType={formData.menu_type} level={formData.level} parentCompanyCode={formData.parentCompanyCode} uiTexts={uiTexts} diff --git a/frontend/components/admin/AddColumnModal.tsx b/frontend/components/admin/AddColumnModal.tsx index 92ab76ea..bf603d86 100644 --- a/frontend/components/admin/AddColumnModal.tsx +++ b/frontend/components/admin/AddColumnModal.tsx @@ -33,11 +33,11 @@ import { } from "../../types/ddl"; import { INPUT_TYPE_OPTIONS } from "../../types/input-types"; -export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddColumnModalProps) { +export function AddColumnModal({ isOpen, onClose, table_name, onSuccess }: AddColumnModalProps) { const [column, setColumn] = useState({ name: "", label: "", - inputType: "text", + input_type: "text", nullable: true, order: 0, }); @@ -51,7 +51,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol setColumn({ name: "", label: "", - inputType: "text", + input_type: "text", nullable: true, order: 0, }); @@ -122,13 +122,12 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol } // 입력타입 검증 - if (!columnData.inputType) { + if (!columnData.input_type) { errors.push("입력타입을 선택해주세요."); } - // 길이 검증 (길이를 지원하는 타입인 경우) - const inputTypeOption = INPUT_TYPE_OPTIONS.find((opt) => opt.value === columnData.inputType); - if (inputTypeOption?.supportsLength && columnData.length !== undefined) { + // 길이 검증 + if (columnData.length !== undefined) { if ( columnData.length < VALIDATION_RULES.columnLength.min || columnData.length > VALIDATION_RULES.columnLength.max @@ -145,19 +144,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol * 입력타입 변경 처리 */ const handleInputTypeChange = (inputType: string) => { - const inputTypeOption = INPUT_TYPE_OPTIONS.find((opt) => opt.value === inputType); - const updates: Partial = { inputType: inputType as any }; - - // 길이를 지원하는 타입이고 현재 길이가 없으면 기본값 설정 - if (inputTypeOption?.supportsLength && !column.length && inputTypeOption.defaultLength) { - updates.length = inputTypeOption.defaultLength; - } - - // 길이를 지원하지 않는 타입이면 길이 제거 - if (!inputTypeOption?.supportsLength) { - updates.length = undefined; - } - + const updates: Partial = { input_type: inputType as any }; updateColumn(updates); }; @@ -172,7 +159,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol setLoading(true); try { - const result = await ddlApi.addColumn(tableName, { column }); + const result = await ddlApi.addColumn(table_name, { column }); if (result.success) { toast.success(result.message); @@ -192,9 +179,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol /** * 폼 유효성 확인 */ - const isFormValid = validationErrors.length === 0 && column.name && column.inputType; - - const inputTypeOption = INPUT_TYPE_OPTIONS.find((opt) => opt.value === column.inputType); + const isFormValid = validationErrors.length === 0 && column.name && column.input_type; return ( @@ -202,7 +187,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol - 컬럼 추가 - {tableName} + 컬럼 추가 - {table_name} @@ -257,7 +242,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol - @@ -287,14 +272,12 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol length: e.target.value ? parseInt(e.target.value) : undefined, }) } - placeholder={inputTypeOption?.defaultLength?.toString() || ""} - disabled={loading || !inputTypeOption?.supportsLength} + placeholder="길이 (선택사항)" + disabled={loading} min={1} max={65535} /> -

- {inputTypeOption?.supportsLength ? "1-65535 범위에서 설정 가능" : "이 타입은 길이 설정이 불가능합니다"} -

+

1-65535 범위에서 설정 가능 (선택사항)

@@ -304,8 +287,8 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol updateColumn({ defaultValue: e.target.value })} + value={column.default_value || ""} + onChange={(e) => updateColumn({ default_value: e.target.value })} placeholder="기본값 (선택사항)" disabled={loading} /> diff --git a/frontend/components/admin/AdvancedBatchModal.tsx b/frontend/components/admin/AdvancedBatchModal.tsx index c00e2fc8..9f8ff4ab 100644 --- a/frontend/components/admin/AdvancedBatchModal.tsx +++ b/frontend/components/admin/AdvancedBatchModal.tsx @@ -21,7 +21,9 @@ import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; import { showErrorToast } from "@/lib/utils/toastUtils"; -import { BatchAPI, BatchJob, BatchConfig } from "@/lib/api/batch"; +import { BatchAPI, BatchJob } from "@/lib/api/batch"; + +type BatchJobFormState = Partial & { config_json?: Record }; import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection"; // BatchJobModal에서 사용하던 config_json 구조 확장 @@ -55,7 +57,7 @@ export default function AdvancedBatchModal({ initialType = "rest_to_db", }: AdvancedBatchModalProps) { // 기본 BatchJob 정보 관리 - const [formData, setFormData] = useState>({ + const [formData, setFormData] = useState({ job_name: "", description: "", job_type: initialType === "rest_to_db" ? "rest_to_db" : "db_to_rest", @@ -85,10 +87,10 @@ export default function AdvancedBatchModal({ // 수정 모드 setFormData({ ...job, - config_json: job.config_json || {}, + config_json: (job as BatchJobFormState).config_json || {}, }); // 기존 config_json 내용을 상태로 복원 - const savedConfig = job.config_json as RestApiConfigJson; + const savedConfig = ((job as BatchJobFormState).config_json || {}) as RestApiConfigJson; setConfigData({ ...savedConfig, http_method: savedConfig.http_method || "GET", @@ -104,7 +106,7 @@ export default function AdvancedBatchModal({ setFormData({ job_name: "", description: "", - job_type: initialType === "rest_to_db" ? "rest_to_db" : "db_to_rest", // props로 받은 타입 우선 + job_type: initialType === "rest_to_db" ? "rest_to_db" : "db_to_rest", schedule_cron: "", is_active: "Y", config_json: {}, @@ -140,12 +142,12 @@ export default function AdvancedBatchModal({ }; const loadSchedulePresets = async () => { - try { - const presets = await BatchAPI.getSchedulePresets(); - setSchedulePresets(presets); - } catch (error) { - console.error("스케줄 프리셋 조회 오류:", error); - } + setSchedulePresets([ + { value: "0 0 * * *", label: "매일 자정" }, + { value: "0 * * * *", label: "매 시간" }, + { value: "*/30 * * * *", label: "30분마다" }, + { value: "0 9 * * 1-5", label: "평일 오전 9시" }, + ]); }; // 폼 제출 핸들러 diff --git a/frontend/components/admin/BatchJobModal.tsx b/frontend/components/admin/BatchJobModal.tsx index b3fbb0e9..b35b7019 100644 --- a/frontend/components/admin/BatchJobModal.tsx +++ b/frontend/components/admin/BatchJobModal.tsx @@ -4,9 +4,9 @@ import React, { useState, useEffect } from "react"; import { Dialog, DialogContent, + DialogFooter, DialogHeader, - - + DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -23,6 +23,8 @@ import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; import { BatchAPI, BatchJob } from "@/lib/api/batch"; + +type BatchJobFormState = Partial & { config_json?: Record }; // import { CollectionAPI } from "@/lib/api/collection"; // 사용하지 않는 import 제거 interface BatchJobModalProps { @@ -38,7 +40,7 @@ export default function BatchJobModal({ onSave, job, }: BatchJobModalProps) { - const [formData, setFormData] = useState>({ + const [formData, setFormData] = useState({ job_name: "", description: "", job_type: "collection", @@ -63,7 +65,7 @@ export default function BatchJobModal({ if (job) { setFormData({ ...job, - config_json: job.config_json || {}, + config_json: (job as BatchJobFormState).config_json || {}, }); } else { setFormData({ @@ -84,19 +86,19 @@ export default function BatchJobModal({ const loadJobTypes = async () => { try { const types = await BatchAPI.getSupportedJobTypes(); - setJobTypes(types); + setJobTypes(types.map((t) => ({ value: t, label: t }))); } catch (error) { console.error("작업 타입 조회 오류:", error); } }; const loadSchedulePresets = async () => { - try { - const presets = await BatchAPI.getSchedulePresets(); - setSchedulePresets(presets); - } catch (error) { - console.error("스케줄 프리셋 조회 오류:", error); - } + setSchedulePresets([ + { value: "0 0 * * *", label: "매일 자정" }, + { value: "0 * * * *", label: "매 시간" }, + { value: "*/30 * * * *", label: "30분마다" }, + { value: "0 9 * * 1-5", label: "평일 오전 9시" }, + ]); }; const loadCollectionConfigs = async () => { @@ -123,10 +125,10 @@ export default function BatchJobModal({ setIsLoading(true); try { if (job?.id) { - await BatchAPI.updateBatchJob(job.id, formData); + await (BatchAPI as any).updateBatchJob(job.id, formData); toast.success("배치 작업이 수정되었습니다."); } else { - await BatchAPI.createBatchJob(formData as BatchJob); + await (BatchAPI as any).createBatchJob(formData); toast.success("배치 작업이 생성되었습니다."); } onSave(); @@ -149,7 +151,7 @@ export default function BatchJobModal({ }; const handleJobTypeChange = (jobType: string) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, job_type: jobType as any, config_json: {}, @@ -157,10 +159,10 @@ export default function BatchJobModal({ }; const handleCollectionConfigChange = (configId: string) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, config_json: { - ...prev.config_json, + ...(prev.config_json || {}), collectionConfigId: parseInt(configId), }, })); diff --git a/frontend/components/admin/RestApiConnectionModal.tsx b/frontend/components/admin/RestApiConnectionModal.tsx index eae5677d..29fb955d 100644 --- a/frontend/components/admin/RestApiConnectionModal.tsx +++ b/frontend/components/admin/RestApiConnectionModal.tsx @@ -229,7 +229,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }: endpoint_path: endpointPath || undefined, default_headers: defaultHeaders, default_method: defaultMethod, - default_body: defaultBody.trim() || null, // 빈 문자열이면 null로 전송하여 DB 업데이트 + default_body: defaultBody.trim() || undefined, // 빈 문자열이면 undefined로 전송 auth_type: authType, auth_config: authType === "none" ? undefined : authConfig, timeout, diff --git a/frontend/components/admin/ScreenAssignmentTab.tsx b/frontend/components/admin/ScreenAssignmentTab.tsx index 0ba0081f..2b21f5ae 100644 --- a/frontend/components/admin/ScreenAssignmentTab.tsx +++ b/frontend/components/admin/ScreenAssignmentTab.tsx @@ -161,7 +161,7 @@ export const ScreenAssignmentTab: React.FC = ({ menus ); // 단순화된 메뉴 옵션 생성 (모든 메뉴를 평면적으로 표시) - const getMenuOptions = (menuList: MenuItem[]): JSX.Element[] => { + const getMenuOptions = (menuList: MenuItem[]): React.ReactElement[] => { // console.log("메뉴 옵션 생성:", { // total: menuList.length, // sample: menuList.slice(0, 3).map((m) => ({ diff --git a/frontend/components/admin/UserFormModal.tsx b/frontend/components/admin/UserFormModal.tsx index 97726e62..78292847 100644 --- a/frontend/components/admin/UserFormModal.tsx +++ b/frontend/components/admin/UserFormModal.tsx @@ -365,9 +365,8 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF let response; if (isEditMode) { // 수정 모드: 비밀번호 필드 제외 (비밀번호 초기화 기능 별도 제공) - const updateData = { ...userDataToSend }; - delete updateData.user_password; - response = await userAPI.update(updateData); + const { user_password: _pw, ...updateData } = userDataToSend; + response = await userAPI.update(updateData as any); } else { // 등록 모드 response = await userAPI.create(userDataToSend); diff --git a/frontend/components/admin/UserToolbar.tsx b/frontend/components/admin/UserToolbar.tsx index b77b62ff..ee28e879 100644 --- a/frontend/components/admin/UserToolbar.tsx +++ b/frontend/components/admin/UserToolbar.tsx @@ -30,7 +30,7 @@ export function UserToolbar({ onSearchChange({ search_value: value, // 통합 검색 시 고급 검색 필드들 클리어 - searchType: undefined, + search_type: undefined, search_sabun: undefined, search_company_name: undefined, search_dept_name: undefined, @@ -47,7 +47,7 @@ export function UserToolbar({ onSearchChange({ [field]: value, // 고급 검색 시 통합 검색어 클리어 - searchValue: undefined, + search_value: undefined, }); }; diff --git a/frontend/components/admin/dashboard/DashboardTopMenu.tsx b/frontend/components/admin/dashboard/DashboardTopMenu.tsx index eeaa73fc..a1d401ab 100644 --- a/frontend/components/admin/dashboard/DashboardTopMenu.tsx +++ b/frontend/components/admin/dashboard/DashboardTopMenu.tsx @@ -129,7 +129,6 @@ export function DashboardTopMenu({ } // html-to-image 동적 import - // @ts-expect-error - 동적 import const { toPng } = await import("html-to-image"); // 3D/WebGL 렌더링 완료 대기 diff --git a/frontend/components/admin/dashboard/charts/PieChart.tsx b/frontend/components/admin/dashboard/charts/PieChart.tsx index c90b639d..521a4674 100644 --- a/frontend/components/admin/dashboard/charts/PieChart.tsx +++ b/frontend/components/admin/dashboard/charts/PieChart.tsx @@ -90,7 +90,7 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa if (config.showTooltip !== false) { paths .on("mouseover", function (event, d) { - d3.select(this).transition().duration(200).attr("d", arcHover); + d3.select(this).transition().duration(200).attr("d", arcHover as any); const tooltip = g .append("g") @@ -124,7 +124,7 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa .text(`${d.data.value} (${((d.data.value / d3.sum(dataset.data)) * 100).toFixed(1)}%)`); }) .on("mouseout", function (event, d) { - d3.select(this).transition().duration(200).attr("d", arc); + d3.select(this).transition().duration(200).attr("d", arc as any); g.selectAll(".tooltip").remove(); }); } diff --git a/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx b/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx index 7768fd99..76c3ebfa 100644 --- a/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx @@ -147,7 +147,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps } // 외부 커넥션 ID 저장 (백엔드에서 인증 정보 조회용) - updates.externalConnectionId = connection.id; + updates.externalConnectionId = connection.id?.toString(); console.log("최종 업데이트:", updates); @@ -310,9 +310,9 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps // CSV 형식 파싱 (기상청 API) if (textData.includes("#START7777") || textData.includes(",")) { - const lines = textData.split("\n").filter((line) => line.trim() && !line.startsWith("#")); - const parsedRows = lines.map((line) => { - const values = line.split(",").map((v) => v.trim()); + const lines = textData.split("\n").filter((line: string) => line.trim() && !line.startsWith("#")); + const parsedRows = lines.map((line: string) => { + const values = line.split(",").map((v: string) => v.trim()); return { reg_up: values[0] || "", reg_up_ko: values[1] || "", diff --git a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx index d7164c89..3a540012 100644 --- a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx @@ -939,7 +939,7 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M setConfig({ ...config, relationshipName: e.target.value })} + onChange={(e) => setConfig({ ...config, relationship_name: e.target.value })} placeholder="employee_id_department_id_연결" className="text-sm" /> diff --git a/frontend/components/dataflow/CustomEdge.tsx b/frontend/components/dataflow/CustomEdge.tsx index 55302c5d..fa7c1678 100644 --- a/frontend/components/dataflow/CustomEdge.tsx +++ b/frontend/components/dataflow/CustomEdge.tsx @@ -7,6 +7,7 @@ interface CustomEdgeData { relationshipType: string; connectionType: string; label?: string; + [key: string]: unknown; } export const CustomEdge: React.FC>> = ({ diff --git a/frontend/components/dataflow/connection/ActionFieldMappings.tsx b/frontend/components/dataflow/connection/ActionFieldMappings.tsx index 0dfa2632..53b4e028 100644 --- a/frontend/components/dataflow/connection/ActionFieldMappings.tsx +++ b/frontend/components/dataflow/connection/ActionFieldMappings.tsx @@ -124,7 +124,7 @@ export const ActionFieldMappings: React.FC = ({ toConnectionId={toConnectionId} onFromConnectionChange={handleFromConnectionChange} onToConnectionChange={handleToConnectionChange} - actionType={action.action_type} + actionType={action.action_type as "insert" | "update" | "delete"} allowSameConnection={true} /> @@ -137,7 +137,7 @@ export const ActionFieldMappings: React.FC = ({ selectedToTable={selectedToTable} onFromTableChange={handleFromTableChange} onToTableChange={handleToTableChange} - actionType={action.action_type} + actionType={action.action_type as "insert" | "update" | "delete"} allowSameTable={true} showSameTableWarning={true} /> diff --git a/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx b/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx index b3346bd5..d96688a4 100644 --- a/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx +++ b/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx @@ -125,7 +125,7 @@ export const InsertFieldMappingPanel: React.FC = ( defaultValue: col.defaultValue, maxLength: col.maxLength, description: col.description, - })); + })) as ColumnInfo[]; } return fromTableColumns || []; }, [multiFromColumns.length, fromTableColumns?.length]); @@ -141,7 +141,7 @@ export const InsertFieldMappingPanel: React.FC = ( defaultValue: col.defaultValue, maxLength: col.maxLength, description: col.description, - })); + })) as ColumnInfo[]; } return toTableColumns || []; }, [multiToColumns.length, toTableColumns?.length]); diff --git a/frontend/components/dataflow/connection/SimpleExternalCallSettings.tsx b/frontend/components/dataflow/connection/SimpleExternalCallSettings.tsx index bf7d5dae..7afd908d 100644 --- a/frontend/components/dataflow/connection/SimpleExternalCallSettings.tsx +++ b/frontend/components/dataflow/connection/SimpleExternalCallSettings.tsx @@ -244,7 +244,7 @@ export function SimpleExternalCallSettings({ settings, onSettingsChange }: Simpl className="text-sm" />
- 템플릿 변수: {{ recordCount }}, {{ timestamp }}, {{ tableName }} 등을 사용할 수 있습니다. + {"템플릿 변수: {{ recordCount }}, {{ timestamp }}, {{ tableName }} 등을 사용할 수 있습니다."}
diff --git a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx index 3fb71284..52375a03 100644 --- a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx +++ b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx @@ -21,6 +21,9 @@ const initialState: DataConnectionState = { estimatedRows: 0, actionType: "INSERT", }, + actionConditions: [], + actionGroups: [], + controlConditions: [], isLoading: false, validationErrors: [], }; diff --git a/frontend/components/dataflow/connection/redesigned/LeftPanel/LeftPanel.tsx b/frontend/components/dataflow/connection/redesigned/LeftPanel/LeftPanel.tsx index fb637aba..18f1a011 100644 --- a/frontend/components/dataflow/connection/redesigned/LeftPanel/LeftPanel.tsx +++ b/frontend/components/dataflow/connection/redesigned/LeftPanel/LeftPanel.tsx @@ -8,7 +8,7 @@ import { Separator } from "@/components/ui/separator"; import { LeftPanelProps } from "../types/redesigned"; // 컴포넌트 import -import ConnectionTypeSelector from "./ConnectionTypeSelector"; +import { ConnectionTypeSelector } from "./ConnectionTypeSelector"; import MappingDetailList from "./MappingDetailList"; import ActionSummaryPanel from "./ActionSummaryPanel"; diff --git a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx index be08029e..e207678b 100644 --- a/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx +++ b/frontend/components/dataflow/connection/redesigned/RightPanel/ActionConfig/ActionConditionBuilder.tsx @@ -546,7 +546,7 @@ const ActionConditionBuilder: React.FC = ({ (() => { // FROM/TO 테이블 컬럼 구분 let fieldColumn; - let actualFieldName; + let actualFieldName: string | undefined; if (condition.field?.startsWith("from.")) { actualFieldName = condition.field.replace("from.", ""); fieldColumn = fromColumns.find((col) => col.columnName === actualFieldName); @@ -592,7 +592,7 @@ const ActionConditionBuilder: React.FC = ({ {/* 값 타입 선택 */} handleTransformationChange(index, { staticValue: e.target.value })} placeholder="고정 값 입력" className="h-8 text-xs" diff --git a/frontend/components/dataflow/node-editor/panels/properties/HttpRequestActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/HttpRequestActionProperties.tsx index 90d6aed9..ba7704ec 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/HttpRequestActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/HttpRequestActionProperties.tsx @@ -118,9 +118,9 @@ export function HttpRequestActionProperties({ nodeId, data }: HttpRequestActionP }; // 바디 타입 변경 - const handleBodyTypeChange = (value: HttpRequestActionNodeData["bodyType"]) => { - setBodyType(value); - updateNodeData({ bodyType: value }); + const handleBodyTypeChange = (value: string) => { + setBodyType(value as HttpRequestActionNodeData["bodyType"]); + updateNodeData({ bodyType: value as HttpRequestActionNodeData["bodyType"] }); }; // 바디 업데이트 diff --git a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx index 0dfe739a..3e47f77a 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx @@ -86,7 +86,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP // 🔥 REST API 관련 상태 const [apiEndpoint, setApiEndpoint] = useState(data.apiEndpoint || ""); - const [apiMethod, setApiMethod] = useState<"POST" | "PUT" | "PATCH">(data.apiMethod || "POST"); + const [apiMethod, setApiMethod] = useState<"POST" | "PUT" | "PATCH">((data.apiMethod as "POST" | "PUT" | "PATCH") || "POST"); const [apiAuthType, setApiAuthType] = useState<"none" | "basic" | "bearer" | "apikey">(data.apiAuthType || "none"); const [apiAuthConfig, setApiAuthConfig] = useState(data.apiAuthConfig || {}); const [apiHeaders, setApiHeaders] = useState>(data.apiHeaders || {}); @@ -695,7 +695,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP } else if (field === "targetField") { const targetColumn = (() => { if (targetType === "internal") { - return targetColumns.find((col) => col.column_name === value); + return targetColumns.find((col) => col.columnName === value); } else if (targetType === "external") { return externalColumns.find((col) => col.column_name === value); } @@ -705,7 +705,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP newMappings[index] = { ...newMappings[index], targetField: value, - targetFieldLabel: targetColumn?.label_ko || targetColumn?.column_label || targetColumn?.displayName || value, + targetFieldLabel: (targetColumn as any)?.label_ko || (targetColumn as any)?.column_label || (targetColumn as any)?.displayName || value, }; } else if (field === "valueType") { // 🔥 값 생성 유형 변경 시 관련 필드 초기화 @@ -719,11 +719,11 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP }; } else if (field === "numberingRuleId") { // 🔥 채번 규칙 선택 시 이름도 함께 저장 - const selectedRule = numberingRules.find((r) => r.ruleId === value); + const selectedRule = numberingRules.find((r) => r.rule_id === value); newMappings[index] = { ...newMappings[index], numberingRuleId: value, - numberingRuleName: selectedRule?.ruleName, + numberingRuleName: selectedRule?.rule_name, }; } else { newMappings[index] = { @@ -995,8 +995,8 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP {externalTables.map((table) => ( {table.table_name} - {table.table_schema && table.table_schema !== "public" && ( - ({table.table_schema}) + {table.schema && table.schema !== "public" && ( + ({table.schema}) )} ))} @@ -1490,12 +1490,12 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP > {mapping.numberingRuleId ? (() => { - const rule = numberingRules.find((r) => r.ruleId === mapping.numberingRuleId); + const rule = numberingRules.find((r) => r.rule_id === mapping.numberingRuleId); return (
- {rule?.ruleName || mapping.numberingRuleName || mapping.numberingRuleId} + {rule?.rule_name || mapping.numberingRuleName || mapping.numberingRuleId}
); @@ -1518,8 +1518,8 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP {numberingRules.map((rule) => ( { handleMappingChange(index, "numberingRuleId", currentValue); const newState = [...mappingNumberingRulesOpenState]; @@ -1531,14 +1531,14 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
- {rule.ruleName} + {rule.rule_name} - {rule.ruleId} - {rule.tableName && ` - ${rule.tableName}`} + {rule.rule_id} + {rule.table_name && ` - ${rule.table_name}`}
diff --git a/frontend/components/dataflow/node-editor/panels/properties/LogProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/LogProperties.tsx index 049ed2c5..2873e73e 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/LogProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/LogProperties.tsx @@ -55,7 +55,7 @@ export function LogProperties({ nodeId, data }: LogPropertiesProps) {
- setLevel(value as any)}> diff --git a/frontend/components/dataflow/node-editor/panels/properties/ProcedureCallActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/ProcedureCallActionProperties.tsx index d75dec92..2fb0de43 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/ProcedureCallActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/ProcedureCallActionProperties.tsx @@ -240,7 +240,7 @@ export function ProcedureCallActionProperties({ if (res.success && res.data) { const newParams = res.data.map((p: ProcedureParameterInfo) => ({ name: p.name, - dataType: p.dataType, + dataType: p.data_type, mode: p.mode, source: "record_field" as const, field: "", diff --git a/frontend/components/dataflow/node-editor/panels/properties/RestAPISourceProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/RestAPISourceProperties.tsx index 2b051d59..e9f8eefc 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/RestAPISourceProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/RestAPISourceProperties.tsx @@ -28,16 +28,16 @@ export function RestAPISourceProperties({ nodeId, data }: RestAPISourcePropertie const [displayName, setDisplayName] = useState(data.displayName || ""); const [url, setUrl] = useState(data.url || ""); - const [method, setMethod] = useState(data.method || "GET"); + const [method, setMethod] = useState(data.method || "GET"); const [headers, setHeaders] = useState(data.headers || {}); const [newHeaderKey, setNewHeaderKey] = useState(""); const [newHeaderValue, setNewHeaderValue] = useState(""); const [body, setBody] = useState(JSON.stringify(data.body || {}, null, 2)); - const [authType, setAuthType] = useState(data.authentication?.type || "none"); + const [authType, setAuthType] = useState(data.authentication?.type || "none"); const [authToken, setAuthToken] = useState(data.authentication?.token || ""); const [timeout, setTimeout] = useState(data.timeout?.toString() || "30000"); const [responseMapping, setResponseMapping] = useState(data.responseMapping || ""); - const [responseFields, setResponseFields] = useState(data.responseFields || []); + const [responseFields, setResponseFields] = useState(data.responseFields || []); useEffect(() => { setDisplayName(data.displayName || ""); @@ -49,7 +49,7 @@ export function RestAPISourceProperties({ nodeId, data }: RestAPISourcePropertie setAuthToken(data.authentication?.token || ""); setTimeout(data.timeout?.toString() || "30000"); setResponseMapping(data.responseMapping || ""); - setResponseFields(data.responseFields || []); + setResponseFields(data.responseFields as any[] || []); }, [data]); const handleApply = () => { diff --git a/frontend/components/dataflow/node-editor/panels/properties/TableSourceProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/TableSourceProperties.tsx index 13b833a4..468d701f 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/TableSourceProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/TableSourceProperties.tsx @@ -70,12 +70,12 @@ export function TableSourceProperties({ nodeId, data }: TableSourcePropertiesPro // 테이블 목록 변환 (라벨 또는 display_name 우선 표시) const options: TableOption[] = tableList.map((table) => { - // table_label이 있으면 우선 사용, 없으면 display_name, 그것도 없으면 table_name - const label = (table as any).table_label || table.display_name || table.table_name || "알 수 없는 테이블"; + // table_label이 있으면 우선 사용, 없으면 displayName, 그것도 없으면 tableName + const label = (table as any).table_label || table.displayName || table.tableName || "알 수 없는 테이블"; return { - table_name: table.table_name, - display_name: table.display_name || table.table_name, + table_name: table.tableName, + display_name: table.displayName || table.tableName, description: table.description || "", label, }; @@ -158,7 +158,7 @@ export function TableSourceProperties({ nodeId, data }: TableSourcePropertiesPro setDataSourceType(newType); updateNode(nodeId, { dataSourceType: newType, - }); + } as any); console.log(`✅ 데이터 소스 타입 변경: ${newType}`); }; diff --git a/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx index a4417fc4..95f4cdce 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx @@ -103,7 +103,7 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP // 🔥 REST API 관련 상태 const [apiEndpoint, setApiEndpoint] = useState(data.apiEndpoint || ""); - const [apiMethod, setApiMethod] = useState<"PUT" | "PATCH">(data.apiMethod || "PUT"); + const [apiMethod, setApiMethod] = useState<"PUT" | "PATCH">((data.apiMethod as "PUT" | "PATCH") || "PUT"); const [apiAuthType, setApiAuthType] = useState<"none" | "basic" | "bearer" | "apikey">(data.apiAuthType || "none"); const [apiAuthConfig, setApiAuthConfig] = useState(data.apiAuthConfig || {}); const [apiHeaders, setApiHeaders] = useState>(data.apiHeaders || {}); @@ -393,11 +393,11 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP console.log("🔍 UPDATE 노드 - 테이블 목록:", tableList); const options: TableOption[] = tableList.map((table) => { - const label = (table as any).table_label || table.display_name || table.table_name || "알 수 없는 테이블"; + const label = (table as any).table_label || table.displayName || table.tableName || "알 수 없는 테이블"; return { - table_name: table.table_name, - display_name: table.display_name || table.table_name, + table_name: table.tableName, + display_name: table.displayName || table.tableName, description: table.description || "", label, }; @@ -817,7 +817,7 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP setExternalColumns([]); updateNode(nodeId, { externalConnectionId: connectionId, - externalConnectionName: selectedConnection?.name, + externalConnectionName: selectedConnection?.connection_name, externalDbType: selectedConnection?.db_type, }); }} @@ -836,7 +836,7 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP
{conn.db_type} - - {conn.name} + {conn.connection_name}
)) diff --git a/frontend/components/dataflow/node-editor/panels/properties/UpsertActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/UpsertActionProperties.tsx index df348b36..a421da94 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/UpsertActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/UpsertActionProperties.tsx @@ -67,7 +67,7 @@ export function UpsertActionProperties({ nodeId, data }: UpsertActionPropertiesP // 🔥 REST API 관련 상태 const [apiEndpoint, setApiEndpoint] = useState(data.apiEndpoint || ""); - const [apiMethod, setApiMethod] = useState<"POST" | "PUT" | "PATCH">(data.apiMethod || "PUT"); + const [apiMethod, setApiMethod] = useState<"POST" | "PUT" | "PATCH">((data.apiMethod as "POST" | "PUT" | "PATCH") || "POST"); const [apiAuthType, setApiAuthType] = useState<"none" | "basic" | "bearer" | "apikey">(data.apiAuthType || "none"); const [apiAuthConfig, setApiAuthConfig] = useState(data.apiAuthConfig || {}); const [apiHeaders, setApiHeaders] = useState>(data.apiHeaders || {}); @@ -435,11 +435,11 @@ export function UpsertActionProperties({ nodeId, data }: UpsertActionPropertiesP const tableList = await tableTypeApi.getTables(); const options: TableOption[] = tableList.map((table) => { - const label = (table as any).table_label || table.display_name || table.table_name || "알 수 없는 테이블"; + const label = (table as any).table_label || table.displayName || table.tableName || "알 수 없는 테이블"; return { - table_name: table.table_name, - display_name: table.display_name || table.table_name, + table_name: table.tableName, + display_name: table.displayName || table.tableName, description: table.description || "", label, }; @@ -761,7 +761,7 @@ export function UpsertActionProperties({ nodeId, data }: UpsertActionPropertiesP setExternalColumns([]); updateNode(nodeId, { externalConnectionId: connectionId, - externalConnectionName: selectedConnection?.name, + externalConnectionName: selectedConnection?.connection_name, externalDbType: selectedConnection?.db_type, }); }} @@ -780,7 +780,7 @@ export function UpsertActionProperties({ nodeId, data }: UpsertActionPropertiesP
{conn.db_type} - - {conn.name} + {conn.connection_name}
)) diff --git a/frontend/components/flow/FlowConditionBuilder.tsx b/frontend/components/flow/FlowConditionBuilder.tsx index 30165e16..a3d3ef6e 100644 --- a/frontend/components/flow/FlowConditionBuilder.tsx +++ b/frontend/components/flow/FlowConditionBuilder.tsx @@ -125,9 +125,9 @@ export function FlowConditionBuilder({ if (restApiData.columns && restApiData.columns.length > 0) { // 별칭 적용 const prefixedColumns = restApiData.columns.map((col) => ({ - column_name: config.alias ? `${config.alias}${col.column_name}` : col.column_name, - data_type: col.data_type || "varchar", - display_name: `${col.column_label || col.column_name} (${config.connectionName})`, + column_name: config.alias ? `${config.alias}${col.columnName}` : col.columnName, + data_type: col.dataType || "varchar", + display_name: `${col.columnLabel || col.columnName} (${config.connectionName})`, sourceApi: config.connectionName, })); allColumns.push(...prefixedColumns); @@ -181,9 +181,9 @@ export function FlowConditionBuilder({ if (restApiData.columns && restApiData.columns.length > 0) { const columnList = restApiData.columns.map((col) => ({ - column_name: col.column_name, - data_type: col.data_type || "varchar", - display_name: col.column_label || col.column_name, + column_name: col.columnName, + data_type: col.dataType || "varchar", + display_name: col.columnLabel || col.columnName, })); console.log("✅ Setting REST API columns:", columnList.length, "items", columnList); setColumns(columnList); @@ -244,7 +244,7 @@ export function FlowConditionBuilder({ } } else { // 내부 DB인 경우 (기존 로직) - const response = await getTableColumns(tableName); + const response = await getTableColumns(tableName!); console.log("📦 [FlowConditionBuilder] Internal columns response:", response); if (response.success && response.data?.columns) { diff --git a/frontend/components/flow/FlowNodeComponent.tsx b/frontend/components/flow/FlowNodeComponent.tsx index d52291bb..2b63b710 100644 --- a/frontend/components/flow/FlowNodeComponent.tsx +++ b/frontend/components/flow/FlowNodeComponent.tsx @@ -61,9 +61,9 @@ export const FlowNodeComponent = memo(({ data }: NodeProps) => {
- 단계 {data.stepOrder} + 단계 {data.step_order} - {data.integrationType === "procedure" && ( + {data.integration_type === "procedure" && ( SP @@ -73,17 +73,17 @@ export const FlowNodeComponent = memo(({ data }: NodeProps) => {
{data.label}
{/* 테이블 정보 */} - {data.tableName && ( + {data.table_name && (
📊 - {data.tableName} + {data.table_name}
)} {/* 프로시저 정보 */} - {data.integrationType === "procedure" && data.procedureName && ( + {data.integration_type === "procedure" && data.procedure_name && (
- {data.procedureName}() + {data.procedure_name}()
)} diff --git a/frontend/components/flow/FlowStepPanel.tsx b/frontend/components/flow/FlowStepPanel.tsx index 2f8ab5d3..ba2f78b1 100644 --- a/frontend/components/flow/FlowStepPanel.tsx +++ b/frontend/components/flow/FlowStepPanel.tsx @@ -73,31 +73,31 @@ export function FlowStepPanel({ const { toast } = useToast(); console.log("🎯 FlowStepPanel Props:", { - stepTableName: step.tableName, + stepTableName: step.table_name, flowTableName, flowDbSourceType, flowDbConnectionId, flowRestApiConnectionId, flowRestApiEndpoint, flowRestApiJsonPath, - final: step.tableName || flowTableName || "", + final: step.table_name || flowTableName || "", }); const [formData, setFormData] = useState({ - stepName: step.stepName, - tableName: step.tableName || flowTableName || "", // 플로우 테이블명 우선 사용 (신규 방식) - conditionJson: step.conditionJson, + stepName: step.step_name, + tableName: step.table_name || flowTableName || "", // 플로우 테이블명 우선 사용 (신규 방식) + conditionJson: step.condition_json, // 하이브리드 모드 필드 - moveType: step.moveType || "status", - statusColumn: step.statusColumn || "", - statusValue: step.statusValue || "", - targetTable: step.targetTable || "", - fieldMappings: step.fieldMappings || {}, + moveType: (step as any).moveType || "status", + statusColumn: (step as any).statusColumn || "", + statusValue: (step as any).statusValue || "", + targetTable: (step as any).targetTable || "", + fieldMappings: (step as any).fieldMappings || {}, // 외부 연동 필드 - integrationType: step.integrationType || "internal", - integrationConfig: step.integrationConfig, + integrationType: (step as any).integrationType || "internal", + integrationConfig: (step as any).integrationConfig, // 🆕 표시 설정 - displayConfig: step.displayConfig || { visibleColumns: [] }, + displayConfig: step.display_config || { visibleColumns: [] }, }); const [tableList, setTableList] = useState([]); @@ -209,8 +209,8 @@ export function FlowStepPanel({ columnsArray = response.data; } else if (response.data.columns && Array.isArray(response.data.columns)) { columnsArray = response.data.columns; - } else if (response.data.data && Array.isArray(response.data.data)) { - columnsArray = response.data.data; + } else if ((response.data as any).data && Array.isArray((response.data as any).data)) { + columnsArray = (response.data as any).data; } else { console.warn("⚠️ 예상치 못한 data 구조:", response.data); } @@ -319,27 +319,27 @@ export function FlowStepPanel({ useEffect(() => { console.log("🔄 Initializing formData from step:", { id: step.id, - stepName: step.stepName, - statusColumn: step.statusColumn, - statusValue: step.statusValue, + stepName: step.step_name, + statusColumn: (step as any).statusColumn, + statusValue: (step as any).statusValue, flowTableName, // 플로우 정의의 테이블명 }); const newFormData = { - stepName: step.stepName, - tableName: step.tableName || flowTableName || "", // 플로우 테이블명 우선 사용 - conditionJson: step.conditionJson, + stepName: step.step_name, + tableName: step.table_name || flowTableName || "", // 플로우 테이블명 우선 사용 + conditionJson: step.condition_json, // 하이브리드 모드 필드 - moveType: step.moveType || "status", - statusColumn: step.statusColumn || "", - statusValue: step.statusValue || "", - targetTable: step.targetTable || "", - fieldMappings: step.fieldMappings || {}, + moveType: (step as any).moveType || "status", + statusColumn: (step as any).statusColumn || "", + statusValue: (step as any).statusValue || "", + targetTable: (step as any).targetTable || "", + fieldMappings: (step as any).fieldMappings || {}, // 외부 연동 필드 - integrationType: step.integrationType || "internal", - integrationConfig: step.integrationConfig, + integrationType: (step as any).integrationType || "internal", + integrationConfig: (step as any).integrationConfig, // 표시 설정 (displayConfig 반드시 초기화) - displayConfig: step.displayConfig || { visibleColumns: [] }, + displayConfig: step.display_config || { visibleColumns: [] }, }; console.log("✅ Setting formData:", newFormData); @@ -384,9 +384,9 @@ export function FlowStepPanel({ if (restApiData.columns && restApiData.columns.length > 0) { const prefixedColumns = restApiData.columns.map((col) => ({ - column_name: config.alias ? `${config.alias}${col.column_name}` : col.column_name, - data_type: col.data_type || "varchar", - displayName: `${col.column_label || col.column_name} (${config.connectionName})`, + column_name: config.alias ? `${config.alias}${col.columnName}` : col.columnName, + data_type: col.dataType || "varchar", + displayName: `${col.columnLabel || col.columnName} (${config.connectionName})`, })); allColumns.push(...prefixedColumns); } @@ -417,9 +417,9 @@ export function FlowStepPanel({ if (restApiData.columns && restApiData.columns.length > 0) { const columnList = restApiData.columns.map((col) => ({ - column_name: col.column_name, - data_type: col.data_type || "varchar", - displayName: col.column_label || col.column_name, + column_name: col.columnName, + data_type: col.dataType || "varchar", + displayName: col.columnLabel || col.columnName, })); console.log("✅ REST API 컬럼 로드 완료:", columnList.length, "items"); setColumns(columnList); diff --git a/frontend/components/layout/AdminPageRenderer.tsx b/frontend/components/layout/AdminPageRenderer.tsx index ae087212..103dfbc7 100644 --- a/frontend/components/layout/AdminPageRenderer.tsx +++ b/frontend/components/layout/AdminPageRenderer.tsx @@ -129,7 +129,7 @@ const ADMIN_PAGE_REGISTRY: Record> = { // 시스템 "/admin/audit-log": dynamic(() => import("@/app/(main)/admin/audit-log/page"), { ssr: false, loading: LoadingFallback }), "/admin/system-notices": dynamic(() => import("@/app/(main)/admin/system-notices/page"), { ssr: false, loading: LoadingFallback }), - "/admin/aiAssistant": dynamic(() => import("@/app/(main)/admin/aiAssistant/page"), { ssr: false, loading: LoadingFallback }), + "/admin/aiAssistant": dynamic(() => import("@/app/(main)/admin/aiAssistant/page") as any, { ssr: false, loading: LoadingFallback }), // 기타 "/admin/cascading-management": dynamic(() => import("@/app/(main)/admin/cascading-management/page"), { ssr: false, loading: LoadingFallback }), @@ -237,7 +237,7 @@ function DynamicAdminLoader({ url, params }: { url: string; params?: Record mod.default); } catch { if (!cancelled) setFailed(true); diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index f3376358..e1e4621d 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -233,7 +233,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { const pathname = usePathname(); const searchParams = useSearchParams(); const { user, logout, refreshUserData } = useAuth(); - const { userMenus, adminMenus, loading, refreshMenus } = useMenu(); + const { user_menus: userMenus, admin_menus: adminMenus, loading, refreshMenus } = useMenu(); const [sidebarOpen, setSidebarOpen] = useState(true); const [expandedMenus, setExpandedMenus] = useState>(new Set()); const [isMobile, setIsMobile] = useState(false); @@ -249,14 +249,14 @@ function AppLayoutInner({ children }: AppLayoutProps) { const screenMatch = pathname.match(/^\/screens\/(\d+)/); if (screenMatch) { const screenId = parseInt(screenMatch[1]); - const menuObjid = searchParams.get("menuObjid") ? parseInt(searchParams.get("menuObjid")!) : undefined; - store.openTab({ type: "screen", title: `화면 ${screenId}`, screenId, menuObjid }); + const menu_objid = searchParams.get("menuObjid") ? parseInt(searchParams.get("menuObjid")!) : undefined; + store.openTab({ type: "screen", title: `화면 ${screenId}`, screen_id: screenId, menu_objid }); return; } if (pathname.startsWith("/admin") && pathname !== "/admin") { store.setMode("admin"); - store.openTab({ type: "admin", title: pathname.split("/").pop() || "관리자", adminUrl: pathname }); + store.openTab({ type: "admin", title: pathname.split("/").pop() || "관리자", admin_url: pathname }); } }, []); @@ -342,7 +342,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { const currentMenus = isAdminMode ? adminMenus : userMenus; const currentTabs = useTabStore((s) => s[s.mode].tabs); - const currentActiveTabId = useTabStore((s) => s[s.mode].activeTabId); + const currentActiveTabId = useTabStore((s) => s[s.mode].active_tab_id); const activeTab = currentTabs.find((t) => t.id === currentActiveTabId); const toggleMenu = (menuId: string) => { @@ -386,7 +386,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { if (isAdminMenu) { if (menu.url && menu.url !== "#") { console.log("[handleMenuClick] → admin 탭:", menu.url); - openTab({ type: "admin", title: menuName, adminUrl: menu.url }); + openTab({ type: "admin", title: menuName, admin_url: menu.url }); if (isMobile) setSidebarOpen(false); } else { toast.warning("이 메뉴에는 연결된 페이지가 없습니다."); @@ -398,7 +398,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { // 1) screenId가 메뉴 URL에서 추출된 경우 바로 screen 탭 if (menu.screen_id) { console.log("[handleMenuClick] → screen 탭 (URL에서 screenId 추출):", menu.screen_id); - openTab({ type: "screen", title: menuName, screenId: menu.screen_id, menuObjid }); + openTab({ type: "screen", title: menuName, screen_id: menu.screen_id, menu_objid: menuObjid }); if (isMobile) setSidebarOpen(false); return; } @@ -412,7 +412,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { if (assignedScreens.length > 0) { const assignedScreenId = assignedScreens[0].screen_id; console.log("[handleMenuClick] → screen 탭 (assignments):", assignedScreenId); - openTab({ type: "screen", title: menuName, screenId: assignedScreenId, menuObjid }); + openTab({ type: "screen", title: menuName, screen_id: assignedScreenId, menu_objid: menuObjid }); if (isMobile) setSidebarOpen(false); return; } @@ -424,7 +424,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { // 3) 대시보드 할당 (/dashboard/xxx) → admin 탭으로 렌더링 (AdminPageRenderer가 처리) if (menu.url && menu.url.startsWith("/dashboard/")) { console.log("[handleMenuClick] → 대시보드 탭:", menu.url); - openTab({ type: "admin", title: menuName, adminUrl: menu.url }); + openTab({ type: "admin", title: menuName, admin_url: menu.url }); if (isMobile) setSidebarOpen(false); return; } @@ -432,7 +432,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { // 4) 커스텀 페이지 URL (React 직접 구현 페이지) → admin 탭으로 렌더링 if (menu.url && menu.url !== "#" && !menu.url.startsWith("/screen/") && !menu.url.startsWith("/screens/")) { console.log("[handleMenuClick] → 커스텀 페이지 탭:", menu.url); - openTab({ type: "admin", title: menuName, adminUrl: menu.url }); + openTab({ type: "admin", title: menuName, admin_url: menu.url }); if (isMobile) setSidebarOpen(false); return; } @@ -499,12 +499,12 @@ function AppLayoutInner({ children }: AppLayoutProps) { const menuObjid = parseInt((menu.objid || menu.id)?.toString() || "0"); - if (activeTab.type === "admin" && activeTab.adminUrl) { - return menu.url === activeTab.adminUrl; + if (activeTab.type === "admin" && activeTab.admin_url) { + return menu.url === activeTab.admin_url; } if (activeTab.type === "screen") { - if (activeTab.menuObjid != null && menuObjid === activeTab.menuObjid) return true; - const { screenId: activeTabScreenId } = activeTab; + if (activeTab.menu_objid != null && menuObjid === activeTab.menu_objid) return true; + const { screen_id: activeTabScreenId } = activeTab; if (activeTabScreenId != null && menu.screen_id === activeTabScreenId) return true; } return false; diff --git a/frontend/components/layout/ProfileModal.tsx b/frontend/components/layout/ProfileModal.tsx index 42659e65..9146ea69 100644 --- a/frontend/components/layout/ProfileModal.tsx +++ b/frontend/components/layout/ProfileModal.tsx @@ -328,8 +328,8 @@ export function ProfileModal({ onFormChange("positionName", e.target.value)} + value={formData.position_name} + onChange={(e) => onFormChange("position_name", e.target.value)} placeholder="직급을 입력하세요" />
diff --git a/frontend/components/layout/TabBar.tsx b/frontend/components/layout/TabBar.tsx index 508fa641..5a436a2c 100644 --- a/frontend/components/layout/TabBar.tsx +++ b/frontend/components/layout/TabBar.tsx @@ -183,7 +183,7 @@ export function TabBar() { const screens = await menuScreenApi.getScreensByMenu(numericObjid); if (screens.length > 0) { openTab( - { type: "screen", title: menuName, screenId: screens[0].screen_id, menuObjid: numericObjid }, + { type: "screen", title: menuName, screen_id: screens[0].screen_id, menu_objid: numericObjid }, insertIndex, ); return; @@ -192,7 +192,7 @@ export function TabBar() { /* ignore */ } if (url && url !== "#") { - openTab({ type: "admin", title: menuName, adminUrl: url }, insertIndex); + openTab({ type: "admin", title: menuName, admin_url: url }, insertIndex); } else { setExternalDragIdx(null); } diff --git a/frontend/components/layout/TabContent.tsx b/frontend/components/layout/TabContent.tsx index e0d90635..a79a0d83 100644 --- a/frontend/components/layout/TabContent.tsx +++ b/frontend/components/layout/TabContent.tsx @@ -27,7 +27,7 @@ let hasHandledPageLoad = false; export function TabContent() { const tabs = useTabStore(selectTabs); const activeTabId = useTabStore(selectActiveTabId); - const refreshKeys = useTabStore((s) => s.refreshKeys); + const refreshKeys = useTabStore((s) => s.refresh_keys); // 한 번이라도 활성화된 탭만 마운트 (지연 마운트) const mountedTabIdsRef = useRef>(new Set()); @@ -235,16 +235,16 @@ function TabPageRenderer({ tab, refreshKey, }: { - tab: { id: string; type: string; screenId?: number; menuObjid?: number; adminUrl?: string }; + tab: { id: string; type: string; screen_id?: number; menu_objid?: number; admin_url?: string }; refreshKey: number; }) { - const { screenId: tabScreenId } = tab; + const { screen_id: tabScreenId } = tab; console.log("[TabPageRenderer] 탭 렌더링:", { tabId: tab.id, type: tab.type, screenId: tabScreenId, - adminUrl: tab.adminUrl, - menuObjid: tab.menuObjid, + adminUrl: tab.admin_url, + menuObjid: tab.menu_objid, }); if (tab.type === "screen" && tabScreenId != null) { @@ -252,15 +252,15 @@ function TabPageRenderer({ ); } - if (tab.type === "admin" && tab.adminUrl) { + if (tab.type === "admin" && tab.admin_url) { return (
- +
); } diff --git a/frontend/components/mail/MailAccountTable.tsx b/frontend/components/mail/MailAccountTable.tsx index 35af8e37..dfb0c769 100644 --- a/frontend/components/mail/MailAccountTable.tsx +++ b/frontend/components/mail/MailAccountTable.tsx @@ -32,7 +32,7 @@ export default function MailAccountTable({ onTestConnection, }: MailAccountTableProps) { const [searchTerm, setSearchTerm] = useState(''); - const [sortField, setSortField] = useState('createdAt'); + const [sortField, setSortField] = useState('created_at'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // 검색 필터링 @@ -40,7 +40,7 @@ export default function MailAccountTable({ (account) => account.name.toLowerCase().includes(searchTerm.toLowerCase()) || account.email.toLowerCase().includes(searchTerm.toLowerCase()) || - account.smtpHost.toLowerCase().includes(searchTerm.toLowerCase()) + account.smtp_host.toLowerCase().includes(searchTerm.toLowerCase()) ); // 정렬 @@ -145,7 +145,7 @@ export default function MailAccountTable({ handleSort('dailyLimit')} + onClick={() => handleSort('daily_limit')} >
@@ -154,7 +154,7 @@ export default function MailAccountTable({ handleSort('createdAt')} + onClick={() => handleSort('created_at')} >
@@ -180,10 +180,10 @@ export default function MailAccountTable({
- {account.smtpHost}:{account.smtpPort} + {account.smtp_host}:{account.smtp_port}
- {account.smtpSecure ? 'SSL' : 'TLS'} + {account.smtp_secure ? 'SSL' : 'TLS'}
@@ -210,14 +210,14 @@ export default function MailAccountTable({
- {account.dailyLimit > 0 - ? account.dailyLimit.toLocaleString() + {account.daily_limit > 0 + ? account.daily_limit.toLocaleString() : '무제한'}
- {formatDate(account.createdAt)} + {formatDate(account.created_at)}
diff --git a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx index a32d6ecc..58fda3bb 100644 --- a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx +++ b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx @@ -148,7 +148,7 @@ export const NumberingRuleDesigner: React.FC = ({ const newSepTypes: Record = {}; const newCustomSeps: Record = {}; currentRule.parts.forEach((part) => { - const sep = part.separatorAfter ?? currentRule.separator ?? "-"; + const sep = part.separator_after ?? currentRule.separator ?? "-"; if (sep === "") { newSepTypes[part.order] = "none"; newCustomSeps[part.order] = ""; @@ -168,7 +168,7 @@ export const NumberingRuleDesigner: React.FC = ({ setSeparatorTypes(newSepTypes); setCustomSeparators(newCustomSeps); } - }, [currentRule?.ruleId]); + }, [currentRule?.rule_id]); const handlePartSeparatorChange = useCallback((partOrder: number, type: SeparatorType) => { setSeparatorTypes((prev) => ({ ...prev, [partOrder]: type })); @@ -180,7 +180,7 @@ export const NumberingRuleDesigner: React.FC = ({ if (!prev) return null; return { ...prev, - parts: prev.parts.map((p) => (p.order === partOrder ? { ...p, separatorAfter: newSeparator } : p)), + parts: prev.parts.map((p) => (p.order === partOrder ? { ...p, separator_after: newSeparator } : p)), }; }); } @@ -193,7 +193,7 @@ export const NumberingRuleDesigner: React.FC = ({ if (!prev) return null; return { ...prev, - parts: prev.parts.map((p) => (p.order === partOrder ? { ...p, separatorAfter: trimmedValue } : p)), + parts: prev.parts.map((p) => (p.order === partOrder ? { ...p, separator_after: trimmedValue } : p)), }; }); }, []); @@ -207,10 +207,10 @@ export const NumberingRuleDesigner: React.FC = ({ const newPart: NumberingRulePart = { id: `part-${Date.now()}`, order: currentRule.parts.length + 1, - partType: "text", - generationMethod: "auto", - autoConfig: { textValue: "CODE" }, - separatorAfter: "-", + part_type: "text", + generation_method: "auto", + auto_config: { text_value: "CODE" }, + separator_after: "-", }; setCurrentRule((prev) => (prev ? { ...prev, parts: [...prev.parts, newPart] } : null)); setSeparatorTypes((prev) => ({ ...prev, [newPart.order]: "-" })); @@ -260,9 +260,9 @@ export const NumberingRuleDesigner: React.FC = ({ text: { textValue: "" }, }; const partsWithDefaults = currentRule.parts.map((part) => { - if (part.generationMethod === "auto") { - const defaults = defaultAutoConfigs[part.partType] || {}; - return { ...part, autoConfig: { ...defaults, ...part.autoConfig } }; + if (part.generation_method === "auto") { + const defaults = defaultAutoConfigs[part.part_type] || {}; + return { ...part, auto_config: { ...defaults, ...part.auto_config } }; } return part; }); @@ -270,8 +270,8 @@ export const NumberingRuleDesigner: React.FC = ({ ...currentRule, parts: partsWithDefaults, scopeType: "table" as const, - tableName: selectedColumn?.tableName || currentRule.tableName || "", - columnName: selectedColumn?.columnName || currentRule.columnName || "", + table_name: selectedColumn?.tableName || currentRule.table_name || "", + column_name: selectedColumn?.columnName || currentRule.column_name || "", }; const response = await saveNumberingRuleToTest(ruleToSave); if (response.success && response.data) { @@ -389,8 +389,8 @@ export const NumberingRuleDesigner: React.FC = ({
setCurrentRule((prev) => (prev ? { ...prev, ruleName: e.target.value } : null))} + value={currentRule.rule_name} + onChange={(e) => setCurrentRule((prev) => (prev ? { ...prev, rule_name: e.target.value } : null))} placeholder="예: 프로젝트 코드" className="h-9 text-sm" /> @@ -433,19 +433,19 @@ export const NumberingRuleDesigner: React.FC = ({ <> {currentRule.parts.map((part, index) => { const item = partItems.find((i) => i.order === part.order); - const sep = part.separatorAfter ?? globalSep; + const sep = part.separator_after ?? globalSep; const isPartSelected = selectedPartOrder === part.order; - const typeLabel = CODE_PART_TYPE_OPTIONS.find((o) => o.value === part.partType)?.label ?? part.partType; + const typeLabel = CODE_PART_TYPE_OPTIONS.find((o) => o.value === part.part_type)?.label ?? part.part_type; return ( @@ -491,7 +491,7 @@ export const NumberingRuleDesigner: React.FC = ({ onUpdate={(updates) => handleUpdatePart(selectedPart.order, updates)} onDelete={() => handleDeletePart(selectedPart.order)} isPreview={isPreview} - tableName={currentRule.tableName ?? currentTableName} + tableName={currentRule.table_name ?? currentTableName} />
{currentRule.parts.some((p) => p.order === selectedPart.order) && ( @@ -529,15 +529,15 @@ export const NumberingRuleDesigner: React.FC = ({ {/* 저장 바 */}
- {currentRule.tableName && ( - 테이블: {currentRule.tableName} + {currentRule.table_name && ( + 테이블: {currentRule.table_name} )} - {currentRule.columnName && ( - 컬럼: {currentRule.columnName} + {currentRule.column_name && ( + 컬럼: {currentRule.column_name} )} 구분자: {globalSep || "-"} - {currentRule.resetPeriod && currentRule.resetPeriod !== "none" && ( - 리셋: {currentRule.resetPeriod} + {currentRule.reset_period && currentRule.reset_period !== "none" && ( + 리셋: {currentRule.reset_period} )}