diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 85e502a0..1b54d3b9 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -26,12 +26,56 @@ interface EditModalState { onSave?: () => void; groupByColumns?: string[]; // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ (์˜ˆ: ["order_no"]) tableName?: string; // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… (๊ทธ๋ฃน ์กฐํšŒ์šฉ) + buttonConfig?: any; // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • (์ œ์–ด๋กœ์ง ์‹คํ–‰์šฉ) + buttonContext?: any; // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ (screenId, userId ๋“ฑ) + saveButtonConfig?: { + enableDataflowControl?: boolean; + dataflowConfig?: any; + dataflowTiming?: string; + }; // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • } interface EditModalProps { className?: string; } +/** + * ๋ชจ๋‹ฌ ๋‚ด๋ถ€์—์„œ ์ €์žฅ ๋ฒ„ํŠผ ์ฐพ๊ธฐ (์žฌ๊ท€์ ์œผ๋กœ ํƒ์ƒ‰) + * action.type์ด "save"์ธ button-primary ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์Œ + */ +const findSaveButtonInComponents = (components: any[]): any | null => { + if (!components || !Array.isArray(components)) return null; + + for (const comp of components) { + // button-primary์ด๊ณ  action.type์ด save์ธ ๊ฒฝ์šฐ + if ( + comp.componentType === "button-primary" && + comp.componentConfig?.action?.type === "save" + ) { + return comp; + } + + // conditional-container์˜ sections ๋‚ด๋ถ€ ํƒ์ƒ‰ + if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) { + for (const section of comp.componentConfig.sections) { + if (section.screenId) { + // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ์˜ ๋‚ด๋ถ€ ํ™”๋ฉด์€ ๋ณ„๋„๋กœ ๋กœ๋“œํ•ด์•ผ ํ•จ + // ์—ฌ๊ธฐ์„œ๋Š” null ๋ฐ˜ํ™˜ํ•˜๊ณ , loadSaveButtonConfig์—์„œ ์ฒ˜๋ฆฌ + continue; + } + } + } + + // ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์žฌ๊ท€ ํƒ์ƒ‰ + if (comp.children && Array.isArray(comp.children)) { + const found = findSaveButtonInComponents(comp.children); + if (found) return found; + } + } + + return null; +}; + export const EditModal: React.FC = ({ className }) => { const { user } = useAuth(); const [modalState, setModalState] = useState({ @@ -44,6 +88,9 @@ export const EditModal: React.FC = ({ className }) => { onSave: undefined, groupByColumns: undefined, tableName: undefined, + buttonConfig: undefined, + buttonContext: undefined, + saveButtonConfig: undefined, }); const [screenData, setScreenData] = useState<{ @@ -115,11 +162,88 @@ export const EditModal: React.FC = ({ className }) => { }; }; + // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • ์กฐํšŒ + const loadSaveButtonConfig = async (targetScreenId: number): Promise<{ + enableDataflowControl?: boolean; + dataflowConfig?: any; + dataflowTiming?: string; + } | null> => { + try { + // 1. ๋Œ€์ƒ ํ™”๋ฉด์˜ ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ + const layoutData = await screenApi.getLayout(targetScreenId); + + if (!layoutData?.components) { + console.log("[EditModal] ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ ์—†์Œ:", targetScreenId); + return null; + } + + // 2. ์ €์žฅ ๋ฒ„ํŠผ ์ฐพ๊ธฐ + let saveButton = findSaveButtonInComponents(layoutData.components); + + // 3. conditional-container๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‚ด๋ถ€ ํ™”๋ฉด๋„ ํƒ์ƒ‰ + if (!saveButton) { + for (const comp of layoutData.components) { + if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) { + for (const section of comp.componentConfig.sections) { + if (section.screenId) { + try { + const innerLayoutData = await screenApi.getLayout(section.screenId); + saveButton = findSaveButtonInComponents(innerLayoutData?.components || []); + if (saveButton) { + console.log("[EditModal] ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ์ €์žฅ ๋ฒ„ํŠผ ๋ฐœ๊ฒฌ:", { + sectionScreenId: section.screenId, + sectionLabel: section.label, + }); + break; + } + } catch (innerError) { + console.warn("[EditModal] ๋‚ด๋ถ€ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ ์‹คํŒจ:", section.screenId); + } + } + } + if (saveButton) break; + } + } + } + + if (!saveButton) { + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ:", targetScreenId); + return null; + } + + // 4. webTypeConfig์—์„œ ์ œ์–ด๋กœ์ง ์„ค์ • ์ถ”์ถœ + const webTypeConfig = saveButton.webTypeConfig; + if (webTypeConfig?.enableDataflowControl) { + const config = { + enableDataflowControl: webTypeConfig.enableDataflowControl, + dataflowConfig: webTypeConfig.dataflowConfig, + dataflowTiming: webTypeConfig.dataflowConfig?.flowConfig?.executionTiming || "after", + }; + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ ์ œ์–ด๋กœ์ง ์„ค์ • ๋ฐœ๊ฒฌ:", config); + return config; + } + + console.log("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ์— ์ œ์–ด๋กœ์ง ์„ค์ • ์—†์Œ"); + return null; + } catch (error) { + console.warn("[EditModal] ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ • ์กฐํšŒ ์‹คํŒจ:", error); + return null; + } + }; + // ์ „์—ญ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ useEffect(() => { - const handleOpenEditModal = (event: CustomEvent) => { - const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode } = - event.detail; + const handleOpenEditModal = async (event: CustomEvent) => { + const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode, buttonConfig, buttonContext } = event.detail; + + // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • ์กฐํšŒ + let saveButtonConfig: EditModalState["saveButtonConfig"] = undefined; + if (screenId) { + const config = await loadSaveButtonConfig(screenId); + if (config) { + saveButtonConfig = config; + } + } setModalState({ isOpen: true, @@ -131,6 +255,9 @@ export const EditModal: React.FC = ({ className }) => { onSave, groupByColumns, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… + buttonConfig, // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • + buttonContext, // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ + saveButtonConfig, // ๐Ÿ†• ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ์˜ ์ œ์–ด๋กœ์ง ์„ค์ • }); // ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ํผ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” @@ -578,6 +705,46 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] ๊ทธ๋ฃน ์ €์žฅ ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { + hasSaveButtonConfig: !!modalState.saveButtonConfig, + hasButtonConfig: !!modalState.buttonConfig, + controlConfig, + }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + // buttonActions์˜ executeAfterSaveControl ๋™์  import + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + // ์ œ์–ด๋กœ์ง ์‹คํ–‰ + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData: modalState.editData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } else { + console.log("โ„น๏ธ [EditModal] ์ €์žฅ ํ›„ ์‹คํ–‰ํ•  ์ œ์–ด๋กœ์ง ์—†์Œ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + // ์ œ์–ด๋กœ์ง ์˜ค๋ฅ˜๋Š” ์ €์žฅ ์„ฑ๊ณต์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Œ (๊ฒฝ๊ณ ๋งŒ ํ‘œ์‹œ) + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { toast.info("๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค."); @@ -612,6 +779,37 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] INSERT ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { controlConfig }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { throw new Error(response.message || "์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); @@ -654,6 +852,37 @@ export const EditModal: React.FC = ({ className }) => { } } + // ๐Ÿ†• ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ (๋ฒ„ํŠผ์˜ After ํƒ€์ด๋ฐ ์ œ์–ด) + // ์šฐ์„ ์ˆœ์œ„: ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ์ €์žฅ ๋ฒ„ํŠผ ์„ค์ •(saveButtonConfig) > ์ˆ˜์ • ๋ฒ„ํŠผ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ค์ •(buttonConfig) + try { + const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig; + + console.log("[EditModal] UPDATE ์™„๋ฃŒ ํ›„ ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์‹œ๋„", { controlConfig }); + + if (controlConfig?.enableDataflowControl && controlConfig?.dataflowTiming === "after") { + console.log("๐ŸŽฏ [EditModal] ์ €์žฅ ํ›„ ์ œ์–ด๋กœ์ง ๋ฐœ๊ฒฌ:", controlConfig.dataflowConfig); + + const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions"); + + await ButtonActionExecutor.executeAfterSaveControl( + controlConfig, + { + formData, + screenId: modalState.buttonContext?.screenId || modalState.screenId, + tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName, + userId: user?.userId, + companyCode: user?.companyCode, + onRefresh: modalState.onSave, + } + ); + + console.log("โœ… [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์™„๋ฃŒ"); + } + } catch (controlError) { + console.error("โŒ [EditModal] ์ œ์–ด๋กœ์ง ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + toast.warning("์ €์žฅ์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‚˜ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + handleClose(); } else { throw new Error(response.message || "์ˆ˜์ •์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index de72f3c0..26bbd0c9 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -376,6 +376,7 @@ export const ButtonPrimaryComponent: React.FC = ({ // ๐Ÿ”ฅ ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ (webTypeConfig์—์„œ ๊ฐ€์ ธ์˜ด) enableDataflowControl: component.webTypeConfig?.enableDataflowControl, dataflowConfig: component.webTypeConfig?.dataflowConfig, + dataflowTiming: component.webTypeConfig?.dataflowTiming, }; } else if (componentConfig.action && typeof componentConfig.action === "object") { // ๐Ÿ”ฅ ์ด๋ฏธ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ์—๋„ ์ œ์–ด๊ด€๋ฆฌ ์„ค์ • ์ถ”๊ฐ€ @@ -383,8 +384,19 @@ export const ButtonPrimaryComponent: React.FC = ({ ...componentConfig.action, enableDataflowControl: component.webTypeConfig?.enableDataflowControl, dataflowConfig: component.webTypeConfig?.dataflowConfig, + dataflowTiming: component.webTypeConfig?.dataflowTiming, }; } + + // ๐Ÿ” ๋””๋ฒ„๊น…: processedConfig.action ํ™•์ธ + console.log("[ButtonPrimaryComponent] processedConfig.action ์ƒ์„ฑ ์™„๋ฃŒ", { + actionType: processedConfig.action?.type, + enableDataflowControl: processedConfig.action?.enableDataflowControl, + dataflowTiming: processedConfig.action?.dataflowTiming, + dataflowConfig: processedConfig.action?.dataflowConfig, + webTypeConfigRaw: component.webTypeConfig, + componentText: component.text, + }); // ์Šคํƒ€์ผ ๊ณ„์‚ฐ // height: 100%๋กœ ๋ถ€๋ชจ(RealtimePreviewDynamic์˜ ๋‚ด๋ถ€ div)์˜ ๋†’์ด๋ฅผ ๋”ฐ๋ผ๊ฐ diff --git a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx index 3f1a723b..7a693ad5 100644 --- a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx +++ b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx @@ -53,6 +53,9 @@ export interface LocationSwapSelectorProps { formData?: Record; onFormDataChange?: (field: string, value: any) => void; + // ๐Ÿ†• ์‚ฌ์šฉ์ž ์ •๋ณด (DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ์šฉ) + userId?: string; + // componentConfig (ํ™”๋ฉด ๋””์ž์ด๋„ˆ์—์„œ ์ „๋‹ฌ) componentConfig?: { dataSource?: DataSourceConfig; @@ -65,6 +68,10 @@ export interface LocationSwapSelectorProps { showSwapButton?: boolean; swapButtonPosition?: "center" | "right"; variant?: "card" | "inline" | "minimal"; + // ๐Ÿ†• DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • + loadFromDb?: boolean; // DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์—ฌ๋ถ€ + dbTableName?: string; // ์กฐํšŒํ•  ํ…Œ์ด๋ธ”๋ช… (๊ธฐ๋ณธ: vehicles) + dbKeyField?: string; // ํ‚ค ํ•„๋“œ (๊ธฐ๋ณธ: user_id) }; } @@ -80,6 +87,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) formData = {}, onFormDataChange, componentConfig, + userId, } = props; // componentConfig์—์„œ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ (์šฐ์„ ์ˆœ์œ„: componentConfig > props) @@ -93,6 +101,11 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) const destinationLabel = config.destinationLabel || props.destinationLabel || "๋„์ฐฉ์ง€"; const showSwapButton = config.showSwapButton !== false && props.showSwapButton !== false; const variant = config.variant || props.variant || "card"; + + // ๐Ÿ†• DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • + const loadFromDb = config.loadFromDb !== false; // ๊ธฐ๋ณธ๊ฐ’ true + const dbTableName = config.dbTableName || "vehicles"; + const dbKeyField = config.dbKeyField || "user_id"; // ๊ธฐ๋ณธ ์˜ต์…˜ (ํฌํ•ญ/๊ด‘์–‘) const DEFAULT_OPTIONS: LocationOption[] = [ @@ -104,6 +117,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) const [options, setOptions] = useState(DEFAULT_OPTIONS); const [loading, setLoading] = useState(false); const [isSwapping, setIsSwapping] = useState(false); + const [dbLoaded, setDbLoaded] = useState(false); // DB ๋กœ๋“œ ์™„๋ฃŒ ์—ฌ๋ถ€ // ๋กœ์ปฌ ์„ ํƒ ์ƒํƒœ (Select ์ปดํฌ๋„ŒํŠธ์šฉ) const [localDeparture, setLocalDeparture] = useState(""); @@ -193,8 +207,89 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) loadOptions(); }, [dataSource, isDesignMode]); - // formData์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋™๊ธฐํ™” + // ๐Ÿ†• DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ (์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์œ ์ง€) useEffect(() => { + const loadFromDatabase = async () => { + // ๋””์ž์ธ ๋ชจ๋“œ์ด๊ฑฐ๋‚˜, DB ๋กœ๋“œ ๋น„ํ™œ์„ฑํ™”์ด๊ฑฐ๋‚˜, userId๊ฐ€ ์—†์œผ๋ฉด ์Šคํ‚ต + if (isDesignMode || !loadFromDb || !userId) { + console.log("[LocationSwapSelector] DB ๋กœ๋“œ ์Šคํ‚ต:", { isDesignMode, loadFromDb, userId }); + return; + } + + // ์ด๋ฏธ ๋กœ๋“œํ–ˆ์œผ๋ฉด ์Šคํ‚ต + if (dbLoaded) { + return; + } + + try { + console.log("[LocationSwapSelector] DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ๋กœ๋“œ ์‹œ์ž‘:", { dbTableName, dbKeyField, userId }); + + const response = await apiClient.post( + `/table-management/tables/${dbTableName}/data`, + { + page: 1, + size: 1, + search: { [dbKeyField]: userId }, + autoFilter: true, + } + ); + + const vehicleData = response.data?.data?.data?.[0] || response.data?.data?.rows?.[0]; + + if (vehicleData) { + const dbDeparture = vehicleData[departureField] || vehicleData.departure; + const dbDestination = vehicleData[destinationField] || vehicleData.arrival || vehicleData.destination; + + console.log("[LocationSwapSelector] DB์—์„œ ๋กœ๋“œ๋œ ๊ฐ’:", { dbDeparture, dbDestination }); + + // DB์— ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋กœ์ปฌ ์ƒํƒœ ๋ฐ formData ์—…๋ฐ์ดํŠธ + if (dbDeparture && options.some(o => o.value === dbDeparture)) { + setLocalDeparture(dbDeparture); + onFormDataChange?.(departureField, dbDeparture); + + // ๋ผ๋ฒจ๋„ ์—…๋ฐ์ดํŠธ + if (departureLabelField) { + const opt = options.find(o => o.value === dbDeparture); + if (opt) { + onFormDataChange?.(departureLabelField, opt.label); + } + } + } + + if (dbDestination && options.some(o => o.value === dbDestination)) { + setLocalDestination(dbDestination); + onFormDataChange?.(destinationField, dbDestination); + + // ๋ผ๋ฒจ๋„ ์—…๋ฐ์ดํŠธ + if (destinationLabelField) { + const opt = options.find(o => o.value === dbDestination); + if (opt) { + onFormDataChange?.(destinationLabelField, opt.label); + } + } + } + } + + setDbLoaded(true); + } catch (error) { + console.error("[LocationSwapSelector] DB ๋กœ๋“œ ์‹คํŒจ:", error); + setDbLoaded(true); // ์‹คํŒจํ•ด๋„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์ง€ ์•Š์Œ + } + }; + + // ์˜ต์…˜์ด ๋กœ๋“œ๋œ ํ›„์— DB ๋กœ๋“œ ์‹คํ–‰ + if (options.length > 0) { + loadFromDatabase(); + } + }, [userId, loadFromDb, dbTableName, dbKeyField, departureField, destinationField, options, isDesignMode, dbLoaded, onFormDataChange, departureLabelField, destinationLabelField]); + + // formData์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋™๊ธฐํ™” (DB ๋กœ๋“œ ํ›„์—๋„ formData ๋ณ€๊ฒฝ ์‹œ ๋ฐ˜์˜) + useEffect(() => { + // DB ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ์Šคํ‚ต (DB ๊ฐ’ ์šฐ์„ ) + if (loadFromDb && userId && !dbLoaded) { + return; + } + const depVal = formData[departureField]; const destVal = formData[destinationField]; @@ -204,7 +299,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) if (destVal && options.some(o => o.value === destVal)) { setLocalDestination(destVal); } - }, [formData, departureField, destinationField, options]); + }, [formData, departureField, destinationField, options, loadFromDb, userId, dbLoaded]); // ์ถœ๋ฐœ์ง€ ๋ณ€๊ฒฝ const handleDepartureChange = (selectedValue: string) => { diff --git a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx index 4e21cddf..cd84806f 100644 --- a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx +++ b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx @@ -470,6 +470,58 @@ export function LocationSwapSelectorConfigPanel({ + {/* DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • */} +
+

DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ

+

+ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ DB์— ์ €์žฅ๋œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค +

+ +
+ + handleChange("loadFromDb", checked)} + /> +
+ + {config?.loadFromDb !== false && ( + <> +
+ + +
+ +
+ + handleChange("dbKeyField", e.target.value)} + placeholder="user_id" + /> +

+ ํ˜„์žฌ ์‚ฌ์šฉ์ž ID๋กœ ์กฐํšŒํ•  ํ•„๋“œ (๊ธฐ๋ณธ: user_id) +

+
+ + )} +
+ {/* ์•ˆ๋‚ด */}

@@ -480,6 +532,8 @@ export function LocationSwapSelectorConfigPanel({ 2. ์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€ ๊ฐ’์ด ์ €์žฅ๋  ํ•„๋“œ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค
3. ๊ตํ™˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ถœ๋ฐœ์ง€์™€ ๋„์ฐฉ์ง€๊ฐ€ ๋ฐ”๋€๋‹ˆ๋‹ค +
+ 4. DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ๊ฐ’์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค

diff --git a/frontend/lib/registry/components/split-panel-layout2/README.md b/frontend/lib/registry/components/split-panel-layout2/README.md index f1d8544b..4e5debe8 100644 --- a/frontend/lib/registry/components/split-panel-layout2/README.md +++ b/frontend/lib/registry/components/split-panel-layout2/README.md @@ -100,3 +100,4 @@ - [์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ ๊ฐ€์ด๋“œ](../../docs/์ปดํฌ๋„ŒํŠธ_์‹œ์Šคํ…œ_๊ฐ€์ด๋“œ.md) - [split-panel-layout (v1)](../split-panel-layout/README.md) + diff --git a/frontend/lib/registry/components/split-panel-layout2/SplitPanelLayout2Renderer.tsx b/frontend/lib/registry/components/split-panel-layout2/SplitPanelLayout2Renderer.tsx index f582646e..21e70b13 100644 --- a/frontend/lib/registry/components/split-panel-layout2/SplitPanelLayout2Renderer.tsx +++ b/frontend/lib/registry/components/split-panel-layout2/SplitPanelLayout2Renderer.tsx @@ -40,3 +40,4 @@ export class SplitPanelLayout2Renderer extends AutoRegisteringComponentRenderer // ์ž๋™ ๋“ฑ๋ก ์‹คํ–‰ SplitPanelLayout2Renderer.registerSelf(); + diff --git a/frontend/lib/registry/components/universal-form-modal/modals/FieldDetailSettingsModal.tsx b/frontend/lib/registry/components/universal-form-modal/modals/FieldDetailSettingsModal.tsx index 719a99e3..751ac2c6 100644 --- a/frontend/lib/registry/components/universal-form-modal/modals/FieldDetailSettingsModal.tsx +++ b/frontend/lib/registry/components/universal-form-modal/modals/FieldDetailSettingsModal.tsx @@ -840,3 +840,4 @@ export function FieldDetailSettingsModal({ ); } + diff --git a/frontend/lib/registry/components/universal-form-modal/modals/SaveSettingsModal.tsx b/frontend/lib/registry/components/universal-form-modal/modals/SaveSettingsModal.tsx index 9d269c62..27ee00ff 100644 --- a/frontend/lib/registry/components/universal-form-modal/modals/SaveSettingsModal.tsx +++ b/frontend/lib/registry/components/universal-form-modal/modals/SaveSettingsModal.tsx @@ -794,3 +794,4 @@ export function SaveSettingsModal({ ); } + diff --git a/frontend/lib/registry/components/universal-form-modal/modals/SectionLayoutModal.tsx b/frontend/lib/registry/components/universal-form-modal/modals/SectionLayoutModal.tsx index fe981260..dfdecbc0 100644 --- a/frontend/lib/registry/components/universal-form-modal/modals/SectionLayoutModal.tsx +++ b/frontend/lib/registry/components/universal-form-modal/modals/SectionLayoutModal.tsx @@ -514,3 +514,4 @@ export function SectionLayoutModal({ ); } + diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 86c3d90a..cb21ac5a 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -2292,6 +2292,8 @@ export class ButtonActionExecutor { editData: rowData, groupByColumns: groupByColumns.length > 0 ? groupByColumns : undefined, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ์ „๋‹ฌ tableName: context.tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… ์ „๋‹ฌ + buttonConfig: config, // ๐Ÿ†• ๋ฒ„ํŠผ ์„ค์ • ์ „๋‹ฌ (์ œ์–ด๋กœ์ง ์‹คํ–‰์šฉ) + buttonContext: context, // ๐Ÿ†• ๋ฒ„ํŠผ ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ (screenId, userId ๋“ฑ) onSave: () => { context.onRefresh?.(); }, @@ -2827,9 +2829,10 @@ export class ButtonActionExecutor { /** * ์ €์žฅ ํ›„ ์ œ์–ด ์‹คํ–‰ (After Timing) + * EditModal ๋“ฑ ์™ธ๋ถ€์—์„œ๋„ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•˜๋„๋ก public์œผ๋กœ ๋ณ€๊ฒฝ * ๋‹ค์ค‘ ์ œ์–ด ์ˆœ์ฐจ ์‹คํ–‰ ์ง€์› */ - private static async executeAfterSaveControl( + public static async executeAfterSaveControl( config: ButtonActionConfig, context: ButtonActionContext, ): Promise { @@ -4273,39 +4276,80 @@ export class ButtonActionExecutor { try { console.log("๐Ÿ›‘ [handleTrackingStop] ์œ„์น˜ ์ถ”์  ์ข…๋ฃŒ:", { config, context }); - // ์ถ”์  ์ค‘์ธ์ง€ ํ™•์ธ - if (!this.trackingIntervalId) { - toast.warning("์ง„ํ–‰ ์ค‘์ธ ์œ„์น˜ ์ถ”์ ์ด ์—†์Šต๋‹ˆ๋‹ค."); - return false; + // ์ถ”์  ์ค‘์ธ์ง€ ํ™•์ธ (์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ DB ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ข…๋ฃŒ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •) + const isTrackingActive = !!this.trackingIntervalId; + + if (!isTrackingActive) { + // ์ถ”์  ์ค‘์ด ์•„๋‹ˆ์–ด๋„ DB ์ƒํƒœ ๋ณ€๊ฒฝ์€ ์ง„ํ–‰ (์ƒˆ๋กœ๊ณ ์นจ ํ›„ ์ข…๋ฃŒ ์ง€์›) + console.log("โš ๏ธ [handleTrackingStop] trackingIntervalId ์—†์Œ - DB ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ข…๋ฃŒ ์ง„ํ–‰"); + } else { + // ํƒ€์ด๋จธ ์ •๋ฆฌ (์ถ”์  ์ค‘์ธ ๊ฒฝ์šฐ์—๋งŒ) + clearInterval(this.trackingIntervalId); + this.trackingIntervalId = null; } - // ํƒ€์ด๋จธ ์ •๋ฆฌ - clearInterval(this.trackingIntervalId); - this.trackingIntervalId = null; - const tripId = this.currentTripId; - // ๋งˆ์ง€๋ง‰ ์œ„์น˜ ์ €์žฅ (trip_status๋ฅผ completed๋กœ) - const departure = - this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || null; - const arrival = this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; - const departureName = this.trackingContext?.formData?.["departure_name"] || null; - const destinationName = this.trackingContext?.formData?.["destination_name"] || null; - const vehicleId = - this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null; + // ๐Ÿ†• DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์กฐํšŒ (์šด์ „์ž๊ฐ€ ์ค‘๊ฐ„์— ๋ฐ”๊ฟ”๋„ ์›๋ž˜ ๊ฐ’ ์‚ฌ์šฉ) + let dbDeparture: string | null = null; + let dbArrival: string | null = null; + let dbVehicleId: string | null = null; + + const userId = context.userId || this.trackingUserId; + if (userId) { + try { + const { apiClient } = await import("@/lib/api/client"); + const statusTableName = config.trackingStatusTableName || this.trackingConfig?.trackingStatusTableName || context.tableName || "vehicles"; + const keyField = config.trackingStatusKeyField || this.trackingConfig?.trackingStatusKeyField || "user_id"; + + // DB์—์„œ ํ˜„์žฌ ์ฐจ๋Ÿ‰ ์ •๋ณด ์กฐํšŒ + const vehicleResponse = await apiClient.post( + `/table-management/tables/${statusTableName}/data`, + { + page: 1, + size: 1, + search: { [keyField]: userId }, + autoFilter: true, + }, + ); + + const vehicleData = vehicleResponse.data?.data?.data?.[0] || vehicleResponse.data?.data?.rows?.[0]; + if (vehicleData) { + dbDeparture = vehicleData.departure || null; + dbArrival = vehicleData.arrival || null; + dbVehicleId = vehicleData.id || vehicleData.vehicle_id || null; + console.log("๐Ÿ“ [handleTrackingStop] DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์กฐํšŒ:", { dbDeparture, dbArrival, dbVehicleId }); + } + } catch (dbError) { + console.warn("โš ๏ธ [handleTrackingStop] DB ์กฐํšŒ ์‹คํŒจ, formData ์‚ฌ์šฉ:", dbError); + } + } - await this.saveLocationToHistory( - tripId, - departure, - arrival, - departureName, - destinationName, - vehicleId, - "completed", - ); + // ๋งˆ์ง€๋ง‰ ์œ„์น˜ ์ €์žฅ (์ถ”์  ์ค‘์ด์—ˆ๋˜ ๊ฒฝ์šฐ์—๋งŒ) + if (isTrackingActive) { + // DB ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด formData ์‚ฌ์šฉ + const departure = dbDeparture || + this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || null; + const arrival = dbArrival || + this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; + const departureName = this.trackingContext?.formData?.["departure_name"] || null; + const destinationName = this.trackingContext?.formData?.["destination_name"] || null; + const vehicleId = dbVehicleId || + this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null; - // ๐Ÿ†• ๊ฑฐ๋ฆฌ/์‹œ๊ฐ„ ๊ณ„์‚ฐ ๋ฐ ์ €์žฅ - if (tripId) { + await this.saveLocationToHistory( + tripId, + departure, + arrival, + departureName, + destinationName, + vehicleId, + "completed", + ); + } + + // ๐Ÿ†• ๊ฑฐ๋ฆฌ/์‹œ๊ฐ„ ๊ณ„์‚ฐ ๋ฐ ์ €์žฅ (์ถ”์  ์ค‘์ด์—ˆ๋˜ ๊ฒฝ์šฐ์—๋งŒ) + if (isTrackingActive && tripId) { try { const tripStats = await this.calculateTripStats(tripId); console.log("๐Ÿ“Š ์šดํ–‰ ํ†ต๊ณ„:", tripStats); @@ -4417,9 +4461,9 @@ export class ButtonActionExecutor { } } - // ์ƒํƒœ ๋ณ€๊ฒฝ (vehicles ํ…Œ์ด๋ธ” ๋“ฑ) - const effectiveConfig = config.trackingStatusOnStop ? config : this.trackingConfig; - const effectiveContext = context.userId ? context : this.trackingContext; + // ์ƒํƒœ ๋ณ€๊ฒฝ (vehicles ํ…Œ์ด๋ธ” ๋“ฑ) - ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ๋™์ž‘ํ•˜๋„๋ก config ์šฐ์„  ์‚ฌ์šฉ + const effectiveConfig = config.trackingStatusOnStop ? config : this.trackingConfig || config; + const effectiveContext = context.userId ? context : this.trackingContext || context; if (effectiveConfig?.trackingStatusOnStop && effectiveConfig?.trackingStatusField && effectiveContext) { try {