diff --git a/backend-node/src/controllers/entityJoinController.ts b/backend-node/src/controllers/entityJoinController.ts index ab9bbc46..4a541456 100644 --- a/backend-node/src/controllers/entityJoinController.ts +++ b/backend-node/src/controllers/entityJoinController.ts @@ -30,7 +30,6 @@ export class EntityJoinController { autoFilter, // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ž๋™ ํ•„ํ„ฐ dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ (JSON ๋ฌธ์ž์—ด) excludeFilter, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (JSON ๋ฌธ์ž์—ด) - ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ - deduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • (JSON ๋ฌธ์ž์—ด) userLang, // userLang์€ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ search์— ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ํ•จ ...otherParams } = req.query; @@ -50,9 +49,6 @@ export class EntityJoinController { // search๊ฐ€ ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ JSON ํŒŒ์‹ฑ searchConditions = typeof search === "string" ? JSON.parse(search) : search; - - // ๐Ÿ” ๋””๋ฒ„๊ทธ: ํŒŒ์‹ฑ๋œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ๋กœ๊น… - logger.info(`๐Ÿ” ํŒŒ์‹ฑ๋œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด:`, JSON.stringify(searchConditions, null, 2)); } catch (error) { logger.warn("๊ฒ€์ƒ‰ ์กฐ๊ฑด ํŒŒ์‹ฑ ์˜ค๋ฅ˜:", error); searchConditions = {}; @@ -155,24 +151,6 @@ export class EntityJoinController { } } - // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ฒ˜๋ฆฌ - let parsedDeduplication: { - enabled: boolean; - groupByColumn: string; - keepStrategy: "latest" | "earliest" | "base_price" | "current_date"; - sortColumn?: string; - } | undefined = undefined; - if (deduplication) { - try { - parsedDeduplication = - typeof deduplication === "string" ? JSON.parse(deduplication) : deduplication; - logger.info("์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ํŒŒ์‹ฑ ์™„๋ฃŒ:", parsedDeduplication); - } catch (error) { - logger.warn("์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ํŒŒ์‹ฑ ์˜ค๋ฅ˜:", error); - parsedDeduplication = undefined; - } - } - const result = await tableManagementService.getTableDataWithEntityJoins( tableName, { @@ -190,26 +168,13 @@ export class EntityJoinController { screenEntityConfigs: parsedScreenEntityConfigs, dataFilter: parsedDataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ excludeFilter: parsedExcludeFilter, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ „๋‹ฌ - deduplication: parsedDeduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ „๋‹ฌ } ); - // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์ฒ˜๋ฆฌ (๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ์— ์ ์šฉ) - let finalData = result; - if (parsedDeduplication?.enabled && parsedDeduplication.groupByColumn && Array.isArray(result.data)) { - logger.info(`๐Ÿ”„ ์ค‘๋ณต ์ œ๊ฑฐ ์‹œ์ž‘: ๊ธฐ์ค€ ์ปฌ๋Ÿผ = ${parsedDeduplication.groupByColumn}, ์ „๋žต = ${parsedDeduplication.keepStrategy}`); - const originalCount = result.data.length; - finalData = { - ...result, - data: this.deduplicateData(result.data, parsedDeduplication), - }; - logger.info(`โœ… ์ค‘๋ณต ์ œ๊ฑฐ ์™„๋ฃŒ: ${originalCount}๊ฐœ โ†’ ${finalData.data.length}๊ฐœ`); - } - res.status(200).json({ success: true, message: "Entity ์กฐ์ธ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต", - data: finalData, + data: result, }); } catch (error) { logger.error("Entity ์กฐ์ธ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ", error); @@ -584,98 +549,6 @@ export class EntityJoinController { }); } } - - /** - * ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ (๋ฉ”๋ชจ๋ฆฌ ๋‚ด ์ฒ˜๋ฆฌ) - */ - private deduplicateData( - data: any[], - config: { - groupByColumn: string; - keepStrategy: "latest" | "earliest" | "base_price" | "current_date"; - sortColumn?: string; - } - ): any[] { - if (!data || data.length === 0) return data; - - // ๊ทธ๋ฃน๋ณ„๋กœ ๋ฐ์ดํ„ฐ ๋ถ„๋ฅ˜ - const groups: Record = {}; - - for (const row of data) { - const groupKey = row[config.groupByColumn]; - if (groupKey === undefined || groupKey === null) continue; - - if (!groups[groupKey]) { - groups[groupKey] = []; - } - groups[groupKey].push(row); - } - - // ๊ฐ ๊ทธ๋ฃน์—์„œ ํ•˜๋‚˜์˜ ํ–‰๋งŒ ์„ ํƒ - const result: any[] = []; - - for (const [groupKey, rows] of Object.entries(groups)) { - if (rows.length === 0) continue; - - let selectedRow: any; - - switch (config.keepStrategy) { - case "latest": - // ์ •๋ ฌ ์ปฌ๋Ÿผ ๊ธฐ์ค€ ์ตœ์‹  (๊ฐ€์žฅ ํฐ ๊ฐ’) - if (config.sortColumn) { - rows.sort((a, b) => { - const aVal = a[config.sortColumn!]; - const bVal = b[config.sortColumn!]; - if (aVal === bVal) return 0; - if (aVal > bVal) return -1; - return 1; - }); - } - selectedRow = rows[0]; - break; - - case "earliest": - // ์ •๋ ฌ ์ปฌ๋Ÿผ ๊ธฐ์ค€ ์ตœ์ดˆ (๊ฐ€์žฅ ์ž‘์€ ๊ฐ’) - if (config.sortColumn) { - rows.sort((a, b) => { - const aVal = a[config.sortColumn!]; - const bVal = b[config.sortColumn!]; - if (aVal === bVal) return 0; - if (aVal < bVal) return -1; - return 1; - }); - } - selectedRow = rows[0]; - break; - - case "base_price": - // base_price๊ฐ€ true์ธ ํ–‰ ์„ ํƒ - selectedRow = rows.find((r) => r.base_price === true || r.base_price === "true") || rows[0]; - break; - - case "current_date": - // ์˜ค๋Š˜ ๋‚ ์งœ ๊ธฐ์ค€ ์œ ํšจ ๊ธฐ๊ฐ„ ๋‚ด ํ–‰ ์„ ํƒ - const today = new Date().toISOString().split("T")[0]; - selectedRow = rows.find((r) => { - const startDate = r.start_date; - const endDate = r.end_date; - if (!startDate) return true; - if (startDate <= today && (!endDate || endDate >= today)) return true; - return false; - }) || rows[0]; - break; - - default: - selectedRow = rows[0]; - } - - if (selectedRow) { - result.push(selectedRow); - } - } - - return result; - } } export const entityJoinController = new EntityJoinController(); diff --git a/backend-node/src/controllers/screenGroupController.ts b/backend-node/src/controllers/screenGroupController.ts index 2d7bc0e1..569fe793 100644 --- a/backend-node/src/controllers/screenGroupController.ts +++ b/backend-node/src/controllers/screenGroupController.ts @@ -1,23 +1,18 @@ import { Request, Response } from "express"; import { getPool } from "../database/db"; import { logger } from "../utils/logger"; -import { MultiLangService } from "../services/multilangService"; -import { AuthenticatedRequest } from "../types/auth"; // pool ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ const pool = getPool(); -// ๋‹ค๊ตญ์–ด ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค -const multiLangService = new MultiLangService(); - // ============================================================ // ํ™”๋ฉด ๊ทธ๋ฃน (screen_groups) CRUD // ============================================================ // ํ™”๋ฉด ๊ทธ๋ฃน ๋ชฉ๋ก ์กฐํšŒ -export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) => { +export const getScreenGroups = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { page = 1, size = 20, searchTerm } = req.query; const offset = (parseInt(page as string) - 1) * parseInt(size as string); @@ -89,10 +84,10 @@ export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ƒ์„ธ ์กฐํšŒ -export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) => { +export const getScreenGroup = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = ` SELECT sg.*, @@ -135,10 +130,10 @@ export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) = }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ƒ์„ฑ -export const createScreenGroup = async (req: AuthenticatedRequest, res: Response) => { +export const createScreenGroup = async (req: Request, res: Response) => { try { - const userCompanyCode = req.user!.companyCode; - const userId = req.user!.userId; + const userCompanyCode = (req.user as any).companyCode; + const userId = (req.user as any).userId; const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body; if (!group_name || !group_code) { @@ -196,47 +191,6 @@ export const createScreenGroup = async (req: AuthenticatedRequest, res: Response // ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ const updatedResult = await pool.query(`SELECT * FROM screen_groups WHERE id = $1`, [newGroupId]); - // ๋‹ค๊ตญ์–ด ์นดํ…Œ๊ณ ๋ฆฌ ์ž๋™ ์ƒ์„ฑ (๊ทธ๋ฃน ๊ฒฝ๋กœ ๊ธฐ๋ฐ˜) - try { - // ๊ทธ๋ฃน ๊ฒฝ๋กœ ์กฐํšŒ (์ƒ์œ„ ๊ทธ๋ฃน โ†’ ํ˜„์žฌ ๊ทธ๋ฃน) - const groupPathResult = await pool.query( - `WITH RECURSIVE group_path AS ( - SELECT id, parent_group_id, group_name, group_level, 1 as depth - FROM screen_groups - WHERE id = $1 - UNION ALL - SELECT g.id, g.parent_group_id, g.group_name, g.group_level, gp.depth + 1 - FROM screen_groups g - INNER JOIN group_path gp ON g.id = gp.parent_group_id - WHERE g.parent_group_id IS NOT NULL - ) - SELECT group_name FROM group_path - ORDER BY depth DESC`, - [newGroupId] - ); - - const groupPath = groupPathResult.rows.map((r: any) => r.group_name); - - // ํšŒ์‚ฌ ์ด๋ฆ„ ์กฐํšŒ - let companyName = "๊ณตํ†ต"; - if (finalCompanyCode !== "*") { - const companyResult = await pool.query( - `SELECT company_name FROM company_mng WHERE company_code = $1`, - [finalCompanyCode] - ); - if (companyResult.rows.length > 0) { - companyName = companyResult.rows[0].company_name; - } - } - - // ๋‹ค๊ตญ์–ด ์นดํ…Œ๊ณ ๋ฆฌ ์ƒ์„ฑ - await multiLangService.ensureScreenGroupCategory(finalCompanyCode, companyName, groupPath); - logger.info("ํ™”๋ฉด ๊ทธ๋ฃน ๋‹ค๊ตญ์–ด ์นดํ…Œ๊ณ ๋ฆฌ ์ž๋™ ์ƒ์„ฑ ์™„๋ฃŒ", { groupPath, companyCode: finalCompanyCode }); - } catch (multilangError: any) { - // ๋‹ค๊ตญ์–ด ์นดํ…Œ๊ณ ๋ฆฌ ์ƒ์„ฑ ์‹คํŒจํ•ด๋„ ๊ทธ๋ฃน ์ƒ์„ฑ์€ ์„ฑ๊ณต์œผ๋กœ ์ฒ˜๋ฆฌ - logger.warn("ํ™”๋ฉด ๊ทธ๋ฃน ๋‹ค๊ตญ์–ด ์นดํ…Œ๊ณ ๋ฆฌ ์ƒ์„ฑ ์‹คํŒจ (๋ฌด์‹œํ•˜๊ณ  ๊ณ„์†):", multilangError.message); - } - logger.info("ํ™”๋ฉด ๊ทธ๋ฃน ์ƒ์„ฑ", { userCompanyCode, finalCompanyCode, groupId: newGroupId, groupName: group_name, parentGroupId: parent_group_id }); res.json({ success: true, data: updatedResult.rows[0], message: "ํ™”๋ฉด ๊ทธ๋ฃน์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." }); @@ -250,10 +204,10 @@ export const createScreenGroup = async (req: AuthenticatedRequest, res: Response }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ˆ˜์ • -export const updateScreenGroup = async (req: AuthenticatedRequest, res: Response) => { +export const updateScreenGroup = async (req: Request, res: Response) => { try { const { id } = req.params; - const userCompanyCode = req.user!.companyCode; + const userCompanyCode = (req.user as any).companyCode; const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body; // ํšŒ์‚ฌ ์ฝ”๋“œ ๊ฒฐ์ •: ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํšŒ์‚ฌ๋ฅผ ์„ ํƒํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํšŒ์‚ฌ๋กœ, ์•„๋‹ˆ๋ฉด ํ˜„์žฌ ๊ทธ๋ฃน์˜ ํšŒ์‚ฌ ์œ ์ง€ @@ -339,10 +293,10 @@ export const updateScreenGroup = async (req: AuthenticatedRequest, res: Response }; // ํ™”๋ฉด ๊ทธ๋ฃน ์‚ญ์ œ -export const deleteScreenGroup = async (req: AuthenticatedRequest, res: Response) => { +export const deleteScreenGroup = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = `DELETE FROM screen_groups WHERE id = $1`; const params: any[] = [id]; @@ -375,10 +329,10 @@ export const deleteScreenGroup = async (req: AuthenticatedRequest, res: Response // ============================================================ // ๊ทธ๋ฃน์— ํ™”๋ฉด ์ถ”๊ฐ€ -export const addScreenToGroup = async (req: AuthenticatedRequest, res: Response) => { +export const addScreenToGroup = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; - const userId = req.user!.userId; + const companyCode = (req.user as any).companyCode; + const userId = (req.user as any).userId; const { group_id, screen_id, screen_role, display_order, is_default } = req.body; if (!group_id || !screen_id) { @@ -415,10 +369,10 @@ export const addScreenToGroup = async (req: AuthenticatedRequest, res: Response) }; // ๊ทธ๋ฃน์—์„œ ํ™”๋ฉด ์ œ๊ฑฐ -export const removeScreenFromGroup = async (req: AuthenticatedRequest, res: Response) => { +export const removeScreenFromGroup = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = `DELETE FROM screen_group_screens WHERE id = $1`; const params: any[] = [id]; @@ -446,10 +400,10 @@ export const removeScreenFromGroup = async (req: AuthenticatedRequest, res: Resp }; // ๊ทธ๋ฃน ๋‚ด ํ™”๋ฉด ์ˆœ์„œ/์—ญํ•  ์ˆ˜์ • -export const updateScreenInGroup = async (req: AuthenticatedRequest, res: Response) => { +export const updateScreenInGroup = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { screen_role, display_order, is_default } = req.body; let query = ` @@ -485,9 +439,9 @@ export const updateScreenInGroup = async (req: AuthenticatedRequest, res: Respon // ============================================================ // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ๋ชฉ๋ก ์กฐํšŒ -export const getFieldJoins = async (req: AuthenticatedRequest, res: Response) => { +export const getFieldJoins = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { screen_id } = req.query; let query = ` @@ -526,10 +480,10 @@ export const getFieldJoins = async (req: AuthenticatedRequest, res: Response) => }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์ƒ์„ฑ -export const createFieldJoin = async (req: AuthenticatedRequest, res: Response) => { +export const createFieldJoin = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; - const userId = req.user!.userId; + const companyCode = (req.user as any).companyCode; + const userId = (req.user as any).userId; const { screen_id, layout_id, component_id, field_name, save_table, save_column, join_table, join_column, display_column, @@ -567,10 +521,10 @@ export const createFieldJoin = async (req: AuthenticatedRequest, res: Response) }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์ˆ˜์ • -export const updateFieldJoin = async (req: AuthenticatedRequest, res: Response) => { +export const updateFieldJoin = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { layout_id, component_id, field_name, save_table, save_column, join_table, join_column, display_column, @@ -612,10 +566,10 @@ export const updateFieldJoin = async (req: AuthenticatedRequest, res: Response) }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์‚ญ์ œ -export const deleteFieldJoin = async (req: AuthenticatedRequest, res: Response) => { +export const deleteFieldJoin = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = `DELETE FROM screen_field_joins WHERE id = $1`; const params: any[] = [id]; @@ -646,9 +600,9 @@ export const deleteFieldJoin = async (req: AuthenticatedRequest, res: Response) // ============================================================ // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ชฉ๋ก ์กฐํšŒ -export const getDataFlows = async (req: AuthenticatedRequest, res: Response) => { +export const getDataFlows = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { group_id, source_screen_id } = req.query; let query = ` @@ -696,10 +650,10 @@ export const getDataFlows = async (req: AuthenticatedRequest, res: Response) => }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ƒ์„ฑ -export const createDataFlow = async (req: AuthenticatedRequest, res: Response) => { +export const createDataFlow = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; - const userId = req.user!.userId; + const companyCode = (req.user as any).companyCode; + const userId = (req.user as any).userId; const { group_id, source_screen_id, source_action, target_screen_id, target_action, data_mapping, flow_type, flow_label, condition_expression, is_active @@ -735,10 +689,10 @@ export const createDataFlow = async (req: AuthenticatedRequest, res: Response) = }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ˆ˜์ • -export const updateDataFlow = async (req: AuthenticatedRequest, res: Response) => { +export const updateDataFlow = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { group_id, source_screen_id, source_action, target_screen_id, target_action, data_mapping, flow_type, flow_label, condition_expression, is_active @@ -778,10 +732,10 @@ export const updateDataFlow = async (req: AuthenticatedRequest, res: Response) = }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์‚ญ์ œ -export const deleteDataFlow = async (req: AuthenticatedRequest, res: Response) => { +export const deleteDataFlow = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = `DELETE FROM screen_data_flows WHERE id = $1`; const params: any[] = [id]; @@ -812,9 +766,9 @@ export const deleteDataFlow = async (req: AuthenticatedRequest, res: Response) = // ============================================================ // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ -export const getTableRelations = async (req: AuthenticatedRequest, res: Response) => { +export const getTableRelations = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { screen_id, group_id } = req.query; let query = ` @@ -861,10 +815,10 @@ export const getTableRelations = async (req: AuthenticatedRequest, res: Response }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์ƒ์„ฑ -export const createTableRelation = async (req: AuthenticatedRequest, res: Response) => { +export const createTableRelation = async (req: Request, res: Response) => { try { - const companyCode = req.user!.companyCode; - const userId = req.user!.userId; + const companyCode = (req.user as any).companyCode; + const userId = (req.user as any).userId; const { group_id, screen_id, table_name, relation_type, crud_operations, description, is_active } = req.body; if (!screen_id || !table_name) { @@ -894,10 +848,10 @@ export const createTableRelation = async (req: AuthenticatedRequest, res: Respon }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์ˆ˜์ • -export const updateTableRelation = async (req: AuthenticatedRequest, res: Response) => { +export const updateTableRelation = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; const { group_id, table_name, relation_type, crud_operations, description, is_active } = req.body; let query = ` @@ -929,10 +883,10 @@ export const updateTableRelation = async (req: AuthenticatedRequest, res: Respon }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์‚ญ์ œ -export const deleteTableRelation = async (req: AuthenticatedRequest, res: Response) => { +export const deleteTableRelation = async (req: Request, res: Response) => { try { const { id } = req.params; - const companyCode = req.user!.companyCode; + const companyCode = (req.user as any).companyCode; let query = `DELETE FROM screen_table_relations WHERE id = $1`; const params: any[] = [id]; diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index e8c5a1bb..401fe9ce 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -804,12 +804,6 @@ export async function getTableData( } } - // ๐Ÿ†• ์ตœ์ข… ๊ฒ€์ƒ‰ ์กฐ๊ฑด ๋กœ๊ทธ - logger.info( - `๐Ÿ” ์ตœ์ข… ๊ฒ€์ƒ‰ ์กฐ๊ฑด (enhancedSearch):`, - JSON.stringify(enhancedSearch) - ); - // ๋ฐ์ดํ„ฐ ์กฐํšŒ const result = await tableManagementService.getTableData(tableName, { page: parseInt(page), @@ -893,10 +887,7 @@ export async function addTableData( const companyCode = req.user?.companyCode; if (companyCode && !data.company_code) { // ํ…Œ์ด๋ธ”์— company_code ์ปฌ๋Ÿผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ - const hasCompanyCodeColumn = await tableManagementService.hasColumn( - tableName, - "company_code" - ); + const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code"); if (hasCompanyCodeColumn) { data.company_code = companyCode; logger.info(`๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ์ž๋™ ์ถ”๊ฐ€ - ${companyCode}`); @@ -906,10 +897,7 @@ export async function addTableData( // ๐Ÿ†• writer ์ปฌ๋Ÿผ ์ž๋™ ์ถ”๊ฐ€ (ํ…Œ์ด๋ธ”์— writer ์ปฌ๋Ÿผ์ด ์žˆ๊ณ  ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ) const userId = req.user?.userId; if (userId && !data.writer) { - const hasWriterColumn = await tableManagementService.hasColumn( - tableName, - "writer" - ); + const hasWriterColumn = await tableManagementService.hasColumn(tableName, "writer"); if (hasWriterColumn) { data.writer = userId; logger.info(`writer ์ž๋™ ์ถ”๊ฐ€ - ${userId}`); @@ -917,25 +905,13 @@ export async function addTableData( } // ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ - const result = await tableManagementService.addTableData(tableName, data); + await tableManagementService.addTableData(tableName, data); logger.info(`ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ์™„๋ฃŒ: ${tableName}`); - // ๋ฌด์‹œ๋œ ์ปฌ๋Ÿผ์ด ์žˆ์œผ๋ฉด ๊ฒฝ๊ณ  ์ •๋ณด ํฌํ•จ - const response: ApiResponse<{ - skippedColumns?: string[]; - savedColumns?: string[]; - }> = { + const response: ApiResponse = { success: true, - message: - result.skippedColumns.length > 0 - ? `ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. (๋ฌด์‹œ๋œ ์ปฌ๋Ÿผ ${result.skippedColumns.length}๊ฐœ: ${result.skippedColumns.join(", ")})` - : "ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", - data: { - skippedColumns: - result.skippedColumns.length > 0 ? result.skippedColumns : undefined, - savedColumns: result.savedColumns, - }, + message: "ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", }; res.status(201).json(response); @@ -1663,10 +1639,10 @@ export async function toggleLogTable( /** * ๋ฉ”๋‰ด์˜ ์ƒ์œ„ ๋ฉ”๋‰ด๋“ค์ด ์„ค์ •ํ•œ ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ์กฐํšŒ (๊ณ„์ธต ๊ตฌ์กฐ ์ƒ์†) - * + * * @route GET /api/table-management/menu/:menuObjid/category-columns * @description ํ˜„์žฌ ๋ฉ”๋‰ด์™€ ์ƒ์œ„ ๋ฉ”๋‰ด๋“ค์—์„œ ์„ค์ •ํ•œ category_column_mapping์˜ ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ - * + * * ์˜ˆ์‹œ: * - 2๋ ˆ๋ฒจ ๋ฉ”๋‰ด "๊ณ ๊ฐ์‚ฌ๊ด€๋ฆฌ"์—์„œ discount_type, rounding_type ์„ค์ • * - 3๋ ˆ๋ฒจ ๋ฉ”๋‰ด "๊ณ ๊ฐ๋“ฑ๋ก", "๊ณ ๊ฐ์กฐํšŒ" ๋“ฑ์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ๋ณด์ž„ (์ƒ์†) @@ -1679,10 +1655,7 @@ export async function getCategoryColumnsByMenu( const { menuObjid } = req.params; const companyCode = req.user?.companyCode; - logger.info("๐Ÿ“ฅ ๋ฉ”๋‰ด๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์š”์ฒญ", { - menuObjid, - companyCode, - }); + logger.info("๐Ÿ“ฅ ๋ฉ”๋‰ด๋ณ„ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์š”์ฒญ", { menuObjid, companyCode }); if (!menuObjid) { res.status(400).json({ @@ -1708,11 +1681,8 @@ export async function getCategoryColumnsByMenu( if (mappingTableExists) { // ๐Ÿ†• category_column_mapping์„ ์‚ฌ์šฉํ•œ ๊ณ„์ธต ๊ตฌ์กฐ ๊ธฐ๋ฐ˜ ์กฐํšŒ - logger.info( - "๐Ÿ” category_column_mapping ๊ธฐ๋ฐ˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ (๊ณ„์ธต ๊ตฌ์กฐ ์ƒ์†)", - { menuObjid, companyCode } - ); - + logger.info("๐Ÿ” category_column_mapping ๊ธฐ๋ฐ˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ (๊ณ„์ธต ๊ตฌ์กฐ ์ƒ์†)", { menuObjid, companyCode }); + // ํ˜„์žฌ ๋ฉ”๋‰ด์™€ ๋ชจ๋“  ์ƒ์œ„ ๋ฉ”๋‰ด์˜ objid ์กฐํšŒ (์žฌ๊ท€) const ancestorMenuQuery = ` WITH RECURSIVE menu_hierarchy AS ( @@ -1734,21 +1704,17 @@ export async function getCategoryColumnsByMenu( ARRAY_AGG(menu_name_kor) as menu_names FROM menu_hierarchy `; - - const ancestorMenuResult = await pool.query(ancestorMenuQuery, [ - parseInt(menuObjid), - ]); - const ancestorMenuObjids = ancestorMenuResult.rows[0]?.menu_objids || [ - parseInt(menuObjid), - ]; + + const ancestorMenuResult = await pool.query(ancestorMenuQuery, [parseInt(menuObjid)]); + const ancestorMenuObjids = ancestorMenuResult.rows[0]?.menu_objids || [parseInt(menuObjid)]; const ancestorMenuNames = ancestorMenuResult.rows[0]?.menu_names || []; - - logger.info("โœ… ์ƒ์œ„ ๋ฉ”๋‰ด ๊ณ„์ธต ์กฐํšŒ ์™„๋ฃŒ", { - ancestorMenuObjids, + + logger.info("โœ… ์ƒ์œ„ ๋ฉ”๋‰ด ๊ณ„์ธต ์กฐํšŒ ์™„๋ฃŒ", { + ancestorMenuObjids, ancestorMenuNames, - hierarchyDepth: ancestorMenuObjids.length, + hierarchyDepth: ancestorMenuObjids.length }); - + // ์ƒ์œ„ ๋ฉ”๋‰ด๋“ค์— ์„ค์ •๋œ ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ (ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง ์ œ๊ฑฐ) const columnsQuery = ` SELECT DISTINCT @@ -1778,31 +1744,20 @@ export async function getCategoryColumnsByMenu( AND ttc.input_type = 'category' ORDER BY ttc.table_name, ccm.logical_column_name `; - - columnsResult = await pool.query(columnsQuery, [ - companyCode, - ancestorMenuObjids, - ]); - logger.info( - "โœ… category_column_mapping ๊ธฐ๋ฐ˜ ์กฐํšŒ ์™„๋ฃŒ (๊ณ„์ธต ๊ตฌ์กฐ ์ƒ์†)", - { - rowCount: columnsResult.rows.length, - columns: columnsResult.rows.map( - (r: any) => `${r.tableName}.${r.columnName}` - ), - } - ); + + columnsResult = await pool.query(columnsQuery, [companyCode, ancestorMenuObjids]); + logger.info("โœ… category_column_mapping ๊ธฐ๋ฐ˜ ์กฐํšŒ ์™„๋ฃŒ (๊ณ„์ธต ๊ตฌ์กฐ ์ƒ์†)", { + rowCount: columnsResult.rows.length, + columns: columnsResult.rows.map((r: any) => `${r.tableName}.${r.columnName}`) + }); } else { // ๐Ÿ”„ ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹: ํ˜•์ œ ๋ฉ”๋‰ด๋“ค์˜ ํ…Œ์ด๋ธ”์—์„œ ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ - logger.info("๐Ÿ” ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹: ํ˜•์ œ ๋ฉ”๋‰ด ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ", { - menuObjid, - companyCode, - }); - + logger.info("๐Ÿ” ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹: ํ˜•์ œ ๋ฉ”๋‰ด ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ", { menuObjid, companyCode }); + // ํ˜•์ œ ๋ฉ”๋‰ด ์กฐํšŒ const { getSiblingMenuObjids } = await import("../services/menuService"); const siblingObjids = await getSiblingMenuObjids(parseInt(menuObjid)); - + // ํ˜•์ œ ๋ฉ”๋‰ด๋“ค์ด ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ด๋ธ” ์กฐํšŒ const tablesQuery = ` SELECT DISTINCT sd.table_name @@ -1812,17 +1767,11 @@ export async function getCategoryColumnsByMenu( AND sma.company_code = $2 AND sd.table_name IS NOT NULL `; - - const tablesResult = await pool.query(tablesQuery, [ - siblingObjids, - companyCode, - ]); + + const tablesResult = await pool.query(tablesQuery, [siblingObjids, companyCode]); const tableNames = tablesResult.rows.map((row: any) => row.table_name); - - logger.info("โœ… ํ˜•์ œ ๋ฉ”๋‰ด ํ…Œ์ด๋ธ” ์กฐํšŒ ์™„๋ฃŒ", { - tableNames, - count: tableNames.length, - }); + + logger.info("โœ… ํ˜•์ œ ๋ฉ”๋‰ด ํ…Œ์ด๋ธ” ์กฐํšŒ ์™„๋ฃŒ", { tableNames, count: tableNames.length }); if (tableNames.length === 0) { res.json({ @@ -1832,7 +1781,7 @@ export async function getCategoryColumnsByMenu( }); return; } - + const columnsQuery = ` SELECT ttc.table_name AS "tableName", @@ -1857,15 +1806,13 @@ export async function getCategoryColumnsByMenu( AND ttc.input_type = 'category' ORDER BY ttc.table_name, ttc.column_name `; - + columnsResult = await pool.query(columnsQuery, [tableNames, companyCode]); - logger.info("โœ… ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹ ์กฐํšŒ ์™„๋ฃŒ", { - rowCount: columnsResult.rows.length, - }); + logger.info("โœ… ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹ ์กฐํšŒ ์™„๋ฃŒ", { rowCount: columnsResult.rows.length }); } - - logger.info("โœ… ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์™„๋ฃŒ", { - columnCount: columnsResult.rows.length, + + logger.info("โœ… ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์™„๋ฃŒ", { + columnCount: columnsResult.rows.length }); res.json({ @@ -1890,9 +1837,9 @@ export async function getCategoryColumnsByMenu( /** * ๋ฒ”์šฉ ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ API - * + * * ๋ฉ”์ธ ํ…Œ์ด๋ธ”๊ณผ ์„œ๋ธŒ ํ…Œ์ด๋ธ”(๋“ค)์— ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. - * + * * ์š”์ฒญ ๋ณธ๋ฌธ: * { * mainTable: { tableName: string, primaryKeyColumn: string }, @@ -1962,29 +1909,23 @@ export async function multiTableSave( } let mainResult: any; - + if (isUpdate && pkValue) { // UPDATE const updateColumns = Object.keys(mainData) - .filter((col) => col !== pkColumn) + .filter(col => col !== pkColumn) .map((col, idx) => `"${col}" = $${idx + 1}`) .join(", "); const updateValues = Object.keys(mainData) - .filter((col) => col !== pkColumn) - .map((col) => mainData[col]); - + .filter(col => col !== pkColumn) + .map(col => mainData[col]); + // updated_at ์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ - const hasUpdatedAt = await client.query( - ` + const hasUpdatedAt = await client.query(` SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = 'updated_at' - `, - [mainTableName] - ); - const updatedAtClause = - hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 - ? ", updated_at = NOW()" - : ""; + `, [mainTableName]); + const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : ""; const updateQuery = ` UPDATE "${mainTableName}" @@ -1993,43 +1934,29 @@ export async function multiTableSave( ${companyCode !== "*" ? `AND company_code = $${updateValues.length + 2}` : ""} RETURNING * `; - - const updateParams = - companyCode !== "*" - ? [...updateValues, pkValue, companyCode] - : [...updateValues, pkValue]; - - logger.info("๋ฉ”์ธ ํ…Œ์ด๋ธ” UPDATE:", { - query: updateQuery, - paramsCount: updateParams.length, - }); + + const updateParams = companyCode !== "*" + ? [...updateValues, pkValue, companyCode] + : [...updateValues, pkValue]; + + logger.info("๋ฉ”์ธ ํ…Œ์ด๋ธ” UPDATE:", { query: updateQuery, paramsCount: updateParams.length }); mainResult = await client.query(updateQuery, updateParams); } else { // INSERT - const columns = Object.keys(mainData) - .map((col) => `"${col}"`) - .join(", "); - const placeholders = Object.keys(mainData) - .map((_, idx) => `$${idx + 1}`) - .join(", "); + const columns = Object.keys(mainData).map(col => `"${col}"`).join(", "); + const placeholders = Object.keys(mainData).map((_, idx) => `$${idx + 1}`).join(", "); const values = Object.values(mainData); // updated_at ์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ - const hasUpdatedAt = await client.query( - ` + const hasUpdatedAt = await client.query(` SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = 'updated_at' - `, - [mainTableName] - ); - const updatedAtClause = - hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 - ? ", updated_at = NOW()" - : ""; + `, [mainTableName]); + const updatedAtClause = hasUpdatedAt.rowCount && hasUpdatedAt.rowCount > 0 ? ", updated_at = NOW()" : ""; const updateSetClause = Object.keys(mainData) - .filter((col) => col !== pkColumn) - .map((col) => `"${col}" = EXCLUDED."${col}"`) + .filter(col => col !== pkColumn) + .map(col => `"${col}" = EXCLUDED."${col}"`) .join(", "); const insertQuery = ` @@ -2040,10 +1967,7 @@ export async function multiTableSave( RETURNING * `; - logger.info("๋ฉ”์ธ ํ…Œ์ด๋ธ” INSERT/UPSERT:", { - query: insertQuery, - paramsCount: values.length, - }); + logger.info("๋ฉ”์ธ ํ…Œ์ด๋ธ” INSERT/UPSERT:", { query: insertQuery, paramsCount: values.length }); mainResult = await client.query(insertQuery, values); } @@ -2062,15 +1986,12 @@ export async function multiTableSave( const { tableName, linkColumn, items, options } = subTableConfig; // saveMainAsFirst๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, items๊ฐ€ ๋น„์–ด์žˆ์–ด๋„ ๋ฉ”์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ธŒ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•ด์•ผ ํ•จ - const hasSaveMainAsFirst = - options?.saveMainAsFirst && - options?.mainFieldMappings && - options.mainFieldMappings.length > 0; - + const hasSaveMainAsFirst = options?.saveMainAsFirst && + options?.mainFieldMappings && + options.mainFieldMappings.length > 0; + if (!tableName || (!items?.length && !hasSaveMainAsFirst)) { - logger.info( - `์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์Šคํ‚ต: ๋ฐ์ดํ„ฐ ์—†์Œ (saveMainAsFirst: ${hasSaveMainAsFirst})` - ); + logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์Šคํ‚ต: ๋ฐ์ดํ„ฐ ์—†์Œ (saveMainAsFirst: ${hasSaveMainAsFirst})`); continue; } @@ -2083,20 +2004,15 @@ export async function multiTableSave( // ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ ์˜ต์…˜ if (options?.deleteExistingBefore && linkColumn?.subColumn) { - const deleteQuery = - options?.deleteOnlySubItems && options?.mainMarkerColumn - ? `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1 AND "${options.mainMarkerColumn}" = $2` - : `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1`; + const deleteQuery = options?.deleteOnlySubItems && options?.mainMarkerColumn + ? `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1 AND "${options.mainMarkerColumn}" = $2` + : `DELETE FROM "${tableName}" WHERE "${linkColumn.subColumn}" = $1`; + + const deleteParams = options?.deleteOnlySubItems && options?.mainMarkerColumn + ? [savedPkValue, options.subMarkerValue ?? false] + : [savedPkValue]; - const deleteParams = - options?.deleteOnlySubItems && options?.mainMarkerColumn - ? [savedPkValue, options.subMarkerValue ?? false] - : [savedPkValue]; - - logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ:`, { - deleteQuery, - deleteParams, - }); + logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ:`, { deleteQuery, deleteParams }); await client.query(deleteQuery, deleteParams); } @@ -2109,12 +2025,7 @@ export async function multiTableSave( linkColumn, mainDataKeys: Object.keys(mainData), }); - if ( - options?.saveMainAsFirst && - options?.mainFieldMappings && - options.mainFieldMappings.length > 0 && - linkColumn?.subColumn - ) { + if (options?.saveMainAsFirst && options?.mainFieldMappings && options.mainFieldMappings.length > 0 && linkColumn?.subColumn) { const mainSubItem: Record = { [linkColumn.subColumn]: savedPkValue, }; @@ -2128,8 +2039,7 @@ export async function multiTableSave( // ๋ฉ”์ธ ๋งˆ์ปค ์„ค์ • if (options.mainMarkerColumn) { - mainSubItem[options.mainMarkerColumn] = - options.mainMarkerValue ?? true; + mainSubItem[options.mainMarkerColumn] = options.mainMarkerValue ?? true; } // company_code ์ถ”๊ฐ€ @@ -2152,30 +2062,20 @@ export async function multiTableSave( if (companyCode !== "*") { checkParams.push(companyCode); } - + const existingResult = await client.query(checkQuery, checkParams); - + if (existingResult.rows.length > 0) { // UPDATE const updateColumns = Object.keys(mainSubItem) - .filter( - (col) => - col !== linkColumn.subColumn && - col !== options.mainMarkerColumn && - col !== "company_code" - ) + .filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code") .map((col, idx) => `"${col}" = $${idx + 1}`) .join(", "); - + const updateValues = Object.keys(mainSubItem) - .filter( - (col) => - col !== linkColumn.subColumn && - col !== options.mainMarkerColumn && - col !== "company_code" - ) - .map((col) => mainSubItem[col]); - + .filter(col => col !== linkColumn.subColumn && col !== options.mainMarkerColumn && col !== "company_code") + .map(col => mainSubItem[col]); + if (updateColumns) { const updateQuery = ` UPDATE "${tableName}" @@ -2194,26 +2094,14 @@ export async function multiTableSave( } const updateResult = await client.query(updateQuery, updateParams); - subTableResults.push({ - tableName, - type: "main", - data: updateResult.rows[0], - }); + subTableResults.push({ tableName, type: "main", data: updateResult.rows[0] }); } else { - subTableResults.push({ - tableName, - type: "main", - data: existingResult.rows[0], - }); + subTableResults.push({ tableName, type: "main", data: existingResult.rows[0] }); } } else { // INSERT - const mainSubColumns = Object.keys(mainSubItem) - .map((col) => `"${col}"`) - .join(", "); - const mainSubPlaceholders = Object.keys(mainSubItem) - .map((_, idx) => `$${idx + 1}`) - .join(", "); + const mainSubColumns = Object.keys(mainSubItem).map(col => `"${col}"`).join(", "); + const mainSubPlaceholders = Object.keys(mainSubItem).map((_, idx) => `$${idx + 1}`).join(", "); const mainSubValues = Object.values(mainSubItem); const insertQuery = ` @@ -2223,11 +2111,7 @@ export async function multiTableSave( `; const insertResult = await client.query(insertQuery, mainSubValues); - subTableResults.push({ - tableName, - type: "main", - data: insertResult.rows[0], - }); + subTableResults.push({ tableName, type: "main", data: insertResult.rows[0] }); } } @@ -2243,12 +2127,8 @@ export async function multiTableSave( item.company_code = companyCode; } - const subColumns = Object.keys(item) - .map((col) => `"${col}"`) - .join(", "); - const subPlaceholders = Object.keys(item) - .map((_, idx) => `$${idx + 1}`) - .join(", "); + const subColumns = Object.keys(item).map(col => `"${col}"`).join(", "); + const subPlaceholders = Object.keys(item).map((_, idx) => `$${idx + 1}`).join(", "); const subValues = Object.values(item); const subInsertQuery = ` @@ -2257,16 +2137,9 @@ export async function multiTableSave( RETURNING * `; - logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์•„์ดํ…œ ์ €์žฅ:`, { - subInsertQuery, - subValuesCount: subValues.length, - }); + logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์•„์ดํ…œ ์ €์žฅ:`, { subInsertQuery, subValuesCount: subValues.length }); const subResult = await client.query(subInsertQuery, subValues); - subTableResults.push({ - tableName, - type: "sub", - data: subResult.rows[0], - }); + subTableResults.push({ tableName, type: "sub", data: subResult.rows[0] }); } logger.info(`์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์ €์žฅ ์™„๋ฃŒ`); @@ -2307,11 +2180,8 @@ export async function multiTableSave( } /** - * ๋‘ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์ž๋™ ๊ฐ์ง€ - * GET /api/table-management/tables/entity-relations?leftTable=xxx&rightTable=yyy - * - * column_labels์—์„œ ์ •์˜๋œ ์—”ํ‹ฐํ‹ฐ/์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์„ค์ •์„ ๊ธฐ๋ฐ˜์œผ๋กœ - * ๋‘ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. + * ๋‘ ํ…Œ์ด๋ธ” ๊ฐ„ ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ + * column_labels์˜ entity/category ํƒ€์ž… ์„ค์ •์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‘ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์กฐํšŒ */ export async function getTableEntityRelations( req: AuthenticatedRequest, @@ -2320,54 +2190,93 @@ export async function getTableEntityRelations( try { const { leftTable, rightTable } = req.query; - logger.info( - `=== ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์‹œ์ž‘: ${leftTable} <-> ${rightTable} ===` - ); - if (!leftTable || !rightTable) { - const response: ApiResponse = { + res.status(400).json({ success: false, message: "leftTable๊ณผ rightTable ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", - error: { - code: "MISSING_PARAMETERS", - details: "leftTable๊ณผ rightTable ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", - }, - }; - res.status(400).json(response); + }); return; } - const tableManagementService = new TableManagementService(); - const relations = await tableManagementService.detectTableEntityRelations( - String(leftTable), - String(rightTable) - ); + logger.info("=== ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ===", { leftTable, rightTable }); - logger.info(`ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์™„๋ฃŒ: ${relations.length}๊ฐœ ๋ฐœ๊ฒฌ`); + // ๋‘ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด ์กฐํšŒ + const columnLabelsQuery = ` + SELECT + table_name, + column_name, + column_label, + web_type, + detail_settings + FROM column_labels + WHERE table_name IN ($1, $2) + AND web_type IN ('entity', 'category') + `; - const response: ApiResponse = { + const result = await query(columnLabelsQuery, [leftTable, rightTable]); + + // ๊ด€๊ณ„ ๋ถ„์„ + const relations: Array<{ + fromTable: string; + fromColumn: string; + toTable: string; + toColumn: string; + relationType: string; + }> = []; + + for (const row of result) { + try { + const detailSettings = typeof row.detail_settings === "string" + ? JSON.parse(row.detail_settings) + : row.detail_settings; + + if (detailSettings && detailSettings.referenceTable) { + const refTable = detailSettings.referenceTable; + const refColumn = detailSettings.referenceColumn || "id"; + + // leftTable๊ณผ rightTable ๊ฐ„์˜ ๊ด€๊ณ„์ธ์ง€ ํ™•์ธ + if ( + (row.table_name === leftTable && refTable === rightTable) || + (row.table_name === rightTable && refTable === leftTable) + ) { + relations.push({ + fromTable: row.table_name, + fromColumn: row.column_name, + toTable: refTable, + toColumn: refColumn, + relationType: row.web_type, + }); + } + } + } catch (parseError) { + logger.warn("detail_settings ํŒŒ์‹ฑ ์˜ค๋ฅ˜:", { + table: row.table_name, + column: row.column_name, + error: parseError + }); + } + } + + logger.info("ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์™„๋ฃŒ", { + leftTable, + rightTable, + relationsCount: relations.length + }); + + res.json({ success: true, - message: `${relations.length}๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.`, data: { - leftTable: String(leftTable), - rightTable: String(rightTable), + leftTable, + rightTable, relations, }, - }; - - res.status(200).json(response); - } catch (error) { - logger.error("ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:", error); - - const response: ApiResponse = { + }); + } catch (error: any) { + logger.error("ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:", error); + res.status(500).json({ success: false, - message: "ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", - error: { - code: "ENTITY_RELATIONS_ERROR", - details: error instanceof Error ? error.message : "Unknown error", - }, - }; - - res.status(500).json(response); + message: "ํ…Œ์ด๋ธ” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์กฐํšŒ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", + error: error.message, + }); } } diff --git a/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx b/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx index 4e2878eb..106870eb 100644 --- a/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx +++ b/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx @@ -241,5 +241,3 @@ export default function ScreenManagementPage() { ); } - - diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 4923ded7..b61d5dae 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -23,7 +23,6 @@ import { TableSearchWidgetHeightProvider, useTableSearchWidgetHeight } from "@/c import { ScreenContextProvider } from "@/contexts/ScreenContext"; // ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ํ†ต์‹  import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; // ๋ถ„ํ•  ํŒจ๋„ ๋ฆฌ์‚ฌ์ด์ฆˆ import { ActiveTabProvider } from "@/contexts/ActiveTabContext"; // ํ™œ์„ฑ ํƒญ ๊ด€๋ฆฌ -import { ScreenMultiLangProvider } from "@/contexts/ScreenMultiLangContext"; // ํ™”๋ฉด ๋‹ค๊ตญ์–ด function ScreenViewPage() { const params = useParams(); @@ -114,7 +113,7 @@ function ScreenViewPage() { // ํŽธ์ง‘ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { - // console.log("๐ŸŽญ ํŽธ์ง‘ ๋ชจ๋‹ฌ ์—ด๊ธฐ ์ด๋ฒคํŠธ ์ˆ˜์‹ :", event.detail); + console.log("๐ŸŽญ ํŽธ์ง‘ ๋ชจ๋‹ฌ ์—ด๊ธฐ ์ด๋ฒคํŠธ ์ˆ˜์‹ :", event.detail); setEditModalConfig({ screenId: event.detail.screenId, @@ -346,10 +345,9 @@ function ScreenViewPage() { {/* ์ ˆ๋Œ€ ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง (ํ™”๋ฉด๊ด€๋ฆฌ์™€ ๋™์ผํ•œ ๋ฐฉ์‹) */} {layoutReady && layout && layout.components.length > 0 ? ( - -
); })()} -
-
+ ) : ( // ๋นˆ ํ™”๋ฉด์ผ ๋•Œ
diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 1614c9b8..a252eaff 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -388,237 +388,6 @@ select { border-spacing: 0 !important; } -/* ===== POP (Production Operation Panel) Styles ===== */ - -/* POP ์ „์šฉ ๋‹คํฌ ํ…Œ๋งˆ ๋ณ€์ˆ˜ */ -.pop-dark { - /* ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ */ - --pop-bg-deepest: 8 12 21; - --pop-bg-deep: 10 15 28; - --pop-bg-primary: 13 19 35; - --pop-bg-secondary: 18 26 47; - --pop-bg-tertiary: 25 35 60; - --pop-bg-elevated: 32 45 75; - - /* ๋„ค์˜จ ๊ฐ•์กฐ์ƒ‰ */ - --pop-neon-cyan: 0 212 255; - --pop-neon-cyan-bright: 0 240 255; - --pop-neon-cyan-dim: 0 150 190; - --pop-neon-pink: 255 0 102; - --pop-neon-purple: 138 43 226; - - /* ์ƒํƒœ ์ƒ‰์ƒ */ - --pop-success: 0 255 136; - --pop-success-dim: 0 180 100; - --pop-warning: 255 170 0; - --pop-warning-dim: 200 130 0; - --pop-danger: 255 51 51; - --pop-danger-dim: 200 40 40; - - /* ํ…์ŠคํŠธ ์ƒ‰์ƒ */ - --pop-text-primary: 255 255 255; - --pop-text-secondary: 180 195 220; - --pop-text-muted: 100 120 150; - - /* ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ */ - --pop-border: 40 55 85; - --pop-border-light: 55 75 110; -} - -/* POP ์ „์šฉ ๋ผ์ดํŠธ ํ…Œ๋งˆ ๋ณ€์ˆ˜ */ -.pop-light { - --pop-bg-deepest: 245 247 250; - --pop-bg-deep: 240 243 248; - --pop-bg-primary: 250 251 253; - --pop-bg-secondary: 255 255 255; - --pop-bg-tertiary: 245 247 250; - --pop-bg-elevated: 235 238 245; - - --pop-neon-cyan: 0 122 204; - --pop-neon-cyan-bright: 0 140 230; - --pop-neon-cyan-dim: 0 100 170; - --pop-neon-pink: 220 38 127; - --pop-neon-purple: 118 38 200; - - --pop-success: 22 163 74; - --pop-success-dim: 21 128 61; - --pop-warning: 245 158 11; - --pop-warning-dim: 217 119 6; - --pop-danger: 220 38 38; - --pop-danger-dim: 185 28 28; - - --pop-text-primary: 15 23 42; - --pop-text-secondary: 71 85 105; - --pop-text-muted: 148 163 184; - - --pop-border: 226 232 240; - --pop-border-light: 203 213 225; -} - -/* POP ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๋“œ ํŒจํ„ด */ -.pop-bg-pattern::before { - content: ""; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - repeating-linear-gradient(90deg, rgba(0, 212, 255, 0.03) 0px, transparent 1px, transparent 60px), - repeating-linear-gradient(0deg, rgba(0, 212, 255, 0.03) 0px, transparent 1px, transparent 60px), - radial-gradient(ellipse 80% 50% at 50% 0%, rgba(0, 212, 255, 0.08) 0%, transparent 60%); - pointer-events: none; - z-index: 0; -} - -.pop-light .pop-bg-pattern::before { - background: - repeating-linear-gradient(90deg, rgba(0, 122, 204, 0.02) 0px, transparent 1px, transparent 60px), - repeating-linear-gradient(0deg, rgba(0, 122, 204, 0.02) 0px, transparent 1px, transparent 60px), - radial-gradient(ellipse 80% 50% at 50% 0%, rgba(0, 122, 204, 0.05) 0%, transparent 60%); -} - -/* POP ๊ธ€๋กœ์šฐ ํšจ๊ณผ */ -.pop-glow-cyan { - box-shadow: - 0 0 20px rgba(0, 212, 255, 0.5), - 0 0 40px rgba(0, 212, 255, 0.3); -} - -.pop-glow-cyan-strong { - box-shadow: - 0 0 10px rgba(0, 212, 255, 0.8), - 0 0 30px rgba(0, 212, 255, 0.5), - 0 0 50px rgba(0, 212, 255, 0.3); -} - -.pop-glow-success { - box-shadow: 0 0 15px rgba(0, 255, 136, 0.5); -} - -.pop-glow-warning { - box-shadow: 0 0 15px rgba(255, 170, 0, 0.5); -} - -.pop-glow-danger { - box-shadow: 0 0 15px rgba(255, 51, 51, 0.5); -} - -/* POP ํŽ„์Šค ๊ธ€๋กœ์šฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ */ -@keyframes pop-pulse-glow { - 0%, - 100% { - box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); - } - 50% { - box-shadow: - 0 0 20px rgba(0, 212, 255, 0.8), - 0 0 30px rgba(0, 212, 255, 0.4); - } -} - -.pop-animate-pulse-glow { - animation: pop-pulse-glow 2s ease-in-out infinite; -} - -/* POP ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ์ƒค์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ */ -@keyframes pop-progress-shine { - 0% { - opacity: 0; - transform: translateX(-20px); - } - 50% { - opacity: 1; - } - 100% { - opacity: 0; - transform: translateX(20px); - } -} - -.pop-progress-shine::after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 20px; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3)); - animation: pop-progress-shine 1.5s ease-in-out infinite; -} - -/* POP ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ */ -.pop-scrollbar::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -.pop-scrollbar::-webkit-scrollbar-track { - background: rgb(var(--pop-bg-secondary)); -} - -.pop-scrollbar::-webkit-scrollbar-thumb { - background: rgb(var(--pop-border-light)); - border-radius: 9999px; -} - -.pop-scrollbar::-webkit-scrollbar-thumb:hover { - background: rgb(var(--pop-neon-cyan-dim)); -} - -/* POP ์Šคํฌ๋กค๋ฐ” ์ˆจ๊ธฐ๊ธฐ */ -.pop-hide-scrollbar::-webkit-scrollbar { - display: none; -} - -.pop-hide-scrollbar { - -ms-overflow-style: none; - scrollbar-width: none; -} - -/* ===== Marching Ants Animation (Excel Copy Border) ===== */ -@keyframes marching-ants-h { - 0% { - background-position: 0 0; - } - 100% { - background-position: 16px 0; - } -} - -@keyframes marching-ants-v { - 0% { - background-position: 0 0; - } - 100% { - background-position: 0 16px; - } -} - -.animate-marching-ants-h { - background: repeating-linear-gradient( - 90deg, - hsl(var(--primary)) 0, - hsl(var(--primary)) 4px, - transparent 4px, - transparent 8px - ); - background-size: 16px 2px; - animation: marching-ants-h 0.4s linear infinite; -} - -.animate-marching-ants-v { - background: repeating-linear-gradient( - 180deg, - hsl(var(--primary)) 0, - hsl(var(--primary)) 4px, - transparent 4px, - transparent 8px - ); - background-size: 2px 16px; - animation: marching-ants-v 0.4s linear infinite; -} - /* ===== ์ €์žฅ ํ…Œ์ด๋ธ” ๋ง‰๋Œ€๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ===== */ @keyframes saveBarDrop { 0% { diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index 8445f5e1..af6c9dbc 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useCallback, useEffect, useMemo } from "react"; +import React, { useState, useCallback } from "react"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; @@ -48,7 +48,6 @@ import { isFileComponent } from "@/lib/utils/componentTypeUtils"; import { buildGridClasses } from "@/lib/constants/columnSpans"; import { cn } from "@/lib/utils"; import { useScreenPreview } from "@/contexts/ScreenPreviewContext"; -import { useMultiLang } from "@/hooks/useMultiLang"; import { TableOptionsProvider } from "@/contexts/TableOptionsContext"; import { TableOptionsToolbar } from "./table-options/TableOptionsToolbar"; import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; @@ -89,7 +88,7 @@ const CascadingDropdownWrapper: React.FC = ({ relationCode, parentValue, }); - + // ์‹ค์ œ ์‚ฌ์šฉํ•  ์„ค์ • (์ง์ ‘ ์„ค์ • ๋˜๋Š” API์—์„œ ๊ฐ€์ ธ์˜จ ์„ค์ •) const effectiveConfig = config || relationConfig; @@ -110,7 +109,11 @@ const CascadingDropdownWrapper: React.FC = ({ const isDisabled = disabled || !parentValue || loading; return ( - onChange?.(newValue)} + disabled={isDisabled} + > {loading ? (
@@ -184,80 +187,22 @@ export const InteractiveScreenViewer: React.FC = ( const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ const { userName, user } = useAuth(); // ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ช…๊ณผ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ - const { userLang } = useMultiLang(); // ๋‹ค๊ตญ์–ด ํ›… const [localFormData, setLocalFormData] = useState>({}); const [dateValues, setDateValues] = useState>({}); - - // ๋‹ค๊ตญ์–ด ๋ฒˆ์—ญ ์ƒํƒœ (langKeyId๊ฐ€ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๋ฒˆ์—ญ ํ…์ŠคํŠธ) - const [translations, setTranslations] = useState>({}); - - // ๋‹ค๊ตญ์–ด ํ‚ค ์ˆ˜์ง‘ ๋ฐ ๋ฒˆ์—ญ ๋กœ๋“œ - useEffect(() => { - const loadTranslations = async () => { - // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ langKey ์ˆ˜์ง‘ - const langKeysToFetch: string[] = []; - - const collectLangKeys = (comps: ComponentData[]) => { - comps.forEach((comp) => { - // ์ปดํฌ๋„ŒํŠธ ๋ผ๋ฒจ์˜ langKey - if ((comp as any).langKey) { - langKeysToFetch.push((comp as any).langKey); - } - // componentConfig ๋‚ด์˜ langKey (๋ฒ„ํŠผ ํ…์ŠคํŠธ ๋“ฑ) - if ((comp as any).componentConfig?.langKey) { - langKeysToFetch.push((comp as any).componentConfig.langKey); - } - // ์ž์‹ ์ปดํฌ๋„ŒํŠธ ์žฌ๊ท€ ์ฒ˜๋ฆฌ - if ((comp as any).children) { - collectLangKeys((comp as any).children); - } - }); - }; - - collectLangKeys(allComponents); - - // langKey๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐฐ์น˜ ์กฐํšŒ - if (langKeysToFetch.length > 0 && userLang) { - try { - const { apiClient } = await import("@/lib/api/client"); - const response = await apiClient.post( - "/multilang/batch", - { - langKeys: [...new Set(langKeysToFetch)], // ์ค‘๋ณต ์ œ๊ฑฐ - }, - { - params: { - userLang, - companyCode: user?.companyCode || "*", - }, - }, - ); - - if (response.data?.success && response.data?.data) { - setTranslations(response.data.data); - } - } catch (error) { - console.error("๋‹ค๊ตญ์–ด ๋ฒˆ์—ญ ๋กœ๋“œ ์‹คํŒจ:", error); - } - } - }; - - loadTranslations(); - }, [allComponents, userLang, user?.companyCode]); - + // ํŒ์—… ํ™”๋ฉด ์ƒํƒœ const [popupScreen, setPopupScreen] = useState<{ screenId: number; title: string; size: string; } | null>(null); - + // ํŒ์—… ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์ƒํƒœ const [popupLayout, setPopupLayout] = useState([]); const [popupLoading, setPopupLoading] = useState(false); const [popupScreenResolution, setPopupScreenResolution] = useState<{ width: number; height: number } | null>(null); const [popupScreenInfo, setPopupScreenInfo] = useState<{ id: number; tableName?: string } | null>(null); - + // ํŒ์—… ์ „์šฉ formData ์ƒํƒœ const [popupFormData, setPopupFormData] = useState>({}); @@ -265,68 +210,64 @@ export const InteractiveScreenViewer: React.FC = ( const finalFormData = { ...localFormData, ...externalFormData }; // ๊ฐœ์„ ๋œ ๊ฒ€์ฆ ์‹œ์Šคํ…œ (์„ ํƒ์  ํ™œ์„ฑํ™”) - const enhancedValidation = - enableEnhancedValidation && screenInfo && tableColumns.length > 0 - ? useFormValidation( - finalFormData, - allComponents.filter((c) => c.type === "widget") as WidgetComponent[], - tableColumns, - { - id: screenInfo.id, - screenName: screenInfo.tableName || "unknown", - tableName: screenInfo.tableName, - screenResolution: { width: 800, height: 600 }, - gridSettings: { size: 20, color: "#e0e0e0", opacity: 0.5 }, - description: "๋™์  ํ™”๋ฉด", - }, - { - enableRealTimeValidation: true, - validationDelay: 300, - enableAutoSave: false, - showToastMessages: true, - ...validationOptions, - }, - ) - : null; + const enhancedValidation = enableEnhancedValidation && screenInfo && tableColumns.length > 0 + ? useFormValidation( + finalFormData, + allComponents.filter(c => c.type === 'widget') as WidgetComponent[], + tableColumns, + { + id: screenInfo.id, + screenName: screenInfo.tableName || "unknown", + tableName: screenInfo.tableName, + screenResolution: { width: 800, height: 600 }, + gridSettings: { size: 20, color: "#e0e0e0", opacity: 0.5 }, + description: "๋™์  ํ™”๋ฉด" + }, + { + enableRealTimeValidation: true, + validationDelay: 300, + enableAutoSave: false, + showToastMessages: true, + ...validationOptions, + } + ) + : null; // ์ž๋™๊ฐ’ ์ƒ์„ฑ ํ•จ์ˆ˜ - const generateAutoValue = useCallback( - async (autoValueType: string, ruleId?: string): Promise => { - const now = new Date(); - switch (autoValueType) { - case "current_datetime": - return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss - case "current_date": - return now.toISOString().slice(0, 10); // YYYY-MM-DD - case "current_time": - return now.toTimeString().slice(0, 8); // HH:mm:ss - case "current_user": - // ์‹ค์ œ ์ ‘์†์ค‘์ธ ์‚ฌ์šฉ์ž๋ช… ์‚ฌ์šฉ - return userName || "์‚ฌ์šฉ์ž"; // ์‚ฌ์šฉ์ž๋ช…์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ - case "uuid": - return crypto.randomUUID(); - case "sequence": - return `SEQ_${Date.now()}`; - case "numbering_rule": - // ์ฑ„๋ฒˆ ๊ทœ์น™ ์‚ฌ์šฉ - if (ruleId) { - try { - const { generateNumberingCode } = await import("@/lib/api/numberingRule"); - const response = await generateNumberingCode(ruleId); - if (response.success && response.data) { - return response.data.generatedCode; - } - } catch (error) { - console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ:", error); + const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise => { + const now = new Date(); + switch (autoValueType) { + case "current_datetime": + return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss + case "current_date": + return now.toISOString().slice(0, 10); // YYYY-MM-DD + case "current_time": + return now.toTimeString().slice(0, 8); // HH:mm:ss + case "current_user": + // ์‹ค์ œ ์ ‘์†์ค‘์ธ ์‚ฌ์šฉ์ž๋ช… ์‚ฌ์šฉ + return userName || "์‚ฌ์šฉ์ž"; // ์‚ฌ์šฉ์ž๋ช…์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ + case "uuid": + return crypto.randomUUID(); + case "sequence": + return `SEQ_${Date.now()}`; + case "numbering_rule": + // ์ฑ„๋ฒˆ ๊ทœ์น™ ์‚ฌ์šฉ + if (ruleId) { + try { + const { generateNumberingCode } = await import("@/lib/api/numberingRule"); + const response = await generateNumberingCode(ruleId); + if (response.success && response.data) { + return response.data.generatedCode; } + } catch (error) { + console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ:", error); } - return ""; - default: - return ""; - } - }, - [userName], - ); // userName ์˜์กด์„ฑ ์ถ”๊ฐ€ + } + return ""; + default: + return ""; + } + }, [userName]); // userName ์˜์กด์„ฑ ์ถ”๊ฐ€ // ํŒ์—… ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋กœ๋“œ React.useEffect(() => { @@ -335,29 +276,29 @@ export const InteractiveScreenViewer: React.FC = ( try { setPopupLoading(true); // console.log("๐Ÿ” ํŒ์—… ํ™”๋ฉด ๋กœ๋“œ ์‹œ์ž‘:", popupScreen); - + // ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ๊ณผ ํ™”๋ฉด ์ •๋ณด๋ฅผ ๋ณ‘๋ ฌ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ const [layout, screen] = await Promise.all([ screenApi.getLayout(popupScreen.screenId), - screenApi.getScreen(popupScreen.screenId), + screenApi.getScreen(popupScreen.screenId) ]); - + console.log("๐Ÿ“Š ํŒ์—… ํ™”๋ฉด ๋กœ๋“œ ์™„๋ฃŒ:", { componentsCount: layout.components?.length || 0, screenInfo: { screenId: screen.screenId, - tableName: screen.tableName, + tableName: screen.tableName }, - popupFormData: {}, + popupFormData: {} }); - + setPopupLayout(layout.components || []); setPopupScreenResolution(layout.screenResolution || null); setPopupScreenInfo({ id: popupScreen.screenId, - tableName: screen.tableName, + tableName: screen.tableName }); - + // ํŒ์—… formData ์ดˆ๊ธฐํ™” setPopupFormData({}); } catch (error) { @@ -368,7 +309,7 @@ export const InteractiveScreenViewer: React.FC = ( setPopupLoading(false); } }; - + loadPopupLayout(); } }, [popupScreen]); @@ -379,7 +320,7 @@ export const InteractiveScreenViewer: React.FC = ( external: externalFormData, local: localFormData, merged: formData, - hasExternalCallback: !!onFormDataChange, + hasExternalCallback: !!onFormDataChange }); // ํผ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ @@ -388,16 +329,16 @@ export const InteractiveScreenViewer: React.FC = ( if (isPreviewMode) { return; } - + // console.log(`๐Ÿ”„ updateFormData: ${fieldName} = "${value}" (์™ธ๋ถ€์ฝœ๋ฐฑ: ${!!onFormDataChange})`); - + // ํ•ญ์ƒ ๋กœ์ปฌ ์ƒํƒœ๋„ ์—…๋ฐ์ดํŠธ setLocalFormData((prev) => ({ ...prev, [fieldName]: value, })); // console.log(`๐Ÿ’พ ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ: ${fieldName} = "${value}"`); - + // ์™ธ๋ถ€ ์ฝœ๋ฐฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋„ ์ „๋‹ฌ (๊ฐœ๋ณ„ ํ•„๋“œ ๋‹จ์œ„๋กœ) if (onFormDataChange) { onFormDataChange(fieldName, value); @@ -412,24 +353,29 @@ export const InteractiveScreenViewer: React.FC = ( // console.log("๐Ÿ”ง initAutoInputFields ์‹คํ–‰ ์‹œ์ž‘"); for (const comp of allComponents) { // ๐Ÿ†• type: "component" ๋˜๋Š” type: "widget" ๋ชจ๋‘ ์ฒ˜๋ฆฌ - if (comp.type === "widget" || comp.type === "component") { + if (comp.type === 'widget' || comp.type === 'component') { const widget = comp as WidgetComponent; const fieldName = widget.columnName || widget.id; - + // ๐Ÿ†• autoFill ์ฒ˜๋ฆฌ (ํ…Œ์ด๋ธ” ์กฐํšŒ ๊ธฐ๋ฐ˜ ์ž๋™ ์ž…๋ ฅ) if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { const autoFillConfig = widget.autoFill || (comp as any).autoFill; const currentValue = formData[fieldName]; - if (currentValue === undefined || currentValue === "") { + if (currentValue === undefined || currentValue === '') { const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; - + // ์‚ฌ์šฉ์ž ์ •๋ณด์—์„œ ํ•„ํ„ฐ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ const userValue = user?.[userField]; - + if (userValue && sourceTable && filterColumn && displayColumn) { try { - const result = await tableTypeApi.getTableRecord(sourceTable, filterColumn, userValue, displayColumn); - + const result = await tableTypeApi.getTableRecord( + sourceTable, + filterColumn, + userValue, + displayColumn + ); + updateFormData(fieldName, result.value); } catch (error) { console.error(`autoFill ์กฐํšŒ ์‹คํŒจ: ${fieldName}`, error); @@ -438,40 +384,37 @@ export const InteractiveScreenViewer: React.FC = ( } continue; // autoFill์ด ํ™œ์„ฑํ™”๋˜๋ฉด ์ผ๋ฐ˜ ์ž๋™์ž…๋ ฅ์€ ๊ฑด๋„ˆ๋œ€ } - + // ๊ธฐ์กด widget ํƒ€์ž… ์ „์šฉ ๋กœ์ง์€ widget์ธ ๊ฒฝ์šฐ๋งŒ - if (comp.type !== "widget") continue; - + if (comp.type !== 'widget') continue; + // ํ…์ŠคํŠธ ํƒ€์ž… ์œ„์ ฏ์˜ ์ž๋™์ž…๋ ฅ ์ฒ˜๋ฆฌ (๊ธฐ์กด ๋กœ์ง) - if ( - (widget.widgetType === "text" || widget.widgetType === "email" || widget.widgetType === "tel") && - widget.webTypeConfig - ) { + if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') && + widget.webTypeConfig) { const config = widget.webTypeConfig as TextTypeConfig; const isAutoInput = config?.autoInput || false; - + if (isAutoInput && config?.autoValueType) { // ์ด๋ฏธ ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ const currentValue = formData[fieldName]; console.log(`๐Ÿ” ์ž๋™์ž…๋ ฅ ํ•„๋“œ ์ฒดํฌ: ${fieldName}`, { currentValue, - isEmpty: currentValue === undefined || currentValue === "", + isEmpty: currentValue === undefined || currentValue === '', isAutoInput, - autoValueType: config.autoValueType, + autoValueType: config.autoValueType }); - - if (currentValue === undefined || currentValue === "") { - const autoValue = - config.autoValueType === "custom" - ? config.customValue || "" - : generateAutoValue(config.autoValueType); - + + if (currentValue === undefined || currentValue === '') { + const autoValue = config.autoValueType === "custom" + ? config.customValue || "" + : generateAutoValue(config.autoValueType); + console.log("๐Ÿ”„ ์ž๋™์ž…๋ ฅ ํ•„๋“œ ์ดˆ๊ธฐํ™”:", { fieldName, autoValueType: config.autoValueType, - autoValue, + autoValue }); - + updateFormData(fieldName, autoValue); } else { // console.log(`โญ๏ธ ์ž๋™์ž…๋ ฅ ๊ฑด๋„ˆ๋œ€ (๊ฐ’ ์žˆ์Œ): ${fieldName} = "${currentValue}"`); @@ -528,7 +471,7 @@ export const InteractiveScreenViewer: React.FC = ( const FlowWidget = require("@/components/screen/widgets/FlowWidget").FlowWidget; // componentConfig์—์„œ flowId ์ถ”์ถœ const flowConfig = (comp as any).componentConfig || {}; - + console.log("๐Ÿ” InteractiveScreenViewer ํ”Œ๋กœ์šฐ ์œ„์ ฏ ๋ณ€ํ™˜:", { compType: comp.type, hasComponentConfig: !!(comp as any).componentConfig, @@ -536,7 +479,7 @@ export const InteractiveScreenViewer: React.FC = ( flowConfigFlowId: flowConfig.flowId, finalFlowId: flowConfig.flowId, }); - + const flowComponent = { ...comp, type: "flow" as const, @@ -546,9 +489,9 @@ export const InteractiveScreenViewer: React.FC = ( allowDataMove: flowConfig.allowDataMove || false, displayMode: flowConfig.displayMode || "horizontal", }; - + console.log("๐Ÿ” InteractiveScreenViewer ์ตœ์ข… flowComponent:", flowComponent); - + return (
@@ -560,7 +503,7 @@ export const InteractiveScreenViewer: React.FC = ( const componentType = (comp as any).componentType || (comp as any).componentId; if (comp.type === "tabs" || (comp.type === "component" && componentType === "tabs-widget")) { const TabsWidget = require("@/components/screen/widgets/TabsWidget").TabsWidget; - + // componentConfig์—์„œ ํƒญ ์ •๋ณด ์ถ”์ถœ const tabsConfig = comp.componentConfig || {}; const tabsComponent = { @@ -573,7 +516,7 @@ export const InteractiveScreenViewer: React.FC = ( allowCloseable: tabsConfig.allowCloseable || false, persistSelection: tabsConfig.persistSelection || false, }; - + console.log("๐Ÿ” ํƒญ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:", { originalType: comp.type, componentType, @@ -581,11 +524,11 @@ export const InteractiveScreenViewer: React.FC = ( tabs: tabsComponent.tabs, tabsConfig, }); - + return (
-
@@ -598,7 +541,7 @@ export const InteractiveScreenViewer: React.FC = ( const componentConfig = (comp as any).componentConfig || {}; // config๊ฐ€ ์ค‘์ฒฉ๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Œ: componentConfig.config ๋˜๋Š” componentConfig ์ง์ ‘ const rackConfig = componentConfig.config || componentConfig; - + console.log("๐Ÿ—๏ธ ๋ ‰ ๊ตฌ์กฐ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:", { componentType, componentConfig, @@ -606,7 +549,7 @@ export const InteractiveScreenViewer: React.FC = ( fieldMapping: rackConfig.fieldMapping, formData, }); - + return (
= ( ); } - const { widgetType, label: originalLabel, placeholder, required, readonly, columnName } = comp; + const { widgetType, label, placeholder, required, readonly, columnName } = comp; const fieldName = columnName || comp.id; const currentValue = formData[fieldName] || ""; - // ๋‹ค๊ตญ์–ด ๋ผ๋ฒจ ์ ์šฉ (langKey๊ฐ€ ์žˆ์œผ๋ฉด ๋ฒˆ์—ญ ํ…์ŠคํŠธ ์‚ฌ์šฉ) - const compLangKey = (comp as any).langKey; - const label = compLangKey && translations[compLangKey] ? translations[compLangKey] : originalLabel; - // ์Šคํƒ€์ผ ์ ์šฉ const applyStyles = (element: React.ReactElement) => { if (!comp.style) return element; @@ -644,7 +583,7 @@ export const InteractiveScreenViewer: React.FC = ( return React.cloneElement(element, { style: { ...element.props.style, // ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ - ...styleWithoutSize, // width/height ์ œ์™ธํ•œ ์Šคํƒ€์ผ๋งŒ ์ ์šฉ + ...styleWithoutSize, // width/height ์ œ์™ธํ•œ ์Šคํƒ€์ผ๋งŒ ์ ์šฉ boxSizing: "border-box", }, }); @@ -659,13 +598,12 @@ export const InteractiveScreenViewer: React.FC = ( // ์ž๋™์ž…๋ ฅ ๊ด€๋ จ ์ฒ˜๋ฆฌ const isAutoInput = config?.autoInput || false; - const autoValue = - isAutoInput && config?.autoValueType - ? config.autoValueType === "custom" - ? config.customValue || "" - : generateAutoValue(config.autoValueType) - : ""; - + const autoValue = isAutoInput && config?.autoValueType + ? config.autoValueType === "custom" + ? config.customValue || "" + : generateAutoValue(config.autoValueType) + : ""; + // ๊ธฐ๋ณธ๊ฐ’ ๋˜๋Š” ์ž๋™๊ฐ’ ์„ค์ • const displayValue = isAutoInput ? autoValue : currentValue || config?.defaultValue || ""; @@ -858,17 +796,17 @@ export const InteractiveScreenViewer: React.FC = ( }); const finalPlaceholder = config?.placeholder || placeholder || "์„ ํƒํ•˜์„ธ์š”..."; - + // ๐Ÿ†• ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด ์ฒ˜๋ฆฌ (๋ฐฉ๋ฒ• 1: ๊ด€๊ณ„ ์ฝ”๋“œ ๋ฐฉ์‹ - ๊ถŒ์žฅ) if (config?.cascadingRelationCode && config?.cascadingParentField) { const parentFieldValue = formData[config.cascadingParentField]; - + console.log("๐Ÿ”— ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด (๊ด€๊ณ„์ฝ”๋“œ ๋ฐฉ์‹):", { relationCode: config.cascadingRelationCode, parentField: config.cascadingParentField, parentValue: parentFieldValue, }); - + return applyStyles( = ( />, ); } - + // ๐Ÿ”„ ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด ์ฒ˜๋ฆฌ (๋ฐฉ๋ฒ• 2: ์ง์ ‘ ์„ค์ • ๋ฐฉ์‹ - ๋ ˆ๊ฑฐ์‹œ) if (config?.cascading?.enabled) { const cascadingConfig = config.cascading; const parentValue = formData[cascadingConfig.parentField]; - + return applyStyles( = ( />, ); } - + // ์ผ๋ฐ˜ Select const options = config?.options || [ { label: "์˜ต์…˜ 1", value: "option1" }, @@ -1069,7 +1007,7 @@ export const InteractiveScreenViewer: React.FC = ( min={config?.minDate} max={config?.maxDate} className="w-full" - style={{ height: "100%" }} + style={{ height: "100%" }} />, ); } else { @@ -1137,20 +1075,19 @@ export const InteractiveScreenViewer: React.FC = ( case "file": { const widget = comp as WidgetComponent; const config = widget.webTypeConfig as FileTypeConfig | undefined; - + // ํ˜„์žฌ ํŒŒ์ผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ const getCurrentValue = () => { const fieldName = widget.columnName || widget.id; return (externalFormData?.[fieldName] || localFormData[fieldName]) as any; }; - + const currentValue = getCurrentValue(); // ํ™”๋ฉด ID ์ถ”์ถœ (URL์—์„œ) - const screenId = - typeof window !== "undefined" && window.location.pathname.includes("/screens/") - ? parseInt(window.location.pathname.split("/screens/")[1]) - : null; + const screenId = typeof window !== 'undefined' && window.location.pathname.includes('/screens/') + ? parseInt(window.location.pathname.split('/screens/')[1]) + : null; console.log("๐Ÿ“ InteractiveScreenViewer - File ์œ„์ ฏ:", { componentId: widget.id, @@ -1172,14 +1109,14 @@ export const InteractiveScreenViewer: React.FC = ( e.target.value = ""; // ํŒŒ์ผ ์„ ํƒ ์ทจ์†Œ return; } - + const files = e.target.files; const fieldName = widget.columnName || widget.id; - + // ํŒŒ์ผ ์„ ํƒ์„ ์ทจ์†Œํ•œ ๊ฒฝ์šฐ (files๊ฐ€ null์ด๊ฑฐ๋‚˜ ๊ธธ์ด๊ฐ€ 0) if (!files || files.length === 0) { // console.log("๐Ÿ“ ํŒŒ์ผ ์„ ํƒ ์ทจ์†Œ๋จ - ๊ธฐ์กด ํŒŒ์ผ ์œ ์ง€"); - + // ํ˜„์žฌ ์ €์žฅ๋œ ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ const currentStoredValue = externalFormData?.[fieldName] || localFormData[fieldName]; if (currentStoredValue) { @@ -1208,19 +1145,19 @@ export const InteractiveScreenViewer: React.FC = ( // ์‹ค์ œ ์„œ๋ฒ„๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ try { toast.loading(`${files.length}๊ฐœ ํŒŒ์ผ ์—…๋กœ๋“œ ์ค‘...`); - + const uploadResult = await uploadFilesAndCreateData(files); - + if (uploadResult.success) { // console.log("๐Ÿ“ ์—…๋กœ๋“œ ์™„๋ฃŒ๋œ ํŒŒ์ผ ๋ฐ์ดํ„ฐ:", uploadResult.data); - - setLocalFormData((prev) => ({ ...prev, [fieldName]: uploadResult.data })); - + + setLocalFormData(prev => ({ ...prev, [fieldName]: uploadResult.data })); + // ์™ธ๋ถ€ ํผ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ ํ˜ธ์ถœ if (onFormDataChange) { onFormDataChange(fieldName, uploadResult.data); } - + toast.success(uploadResult.message); } else { throw new Error("ํŒŒ์ผ ์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); @@ -1228,7 +1165,7 @@ export const InteractiveScreenViewer: React.FC = ( } catch (error) { // console.error("ํŒŒ์ผ ์—…๋กœ๋“œ ์˜ค๋ฅ˜:", error); toast.error("ํŒŒ์ผ ์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); - + // ํŒŒ์ผ ์ž…๋ ฅ ์ดˆ๊ธฐํ™” e.target.value = ""; return; @@ -1237,13 +1174,13 @@ export const InteractiveScreenViewer: React.FC = ( const clearFile = () => { const fieldName = widget.columnName || widget.id; - setLocalFormData((prev) => ({ ...prev, [fieldName]: null })); - + setLocalFormData(prev => ({ ...prev, [fieldName]: null })); + // ์™ธ๋ถ€ ํผ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ ํ˜ธ์ถœ if (onFormDataChange) { onFormDataChange(fieldName, null); } - + // ํŒŒ์ผ input ์ดˆ๊ธฐํ™” const fileInput = document.querySelector(`input[type="file"][data-field="${fieldName}"]`) as HTMLInputElement; if (fileInput) { @@ -1257,31 +1194,39 @@ export const InteractiveScreenViewer: React.FC = ( // ์ƒˆ๋กœ์šด JSON ๊ตฌ์กฐ์—์„œ ํŒŒ์ผ ์ •๋ณด ์ถ”์ถœ const fileData = currentValue.files || []; if (fileData.length === 0) return null; - + return (
-
์—…๋กœ๋“œ๋œ ํŒŒ์ผ ({fileData.length}๊ฐœ)
+
+ ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ({fileData.length}๊ฐœ) +
{fileData.map((fileInfo: any, index: number) => { - const isImage = fileInfo.type?.startsWith("image/"); - + const isImage = fileInfo.type?.startsWith('image/'); + return ( -
-
+
+
{isImage ? (
IMG
) : ( - + )}
-
-

{fileInfo.name}

-

{(fileInfo.size / 1024 / 1024).toFixed(2)} MB

-

{fileInfo.type || "์•Œ ์ˆ˜ ์—†๋Š” ํ˜•์‹"}

-

- ์—…๋กœ๋“œ: {new Date(fileInfo.uploadedAt).toLocaleString("ko-KR")} +

+

{fileInfo.name}

+

+ {(fileInfo.size / 1024 / 1024).toFixed(2)} MB

+

{fileInfo.type || '์•Œ ์ˆ˜ ์—†๋Š” ํ˜•์‹'}

+

์—…๋กœ๋“œ: {new Date(fileInfo.uploadedAt).toLocaleString('ko-KR')}

-
@@ -1292,7 +1237,7 @@ export const InteractiveScreenViewer: React.FC = ( }; const fieldName = widget.columnName || widget.id; - + return applyStyles(
{/* ํŒŒ์ผ ์„ ํƒ ์˜์—ญ */} @@ -1305,45 +1250,45 @@ export const InteractiveScreenViewer: React.FC = ( required={required} multiple={config?.multiple} accept={config?.accept} - className="absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed" + className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed" style={{ zIndex: 1 }} /> -
0 - ? "border-success/30 bg-success/10" - : "border-input bg-muted hover:border-input/80 hover:bg-muted/80", - readonly && "cursor-not-allowed opacity-50", - !readonly && "cursor-pointer", - )} - > +
0 + ? 'border-success/30 bg-success/10' + : 'border-input bg-muted hover:border-input/80 hover:bg-muted/80', + readonly && 'cursor-not-allowed opacity-50', + !readonly && 'cursor-pointer' + )}>
{currentValue && currentValue.files && currentValue.files.length > 0 ? ( <>
-
- +
+
-

- {currentValue.totalCount === 1 ? "ํŒŒ์ผ ์„ ํƒ๋จ" : `${currentValue.totalCount}๊ฐœ ํŒŒ์ผ ์„ ํƒ๋จ`} +

+ {currentValue.totalCount === 1 + ? 'ํŒŒ์ผ ์„ ํƒ๋จ' + : `${currentValue.totalCount}๊ฐœ ํŒŒ์ผ ์„ ํƒ๋จ`}

-

+

์ด {(currentValue.totalSize / 1024 / 1024).toFixed(2)}MB

-

ํด๋ฆญํ•˜์—ฌ ๋‹ค๋ฅธ ํŒŒ์ผ ์„ ํƒ

+

ํด๋ฆญํ•˜์—ฌ ๋‹ค๋ฅธ ํŒŒ์ผ ์„ ํƒ

) : ( <> - -

- {config?.dragDrop ? "ํŒŒ์ผ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋†“๊ฑฐ๋‚˜ ํด๋ฆญํ•˜์—ฌ ์„ ํƒ" : "ํด๋ฆญํ•˜์—ฌ ํŒŒ์ผ ์„ ํƒ"} + +

+ {config?.dragDrop ? 'ํŒŒ์ผ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋†“๊ฑฐ๋‚˜ ํด๋ฆญํ•˜์—ฌ ์„ ํƒ' : 'ํด๋ฆญํ•˜์—ฌ ํŒŒ์ผ ์„ ํƒ'}

{(config?.accept || config?.maxSize) && ( -
+
{config.accept &&
ํ—ˆ์šฉ ํ˜•์‹: {config.accept}
} {config.maxSize &&
์ตœ๋Œ€ ํฌ๊ธฐ: {config.maxSize}MB
} {config.multiple &&
๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅ
} @@ -1354,10 +1299,10 @@ export const InteractiveScreenViewer: React.FC = (
- + {/* ํŒŒ์ผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} {renderFilePreview()} -
, +
); } @@ -1365,7 +1310,7 @@ export const InteractiveScreenViewer: React.FC = ( const widget = comp as WidgetComponent; const config = widget.webTypeConfig as CodeTypeConfig | undefined; - console.log("๐Ÿ” [InteractiveScreenViewer] Code ์œ„์ ฏ ๋ Œ๋”๋ง:", { + console.log(`๐Ÿ” [InteractiveScreenViewer] Code ์œ„์ ฏ ๋ Œ๋”๋ง:`, { componentId: widget.id, columnName: widget.columnName, codeCategory: config?.codeCategory, @@ -1399,11 +1344,11 @@ export const InteractiveScreenViewer: React.FC = ( onEvent={(event: string, data: any) => { // console.log(`Code widget event: ${event}`, data); }} - />, + /> ); } catch (error) { // console.error("DynamicWebTypeRenderer ๋กœ๋”ฉ ์‹คํŒจ, ๊ธฐ๋ณธ Select ์‚ฌ์šฉ:", error); - + // ํด๋ฐฑ: ๊ธฐ๋ณธ Select ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ return applyStyles( , + ); } } case "entity": { - // DynamicWebTypeRenderer๋กœ ์œ„์ž„ํ•˜์—ฌ EntitySearchInputWrapper ์‚ฌ์šฉ const widget = comp as WidgetComponent; - return applyStyles( - updateFormData(fieldName, value), - onFormDataChange: updateFormData, - formData: formData, - readonly: readonly, - required: required, - placeholder: widget.placeholder || "์—”ํ‹ฐํ‹ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”", - isInteractive: true, - className: "w-full h-full", - }} - />, + const config = widget.webTypeConfig as EntityTypeConfig | undefined; + + console.log("๐Ÿข InteractiveScreenViewer - Entity ์œ„์ ฏ:", { + componentId: widget.id, + widgetType: widget.widgetType, + config, + appliedSettings: { + entityName: config?.entityName, + displayField: config?.displayField, + valueField: config?.valueField, + multiple: config?.multiple, + defaultValue: config?.defaultValue, + }, + }); + + const finalPlaceholder = config?.placeholder || "์—”ํ‹ฐํ‹ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”..."; + const defaultOptions = [ + { label: "์‚ฌ์šฉ์ž", value: "user" }, + { label: "์ œํ’ˆ", value: "product" }, + { label: "์ฃผ๋ฌธ", value: "order" }, + { label: "์นดํ…Œ๊ณ ๋ฆฌ", value: "category" }, + ]; + + return ( + , ); } @@ -1455,9 +1433,9 @@ export const InteractiveScreenViewer: React.FC = ( if (isPreviewMode) { return; } - + const actionType = config?.actionType || "save"; - + try { switch (actionType) { case "save": @@ -1494,7 +1472,7 @@ export const InteractiveScreenViewer: React.FC = ( await handleCustomAction(); break; default: - // console.log(`์•Œ ์ˆ˜ ์—†๋Š” ์•ก์…˜ ํƒ€์ž…: ${actionType}`); + // console.log(`์•Œ ์ˆ˜ ์—†๋Š” ์•ก์…˜ ํƒ€์ž…: ${actionType}`); } } catch (error) { // console.error(`๋ฒ„ํŠผ ์•ก์…˜ ์‹คํ–‰ ์˜ค๋ฅ˜ (${actionType}):`, error); @@ -1525,24 +1503,24 @@ export const InteractiveScreenViewer: React.FC = ( // ๊ธฐ์กด ๋ฐฉ์‹ (๋ ˆ๊ฑฐ์‹œ ์ง€์›) const currentFormData = { ...localFormData, ...externalFormData }; // console.log("๐Ÿ’พ ๊ธฐ์กด ๋ฐฉ์‹์œผ๋กœ ์ €์žฅ - currentFormData:", currentFormData); - + // formData ์œ ํšจ์„ฑ ์ฒดํฌ๋ฅผ ์™„ํ™” (๋นˆ ๊ฐ์ฒด๋ผ๋„ ์œ„์ ฏ์ด ์žˆ์œผ๋ฉด ์ €์žฅ ์ง„ํ–‰) - const hasWidgets = allComponents.some((comp) => comp.type === "widget"); + const hasWidgets = allComponents.some(comp => comp.type === 'widget'); if (!hasWidgets) { alert("์ €์žฅํ•  ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); return; } // ํ•„์ˆ˜ ํ•ญ๋ชฉ ๊ฒ€์ฆ - const requiredFields = allComponents.filter((c) => c.required && (c.columnName || c.id)); - const missingFields = requiredFields.filter((field) => { + const requiredFields = allComponents.filter(c => c.required && (c.columnName || c.id)); + const missingFields = requiredFields.filter(field => { const fieldName = field.columnName || field.id; const value = currentFormData[fieldName]; return !value || value.toString().trim() === ""; }); if (missingFields.length > 0) { - const fieldNames = missingFields.map((f) => f.label || f.columnName || f.id).join(", "); + const fieldNames = missingFields.map(f => f.label || f.columnName || f.id).join(", "); alert(`๋‹ค์Œ ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”: ${fieldNames}`); return; } @@ -1555,59 +1533,56 @@ export const InteractiveScreenViewer: React.FC = ( try { // ์ปฌ๋Ÿผ๋ช… ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ ๋งคํ•‘ const mappedData: Record = {}; - + // ์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ - allComponents.forEach((comp) => { + allComponents.forEach(comp => { // ์œ„์ ฏ ์ปดํฌ๋„ŒํŠธ์ด๊ณ  ์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ํƒ€์ž…์ธ ๊ฒฝ์šฐ - if (comp.type === "widget") { + if (comp.type === 'widget') { const widget = comp as WidgetComponent; const fieldName = widget.columnName || widget.id; let value = currentFormData[fieldName]; - + console.log(`๐Ÿ” ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ: ${fieldName}`, { widgetType: widget.widgetType, formDataValue: value, hasWebTypeConfig: !!widget.webTypeConfig, - config: widget.webTypeConfig, + config: widget.webTypeConfig }); - + // ์ž๋™์ž…๋ ฅ ํ•„๋“œ์ธ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ’์ด ์—†์„ ๋•Œ ์ƒ์„ฑ - if ( - (widget.widgetType === "text" || widget.widgetType === "email" || widget.widgetType === "tel") && - widget.webTypeConfig - ) { + if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') && + widget.webTypeConfig) { const config = widget.webTypeConfig as TextTypeConfig; const isAutoInput = config?.autoInput || false; - + console.log(`๐Ÿ“‹ ${fieldName} ์ž๋™์ž…๋ ฅ ์ฒดํฌ:`, { isAutoInput, autoValueType: config?.autoValueType, hasValue: !!value, - value, + value }); - - if (isAutoInput && config?.autoValueType && (!value || value === "")) { + + if (isAutoInput && config?.autoValueType && (!value || value === '')) { // ์ž๋™์ž…๋ ฅ์ด๊ณ  ๊ฐ’์ด ์—†์„ ๋•Œ๋งŒ ์ƒ์„ฑ - value = - config.autoValueType === "custom" - ? config.customValue || "" - : generateAutoValue(config.autoValueType); - + value = config.autoValueType === "custom" + ? config.customValue || "" + : generateAutoValue(config.autoValueType); + console.log("๐Ÿ’พ ์ž๋™์ž…๋ ฅ ๊ฐ’ ์ €์žฅ (๊ฐ’์ด ์—†์–ด์„œ ์ƒ์„ฑ):", { fieldName, autoValueType: config.autoValueType, - generatedValue: value, + generatedValue: value }); } else if (isAutoInput && value) { console.log("๐Ÿ’พ ์ž๋™์ž…๋ ฅ ํ•„๋“œ์ง€๋งŒ ๊ธฐ์กด ๊ฐ’ ์œ ์ง€:", { fieldName, - existingValue: value, + existingValue: value }); } else if (!isAutoInput) { // console.log(`๐Ÿ“ ์ผ๋ฐ˜ ์ž…๋ ฅ ํ•„๋“œ: ${fieldName} = "${value}"`); } } - + // ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ๋งŒ ๋งคํ•‘ (๋นˆ ๋ฌธ์ž์—ด๋„ ํฌํ•จํ•˜๋˜, undefined๋Š” ์ œ์™ธ) if (value !== undefined && value !== null && value !== "undefined") { // columnName์ด ์žˆ์œผ๋ฉด columnName์„ ํ‚ค๋กœ, ์—†์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ ID๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ @@ -1626,34 +1601,35 @@ export const InteractiveScreenViewer: React.FC = ( ๋งคํ•‘๋œ๋ฐ์ดํ„ฐ: mappedData, ํ™”๋ฉด์ •๋ณด: screenInfo, ์ „์ฒด์ปดํฌ๋„ŒํŠธ์ˆ˜: allComponents.length, - ์œ„์ ฏ์ปดํฌ๋„ŒํŠธ์ˆ˜: allComponents.filter((c) => c.type === "widget").length, + ์œ„์ ฏ์ปดํฌ๋„ŒํŠธ์ˆ˜: allComponents.filter(c => c.type === 'widget').length, }); // ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ์„ธ ์ •๋ณด ๋กœ๊ทธ // console.log("๐Ÿ” ์ปดํฌ๋„ŒํŠธ๋ณ„ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ƒ์„ธ:"); - allComponents.forEach((comp) => { - if (comp.type === "widget") { + allComponents.forEach(comp => { + if (comp.type === 'widget') { const widget = comp as WidgetComponent; const fieldName = widget.columnName || widget.id; const value = currentFormData[fieldName]; - const hasValue = value !== undefined && value !== null && value !== ""; + const hasValue = value !== undefined && value !== null && value !== ''; // console.log(` - ${fieldName} (${widget.widgetType}): "${value}" (๊ฐ’์žˆ์Œ: ${hasValue}, ์ปฌ๋Ÿผ๋ช…: ${widget.columnName})`); } }); - + // ๋งคํ•‘๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด ๊ฒฝ๊ณ  if (Object.keys(mappedData).length === 0) { // console.warn("โš ๏ธ ๋งคํ•‘๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋นˆ ๋ฐ์ดํ„ฐ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค."); } // ํ…Œ์ด๋ธ”๋ช… ๊ฒฐ์ • (ํ™”๋ฉด ์ •๋ณด์—์„œ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์ฒซ ๋ฒˆ์งธ ์ปดํฌ๋„ŒํŠธ์˜ ํ…Œ์ด๋ธ”๋ช… ์‚ฌ์šฉ) - const tableName = - screenInfo.tableName || allComponents.find((c) => c.columnName)?.tableName || "dynamic_form_data"; // ๊ธฐ๋ณธ๊ฐ’ + const tableName = screenInfo.tableName || + allComponents.find(c => c.columnName)?.tableName || + "dynamic_form_data"; // ๊ธฐ๋ณธ๊ฐ’ // ๐Ÿ†• ์ž๋™์œผ๋กœ ์ž‘์„ฑ์ž ์ •๋ณด ์ถ”๊ฐ€ (user.userId๊ฐ€ ํ™•์‹คํžˆ ์žˆ์Œ) const writerValue = user.userId; const companyCodeValue = user.companyCode || ""; - + console.log("๐Ÿ‘ค ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ •๋ณด:", { userId: user.userId, userName: userName, @@ -1685,11 +1661,11 @@ export const InteractiveScreenViewer: React.FC = ( if (result.success) { alert("์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // console.log("โœ… ์ €์žฅ ์„ฑ๊ณต:", result.data); - + // ์ €์žฅ ํ›„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” (์„ ํƒ์‚ฌํ•ญ) if (onFormDataChange) { const resetData: Record = {}; - Object.keys(formData).forEach((key) => { + Object.keys(formData).forEach(key => { resetData[key] = ""; }); onFormDataChange(resetData); @@ -1703,25 +1679,27 @@ export const InteractiveScreenViewer: React.FC = ( } }; + // ์‚ญ์ œ ์•ก์…˜ const handleDeleteAction = async () => { const confirmMessage = config?.confirmMessage || "์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"; - + if (!confirm(confirmMessage)) { return; } // ์‚ญ์ œํ•  ๋ ˆ์ฝ”๋“œ ID๊ฐ€ ํ•„์š” (ํผ ๋ฐ์ดํ„ฐ์—์„œ id ํ•„๋“œ ์ฐพ๊ธฐ) const recordId = formData["id"] || formData["ID"] || formData["objid"]; - + if (!recordId) { alert("์‚ญ์ œํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (ID๊ฐ€ ์—†์Œ)"); return; } // ํ…Œ์ด๋ธ”๋ช… ๊ฒฐ์ • - const tableName = - screenInfo?.tableName || allComponents.find((c) => c.columnName)?.tableName || "unknown_table"; + const tableName = screenInfo?.tableName || + allComponents.find(c => c.columnName)?.tableName || + "unknown_table"; if (!tableName || tableName === "unknown_table") { alert("ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์—†์–ด ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); @@ -1731,17 +1709,16 @@ export const InteractiveScreenViewer: React.FC = ( try { // console.log("๐Ÿ—‘๏ธ ์‚ญ์ œ ์‹คํ–‰:", { recordId, tableName, formData }); - // screenId ์ „๋‹ฌํ•˜์—ฌ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ - const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName, screenInfo?.id); + const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName); if (result.success) { alert("์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // console.log("โœ… ์‚ญ์ œ ์„ฑ๊ณต"); - + // ์‚ญ์ œ ํ›„ ํผ ์ดˆ๊ธฐํ™” if (onFormDataChange) { const resetData: Record = {}; - Object.keys(formData).forEach((key) => { + Object.keys(formData).forEach(key => { resetData[key] = ""; }); onFormDataChange(resetData); @@ -1758,13 +1735,13 @@ export const InteractiveScreenViewer: React.FC = ( // ํŽธ์ง‘ ์•ก์…˜ const handleEditAction = () => { console.log("โœ๏ธ ์ˆ˜์ • ์•ก์…˜ ์‹คํ–‰"); - + // ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ์˜ ์ˆ˜์ • ๋ชจ๋‹ฌ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ const editModalTitle = config?.editModalTitle || ""; const editModalDescription = config?.editModalDescription || ""; - + console.log("๐Ÿ“ ๋ฒ„ํŠผ ์ˆ˜์ • ๋ชจ๋‹ฌ ์„ค์ •:", { editModalTitle, editModalDescription }); - + // EditModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ const event = new CustomEvent("openEditModal", { detail: { @@ -1793,7 +1770,7 @@ export const InteractiveScreenViewer: React.FC = ( const handleSearchAction = () => { // console.log("๐Ÿ” ๊ฒ€์ƒ‰ ์‹คํ–‰:", formData); // ๊ฒ€์ƒ‰ ๋กœ์ง - const searchTerms = Object.values(formData).filter((v) => v && v.toString().trim()); + const searchTerms = Object.values(formData).filter(v => v && v.toString().trim()); if (searchTerms.length === 0) { alert("๊ฒ€์ƒ‰ํ•  ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); } else { @@ -1806,7 +1783,7 @@ export const InteractiveScreenViewer: React.FC = ( if (confirm("๋ชจ๋“  ์ž…๋ ฅ์„ ์ดˆ๊ธฐํ™”ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) { if (onFormDataChange) { const resetData: Record = {}; - Object.keys(formData).forEach((key) => { + Object.keys(formData).forEach(key => { resetData[key] = ""; }); onFormDataChange(resetData); @@ -1826,24 +1803,22 @@ export const InteractiveScreenViewer: React.FC = ( // ๋‹ซ๊ธฐ ์•ก์…˜ const handleCloseAction = () => { // console.log("โŒ ๋‹ซ๊ธฐ ์•ก์…˜ ์‹คํ–‰"); - + // ๋ชจ๋‹ฌ ๋‚ด๋ถ€์—์„œ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธ const isInModal = document.querySelector('[role="dialog"]') !== null; const isInPopup = window.opener !== null; - + if (isInModal) { // ๋ชจ๋‹ฌ ๋‚ด๋ถ€์ธ ๊ฒฝ์šฐ: ๋ชจ๋‹ฌ์˜ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ // console.log("๐Ÿ”„ ๋ชจ๋‹ฌ ๋‚ด๋ถ€์—์„œ ๋‹ซ๊ธฐ - ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ ์‹œ๋„"); - + // ๋ชจ๋‹ฌ์˜ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์„ ์ฐพ์•„์„œ ํด๋ฆญ - const modalCloseButton = document.querySelector( - '[role="dialog"] button[aria-label*="Close"], [role="dialog"] button[data-dismiss="modal"], [role="dialog"] .dialog-close', - ); + const modalCloseButton = document.querySelector('[role="dialog"] button[aria-label*="Close"], [role="dialog"] button[data-dismiss="modal"], [role="dialog"] .dialog-close'); if (modalCloseButton) { (modalCloseButton as HTMLElement).click(); } else { // ESC ํ‚ค ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ - const escEvent = new KeyboardEvent("keydown", { key: "Escape", keyCode: 27, which: 27 }); + const escEvent = new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, which: 27 }); document.dispatchEvent(escEvent); } } else if (isInPopup) { @@ -1860,7 +1835,7 @@ export const InteractiveScreenViewer: React.FC = ( // ํŒ์—… ์•ก์…˜ const handlePopupAction = () => { // console.log("๐ŸŽฏ ํŒ์—… ์•ก์…˜ ์‹คํ–‰:", { popupScreenId: config?.popupScreenId }); - + if (config?.popupScreenId) { // ํ™”๋ฉด ๋ชจ๋‹ฌ ์—ด๊ธฐ setPopupScreen({ @@ -1879,17 +1854,17 @@ export const InteractiveScreenViewer: React.FC = ( // ๋„ค๋น„๊ฒŒ์ด์…˜ ์•ก์…˜ const handleNavigateAction = () => { const navigateType = config?.navigateType || "url"; - + if (navigateType === "screen" && config?.navigateScreenId) { // ํ™”๋ฉด์œผ๋กœ ์ด๋™ const screenPath = `/screens/${config.navigateScreenId}`; - + console.log("๐ŸŽฏ ํ™”๋ฉด์œผ๋กœ ์ด๋™:", { screenId: config.navigateScreenId, target: config.navigateTarget || "_self", - path: screenPath, + path: screenPath }); - + if (config.navigateTarget === "_blank") { window.open(screenPath, "_blank"); } else { @@ -1899,9 +1874,9 @@ export const InteractiveScreenViewer: React.FC = ( // URL๋กœ ์ด๋™ console.log("๐Ÿ”— URL๋กœ ์ด๋™:", { url: config.navigateUrl, - target: config.navigateTarget || "_self", + target: config.navigateTarget || "_self" }); - + if (config.navigateTarget === "_blank") { window.open(config.navigateUrl, "_blank"); } else { @@ -1911,7 +1886,7 @@ export const InteractiveScreenViewer: React.FC = ( console.log("๐Ÿ”— ๋„ค๋น„๊ฒŒ์ด์…˜ ์ •๋ณด๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค:", { navigateType, hasUrl: !!config?.navigateUrl, - hasScreenId: !!config?.navigateScreenId, + hasScreenId: !!config?.navigateScreenId }); } }; @@ -1934,24 +1909,17 @@ export const InteractiveScreenViewer: React.FC = ( } }; - // ๋ฒ„ํŠผ ํ…์ŠคํŠธ ๋‹ค๊ตญ์–ด ์ ์šฉ (componentConfig.langKey ํ™•์ธ) - const buttonLangKey = (widget as any).componentConfig?.langKey; - const buttonText = - buttonLangKey && translations[buttonLangKey] - ? translations[buttonLangKey] - : (widget as any).componentConfig?.text || label || "๋ฒ„ํŠผ"; - // ์ปค์Šคํ…€ ์ƒ‰์ƒ์ด ์žˆ์œผ๋ฉด Tailwind ํด๋ž˜์Šค ๋Œ€์‹  ์ง์ ‘ ์Šคํƒ€์ผ ์ ์šฉ const hasCustomColors = config?.backgroundColor || config?.textColor; - + return applyStyles( , + {label || "๋ฒ„ํŠผ"} + ); } @@ -1984,73 +1952,71 @@ export const InteractiveScreenViewer: React.FC = ( // ํŒŒ์ผ ์ฒจ๋ถ€ ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ if (isFileComponent(component)) { const fileComponent = component as FileComponent; - + console.log("๐ŸŽฏ File ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:", { componentId: fileComponent.id, currentUploadedFiles: fileComponent.uploadedFiles?.length || 0, hasOnFormDataChange: !!onFormDataChange, - userInfo: user ? { userId: user.userId, companyCode: user.companyCode } : "no user", + userInfo: user ? { userId: user.userId, companyCode: user.companyCode } : "no user" }); + + const handleFileUpdate = useCallback(async (updates: Partial) => { + // ์‹ค์ œ ํ™”๋ฉด์—์„œ๋Š” ํŒŒ์ผ ์—…๋ฐ์ดํŠธ๋ฅผ ์ฒ˜๋ฆฌ + console.log("๐Ÿ“Ž InteractiveScreenViewer - ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ:", { + updates, + hasUploadedFiles: !!updates.uploadedFiles, + uploadedFilesCount: updates.uploadedFiles?.length || 0, + hasOnFormDataChange: !!onFormDataChange + }); + + if (updates.uploadedFiles && onFormDataChange) { + const fieldName = fileComponent.columnName || fileComponent.id; + + // attach_file_info ํ…Œ์ด๋ธ” ๊ตฌ์กฐ์— ๋งž๋Š” ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + const fileInfoForDB = updates.uploadedFiles.map(file => ({ + objid: file.objid.replace('temp_', ''), // temp_ ์ œ๊ฑฐ + target_objid: "", + saved_file_name: file.savedFileName, + real_file_name: file.realFileName, + doc_type: file.docType, + doc_type_name: file.docTypeName, + file_size: file.fileSize, + file_ext: file.fileExt, + file_path: file.filePath, + writer: file.writer, + regdate: file.regdate, + status: file.status, + parent_target_objid: "", + company_code: file.companyCode + })); - const handleFileUpdate = useCallback( - async (updates: Partial) => { - // ์‹ค์ œ ํ™”๋ฉด์—์„œ๋Š” ํŒŒ์ผ ์—…๋ฐ์ดํŠธ๋ฅผ ์ฒ˜๋ฆฌ - console.log("๐Ÿ“Ž InteractiveScreenViewer - ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ:", { - updates, + // console.log("๐Ÿ’พ attach_file_info ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜๋œ ๋ฐ์ดํ„ฐ:", fileInfoForDB); + + // FormData์—๋Š” ํŒŒ์ผ ์—ฐ๊ฒฐ ์ •๋ณด๋งŒ ์ €์žฅ (๊ฐ„๋‹จํ•œ ํ˜•ํƒœ) + const formDataValue = { + fileCount: updates.uploadedFiles.length, + docType: fileComponent.fileConfig.docType, + files: updates.uploadedFiles.map(file => ({ + objid: file.objid, + realFileName: file.realFileName, + fileSize: file.fileSize, + status: file.status + })) + }; + + // console.log("๐Ÿ“ FormData ์ €์žฅ๊ฐ’:", { fieldName, formDataValue }); + onFormDataChange(fieldName, formDataValue); + + // TODO: ์‹ค์ œ API ์—ฐ๋™ ์‹œ attach_file_info ํ…Œ์ด๋ธ”์— ์ €์žฅ + // await saveFilesToDatabase(fileInfoForDB); + + } else { + console.warn("โš ๏ธ ํŒŒ์ผ ์—…๋ฐ์ดํŠธ ์‹คํŒจ:", { hasUploadedFiles: !!updates.uploadedFiles, - uploadedFilesCount: updates.uploadedFiles?.length || 0, - hasOnFormDataChange: !!onFormDataChange, + hasOnFormDataChange: !!onFormDataChange }); - - if (updates.uploadedFiles && onFormDataChange) { - const fieldName = fileComponent.columnName || fileComponent.id; - - // attach_file_info ํ…Œ์ด๋ธ” ๊ตฌ์กฐ์— ๋งž๋Š” ๋ฐ์ดํ„ฐ ์ƒ์„ฑ - const fileInfoForDB = updates.uploadedFiles.map((file) => ({ - objid: file.objid.replace("temp_", ""), // temp_ ์ œ๊ฑฐ - target_objid: "", - saved_file_name: file.savedFileName, - real_file_name: file.realFileName, - doc_type: file.docType, - doc_type_name: file.docTypeName, - file_size: file.fileSize, - file_ext: file.fileExt, - file_path: file.filePath, - writer: file.writer, - regdate: file.regdate, - status: file.status, - parent_target_objid: "", - company_code: file.companyCode, - })); - - // console.log("๐Ÿ’พ attach_file_info ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜๋œ ๋ฐ์ดํ„ฐ:", fileInfoForDB); - - // FormData์—๋Š” ํŒŒ์ผ ์—ฐ๊ฒฐ ์ •๋ณด๋งŒ ์ €์žฅ (๊ฐ„๋‹จํ•œ ํ˜•ํƒœ) - const formDataValue = { - fileCount: updates.uploadedFiles.length, - docType: fileComponent.fileConfig.docType, - files: updates.uploadedFiles.map((file) => ({ - objid: file.objid, - realFileName: file.realFileName, - fileSize: file.fileSize, - status: file.status, - })), - }; - - // console.log("๐Ÿ“ FormData ์ €์žฅ๊ฐ’:", { fieldName, formDataValue }); - onFormDataChange(fieldName, formDataValue); - - // TODO: ์‹ค์ œ API ์—ฐ๋™ ์‹œ attach_file_info ํ…Œ์ด๋ธ”์— ์ €์žฅ - // await saveFilesToDatabase(fileInfoForDB); - } else { - console.warn("โš ๏ธ ํŒŒ์ผ ์—…๋ฐ์ดํŠธ ์‹คํŒจ:", { - hasUploadedFiles: !!updates.uploadedFiles, - hasOnFormDataChange: !!onFormDataChange, - }); - } - }, - [fileComponent, onFormDataChange], - ); + } + }, [fileComponent, onFormDataChange]); return (
@@ -2105,10 +2071,7 @@ export const InteractiveScreenViewer: React.FC = ( (component.label || component.style?.labelText) && !templateTypes.includes(component.type); // ํ…œํ”Œ๋ฆฟ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ผ๋ฒจ ํ‘œ์‹œ ์•ˆํ•จ - // ๋‹ค๊ตญ์–ด ๋ผ๋ฒจ ํ…์ŠคํŠธ ๊ฒฐ์ • (langKey๊ฐ€ ์žˆ์œผ๋ฉด ๋ฒˆ์—ญ ํ…์ŠคํŠธ ์‚ฌ์šฉ) - const langKey = (component as any).langKey; - const originalLabelText = component.style?.labelText || component.label || ""; - const labelText = langKey && translations[langKey] ? translations[langKey] : originalLabelText; + const labelText = component.style?.labelText || component.label || ""; // ๋ผ๋ฒจ ํ‘œ์‹œ ์—ฌ๋ถ€ ๋กœ๊ทธ (๋””๋ฒ„๊น…์šฉ) if (component.type === "widget") { @@ -2117,8 +2080,6 @@ export const InteractiveScreenViewer: React.FC = ( hideLabel, shouldShowLabel, labelText, - langKey, - hasTranslation: !!translations[langKey], }); } @@ -2133,6 +2094,7 @@ export const InteractiveScreenViewer: React.FC = ( marginBottom: component.style?.labelMarginBottom || "4px", }; + // ์ƒ์œ„์—์„œ ๋ผ๋ฒจ์„ ํ‘œ์‹œํ•œ ๊ฒฝ์šฐ, ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ๋Š” ๋ผ๋ฒจ์„ ์ˆจ๊น€ const componentForRendering = shouldShowLabel ? { @@ -2148,122 +2110,112 @@ export const InteractiveScreenViewer: React.FC = ( -
- {/* ํ…Œ์ด๋ธ” ์˜ต์…˜ ํˆด๋ฐ” */} - +
+ {/* ํ…Œ์ด๋ธ” ์˜ต์…˜ ํˆด๋ฐ” */} + + + {/* ๋ฉ”์ธ ์ปจํ…์ธ  */} +
+ {/* ๋ผ๋ฒจ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ‘œ์‹œ (๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ œ์™ธ) */} + {shouldShowLabel && ( + + )} - {/* ๋ฉ”์ธ ์ปจํ…์ธ  */} -
- {/* ๋ผ๋ฒจ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ‘œ์‹œ (๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ œ์™ธ) */} - {shouldShowLabel && ( - - )} - - {/* ์‹ค์ œ ์œ„์ ฏ - ์ƒ์œ„์—์„œ ๋ผ๋ฒจ์„ ๋ Œ๋”๋งํ–ˆ์œผ๋ฏ€๋กœ ์ž์‹์€ ๋ผ๋ฒจ ์ˆจ๊น€ */} -
- {renderInteractiveWidget(componentForRendering)} -
-
+ {/* ์‹ค์ œ ์œ„์ ฏ - ์ƒ์œ„์—์„œ ๋ผ๋ฒจ์„ ๋ Œ๋”๋งํ–ˆ์œผ๋ฏ€๋กœ ์ž์‹์€ ๋ผ๋ฒจ ์ˆจ๊น€ */} +
{renderInteractiveWidget(componentForRendering)}
+
- {/* ๊ฐœ์„ ๋œ ๊ฒ€์ฆ ํŒจ๋„ (์„ ํƒ์  ํ‘œ์‹œ) */} - {showValidationPanel && enhancedValidation && ( -
- { - const success = await enhancedValidation.saveForm(); - if (success) { - toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"); - } - }} - canSave={enhancedValidation.canSave} - compact={true} - showDetails={false} - /> -
- )} + {/* ๊ฐœ์„ ๋œ ๊ฒ€์ฆ ํŒจ๋„ (์„ ํƒ์  ํ‘œ์‹œ) */} + {showValidationPanel && enhancedValidation && ( +
+ { + const success = await enhancedValidation.saveForm(); + if (success) { + toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"); + } + }} + canSave={enhancedValidation.canSave} + compact={true} + showDetails={false} + /> +
+ )} - {/* ๋ชจ๋‹ฌ ํ™”๋ฉด */} - { - setPopupScreen(null); - setPopupFormData({}); // ํŒ์—… ๋‹ซ์„ ๋•Œ formData๋„ ์ดˆ๊ธฐํ™” - }} - > - - - {popupScreen?.title || "์ƒ์„ธ ์ •๋ณด"} - - -
- {popupLoading ? ( -
-
ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
-
- ) : popupLayout.length > 0 ? ( -
- {/* ํŒ์—…์—์„œ๋„ ์‹ค์ œ ์œ„์น˜์™€ ํฌ๊ธฐ๋กœ ๋ Œ๋”๋ง */} - {popupLayout.map((popupComponent) => ( -
{ + setPopupScreen(null); + setPopupFormData({}); // ํŒ์—… ๋‹ซ์„ ๋•Œ formData๋„ ์ดˆ๊ธฐํ™” + }}> + + + {popupScreen?.title || "์ƒ์„ธ ์ •๋ณด"} + + +
+ {popupLoading ? ( +
+
ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
+
+ ) : popupLayout.length > 0 ? ( +
+ {/* ํŒ์—…์—์„œ๋„ ์‹ค์ œ ์œ„์น˜์™€ ํฌ๊ธฐ๋กœ ๋ Œ๋”๋ง */} + {popupLayout.map((popupComponent) => ( +
+ {/* ๐ŸŽฏ ํ•ต์‹ฌ ์ˆ˜์ •: ํŒ์—… ์ „์šฉ formData ์‚ฌ์šฉ */} + { + console.log("๐Ÿ’พ ํŒ์—… formData ์—…๋ฐ์ดํŠธ:", { + fieldName, + value, + valueType: typeof value, + prevFormData: popupFormData + }); + + setPopupFormData(prev => ({ + ...prev, + [fieldName]: value + })); }} - > - {/* ๐ŸŽฏ ํ•ต์‹ฌ ์ˆ˜์ •: ํŒ์—… ์ „์šฉ formData ์‚ฌ์šฉ */} - { - console.log("๐Ÿ’พ ํŒ์—… formData ์—…๋ฐ์ดํŠธ:", { - fieldName, - value, - valueType: typeof value, - prevFormData: popupFormData, - }); - - setPopupFormData((prev) => ({ - ...prev, - [fieldName]: value, - })); - }} - /> -
- ))} -
- ) : ( -
-
ํ™”๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
-
- )} -
-
-
+ /> +
+ ))} +
+ ) : ( +
+
ํ™”๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
+
+ )} +
+ + diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index fbb2d4ec..af4a4542 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -365,7 +365,6 @@ export const InteractiveScreenViewerDynamic: React.FC 0) ? originalData : formData} // ๐Ÿ†• originalData๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ, ์—†์œผ๋ฉด formData ์‚ฌ์šฉ (์ƒ์„ฑ ๋ชจ๋“œ์—์„œ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ) onFormDataChange={handleFormDataChange} screenId={screenInfo?.id} tableName={screenInfo?.tableName} diff --git a/frontend/components/screen/ScreenSettingModal.tsx b/frontend/components/screen/ScreenSettingModal.tsx index c7373e5c..36e2bbe5 100644 --- a/frontend/components/screen/ScreenSettingModal.tsx +++ b/frontend/components/screen/ScreenSettingModal.tsx @@ -416,6 +416,10 @@ export function ScreenSettingModal({ ๊ฐœ์š” + + + ํ…Œ์ด๋ธ” ์„ค์ • + ์ œ์–ด ๊ด€๋ฆฌ @@ -466,7 +470,22 @@ export function ScreenSettingModal({ /> - {/* ํƒญ 2: ์ œ์–ด ๊ด€๋ฆฌ */} + {/* ํƒญ 2: ํ…Œ์ด๋ธ” ์„ค์ • */} + + {mainTable && ( + {}} // ํƒญ์—์„œ๋Š” ๋‹ซ๊ธฐ ๋ถˆํ•„์š” + tableName={mainTable} + tableLabel={mainTableLabel} + screenId={currentScreenId} + onSaveSuccess={handleRefresh} + isEmbedded={true} // ์ž„๋ฒ ๋“œ ๋ชจ๋“œ + /> + )} + + + {/* ํƒญ 3: ์ œ์–ด ๊ด€๋ฆฌ */} ๋ฉ”์ธ ํ…Œ์ด๋ธ” - {mainTable && ( - - )}
{mainTable ? ( @@ -3742,10 +3758,11 @@ function ControlManagementTab({
{/* ๋ฒ„ํŠผ ํ”„๋ฆฌ๋ทฐ */}
{currentLabel || "๋ฒ„ํŠผ"} @@ -3870,6 +3887,34 @@ function ControlManagementTab({
+ {/* ๋ฒ„ํŠผ ๋ชจ์„œ๋ฆฌ (borderRadius) */} +
+ +
+ + ๋ฒ„ํŠผ ๋ชจ์„œ๋ฆฌ ๋‘ฅ๊ธ€๊ธฐ +
+
+ {/* ํ™•์ธ ๋ฉ”์‹œ์ง€ ์„ค์ • (save/delete ์•ก์…˜์—์„œ๋งŒ ํ‘œ์‹œ) */} {((editedValues[btn.id]?.actionType || btn.actionType) === "save" || (editedValues[btn.id]?.actionType || btn.actionType) === "delete") && ( diff --git a/frontend/components/screen/TableSettingModal.tsx b/frontend/components/screen/TableSettingModal.tsx index f0613b66..c176c25c 100644 --- a/frontend/components/screen/TableSettingModal.tsx +++ b/frontend/components/screen/TableSettingModal.tsx @@ -129,6 +129,7 @@ interface TableSettingModalProps { columns?: ColumnInfo[]; filterColumns?: string[]; onSaveSuccess?: () => void; + isEmbedded?: boolean; // ํƒญ ์•ˆ์— ์ž„๋ฒ ๋“œ ๋ชจ๋“œ๋กœ ํ‘œ์‹œ } // ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ Select ์ปดํฌ๋„ŒํŠธ @@ -256,6 +257,7 @@ export function TableSettingModal({ columns = [], filterColumns = [], onSaveSuccess, + isEmbedded = false, }: TableSettingModalProps) { const [activeTab, setActiveTab] = useState("columns"); const [loading, setLoading] = useState(false); @@ -304,9 +306,19 @@ export function TableSettingModal({ // ์ดˆ๊ธฐ ํŽธ์ง‘ ์ƒํƒœ ์„ค์ • const initialEdits: Record> = {}; columnsData.forEach((col) => { + // referenceTable์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด inputType์€ entity์—ฌ์•ผ ํ•จ + let effectiveInputType = col.inputType || "direct"; + if (col.referenceTable && effectiveInputType !== "entity") { + effectiveInputType = "entity"; + } + // codeCategory/codeValue๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด inputType์€ code์—ฌ์•ผ ํ•จ + if (col.codeCategory && effectiveInputType !== "code") { + effectiveInputType = "code"; + } + initialEdits[col.columnName] = { displayName: col.displayName, - inputType: col.inputType || "direct", + inputType: effectiveInputType, referenceTable: col.referenceTable, referenceColumn: col.referenceColumn, displayColumn: col.displayColumn, @@ -343,10 +355,10 @@ export function TableSettingModal({ try { // ๋ชจ๋“  ํ™”๋ฉด ์กฐํšŒ const screensResponse = await screenApi.getScreens({ size: 1000 }); - if (screensResponse.items) { + if (screensResponse.data) { const usingScreens: ScreenUsingTable[] = []; - screensResponse.items.forEach((screen: any) => { + screensResponse.data.forEach((screen: any) => { // ๋ฉ”์ธ ํ…Œ์ด๋ธ”๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ if (screen.tableName === tableName) { usingScreens.push({ @@ -418,6 +430,35 @@ export function TableSettingModal({ }, })); + // ์ž…๋ ฅ ํƒ€์ž… ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ํ•„๋“œ ์ดˆ๊ธฐํ™” + if (field === "inputType") { + // ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์ฐธ์กฐ ์„ค์ • ์ดˆ๊ธฐํ™” + if (value !== "entity") { + setEditedColumns((prev) => ({ + ...prev, + [columnName]: { + ...prev[columnName], + inputType: value, + referenceTable: "", + referenceColumn: "", + displayColumn: "", + }, + })); + } + // ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์ฝ”๋“œ ์„ค์ • ์ดˆ๊ธฐํ™” + if (value !== "code") { + setEditedColumns((prev) => ({ + ...prev, + [columnName]: { + ...prev[columnName], + inputType: value, + codeCategory: "", + codeValue: "", + }, + })); + } + } + // ์ฐธ์กฐ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ์ฐธ์กฐ ์ปฌ๋Ÿผ ์ดˆ๊ธฐํ™” if (field === "referenceTable") { setEditedColumns((prev) => ({ @@ -452,8 +493,18 @@ export function TableSettingModal({ // detailSettings ์ฒ˜๋ฆฌ (Entity ํƒ€์ž…์ธ ๊ฒฝ์šฐ) let finalDetailSettings = mergedColumn.detailSettings || ""; + + // referenceTable์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด inputType์„ entity๋กœ ์ž๋™ ์„ค์ • + let currentInputType = (mergedColumn.inputType || "") as string; + if (mergedColumn.referenceTable && currentInputType !== "entity") { + currentInputType = "entity"; + } + // codeCategory๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด inputType์„ code๋กœ ์ž๋™ ์„ค์ • + if (mergedColumn.codeCategory && currentInputType !== "code") { + currentInputType = "code"; + } - if (mergedColumn.inputType === "entity" && mergedColumn.referenceTable) { + if (currentInputType === "entity" && mergedColumn.referenceTable) { // ๊ธฐ์กด detailSettings๋ฅผ ํŒŒ์‹ฑํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ์ƒ์„ฑ let existingSettings: Record = {}; if (typeof mergedColumn.detailSettings === "string" && mergedColumn.detailSettings.trim().startsWith("{")) { @@ -479,7 +530,7 @@ export function TableSettingModal({ } // Code ํƒ€์ž…์ธ ๊ฒฝ์šฐ hierarchyRole์„ detailSettings์— ํฌํ•จ - if (mergedColumn.inputType === "code" && (mergedColumn as any).hierarchyRole) { + if (currentInputType === "code" && (mergedColumn as any).hierarchyRole) { let existingSettings: Record = {}; if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) { try { @@ -502,7 +553,7 @@ export function TableSettingModal({ const columnSetting: ColumnSettings = { columnName: columnName, columnLabel: mergedColumn.displayName || originalColumn.displayName || "", - webType: mergedColumn.inputType || originalColumn.inputType || "text", + inputType: currentInputType || "text", // referenceTable/codeCategory๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ ์ž๋™ ๋ณด์ •๋œ ๊ฐ’ ์‚ฌ์šฉ detailSettings: finalDetailSettings, codeCategory: mergedColumn.codeCategory || originalColumn.codeCategory || "", codeValue: mergedColumn.codeValue || originalColumn.codeValue || "", @@ -593,6 +644,158 @@ export function TableSettingModal({ ]; }; + // ์ž„๋ฒ ๋“œ ๋ชจ๋“œ + if (isEmbedded) { + return ( + <> +
+ {/* ํ—ค๋” */} +
+
+ + {tableLabel || tableName} + {tableName !== tableLabel && tableName !== (tableLabel || tableName) && ( + ({tableName}) + )} +
+
+ + + +
+
+
+ {/* ์ขŒ์ธก: ํƒญ (40%) */} +
+ + + + + ์ปฌ๋Ÿผ ์„ค์ • + + + + ํ™”๋ฉด ์—ฐ๋™ + + + + ์ฐธ์กฐ ๊ด€๊ณ„ + + + + + ({ + ...col, + isPK: col.columnName === "id" || col.columnName.endsWith("_id"), + isFK: (col.inputType as string) === "entity", + }))} + editedColumns={editedColumns} + selectedColumn={selectedColumn} + onSelectColumn={setSelectedColumn} + loading={loading} + /> + + + + + + + + + + +
+ + {/* ์šฐ์ธก: ์ƒ์„ธ ์„ค์ • (60%) */} +
+ {selectedColumn && mergedColumns.find((c) => c.columnName === selectedColumn) ? ( + c.columnName === selectedColumn)!} + editedColumn={editedColumns[selectedColumn] || {}} + tableOptions={tableOptions} + inputTypeOptions={inputTypeOptions} + getRefColumnOptions={getRefColumnOptions} + loadingRefColumns={loadingRefColumns} + onColumnChange={(field, value) => handleColumnChange(selectedColumn, field, value)} + /> + ) : ( +
+
+ +

์™ผ์ชฝ์—์„œ ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜๋ฉด

+

์ƒ์„ธ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

+
+
+ )} +
+
+
+ + {/* ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ ๋ชจ๋‹ฌ */} + + +
+

ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ

+ +
+
+ +
+
+
+ + ); + } + + // ๊ธฐ์กด ๋ชจ๋‹ฌ ๋ชจ๋“œ return ( <> @@ -843,6 +1046,7 @@ function ColumnListTab({
{filteredColumns.map((col) => { const edited = editedColumns[col.columnName] || {}; + // editedColumns์—์„œ inputType์„ ๊ฐ€์ ธ์˜ด (์ดˆ๊ธฐํ™” ์‹œ ์ด๋ฏธ ๋ณด์ •๋จ) const inputType = (edited.inputType || col.inputType || "text") as string; const isSelected = selectedColumn === col.columnName; @@ -873,23 +1077,17 @@ function ColumnListTab({ PK )} - {col.isFK && ( - - - FK - - )} - {(edited.referenceTable || col.referenceTable) && ( + {/* ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์ด๊ฑฐ๋‚˜ referenceTable์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ์กฐ์ธ ๋ฐฐ์ง€ ํ‘œ์‹œ (FK์™€ ๋™์ผ ์˜๋ฏธ) */} + {(inputType === "entity" || edited.referenceTable || col.referenceTable) && ( + ์กฐ์ธ )}
-
+
{col.columnName} - โ€ข - {col.dataType}
); @@ -925,10 +1123,11 @@ function ColumnDetailPanel({ onColumnChange, }: ColumnDetailPanelProps) { const currentLabel = editedColumn.displayName ?? columnInfo.displayName ?? ""; - const currentInputType = (editedColumn.inputType ?? columnInfo.inputType ?? "text") as string; const currentRefTable = editedColumn.referenceTable ?? columnInfo.referenceTable ?? ""; const currentRefColumn = editedColumn.referenceColumn ?? columnInfo.referenceColumn ?? ""; const currentDisplayColumn = editedColumn.displayColumn ?? columnInfo.displayColumn ?? ""; + // editedColumn์—์„œ inputType์„ ๊ฐ€์ ธ์˜ด (์ดˆ๊ธฐํ™” ์‹œ ์ด๋ฏธ ๋ณด์ •๋จ) + const currentInputType = (editedColumn.inputType ?? columnInfo.inputType ?? "text") as string; return (
@@ -948,9 +1147,10 @@ function ColumnDetailPanel({ Primary Key )} - {columnInfo.isFK && ( - - Foreign Key + {/* ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์ด๊ฑฐ๋‚˜ referenceTable์ด ์žˆ์œผ๋ฉด ์กฐ์ธ ๋ฐฐ์ง€ ํ‘œ์‹œ */} + {(currentInputType === "entity" || currentRefTable) && ( + + ์กฐ์ธ )}
diff --git a/frontend/lib/api/entityJoin.ts b/frontend/lib/api/entityJoin.ts index ddb00ac7..cbcab931 100644 --- a/frontend/lib/api/entityJoin.ts +++ b/frontend/lib/api/entityJoin.ts @@ -77,12 +77,6 @@ export const entityJoinApi = { filterColumn?: string; filterValue?: any; }; // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) - deduplication?: { - enabled: boolean; - groupByColumn: string; - keepStrategy: "latest" | "earliest" | "base_price" | "current_date"; - sortColumn?: string; - }; // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • companyCodeOverride?: string; // ๐Ÿ†• ํ”„๋ฆฌ๋ทฐ์šฉ ํšŒ์‚ฌ ์ฝ”๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ (์ตœ๊ณ  ๊ด€๋ฆฌ์ž๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) } = {}, ): Promise => { @@ -116,7 +110,6 @@ export const entityJoinApi = { autoFilter: JSON.stringify(autoFilter), // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ๋ง (์˜ค๋ฒ„๋ผ์ด๋“œ ํฌํ•จ) dataFilter: params.dataFilter ? JSON.stringify(params.dataFilter) : undefined, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ excludeFilter: params.excludeFilter ? JSON.stringify(params.excludeFilter) : undefined, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ - deduplication: params.deduplication ? JSON.stringify(params.deduplication) : undefined, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • }, }); return response.data.data; diff --git a/frontend/lib/api/tableManagement.ts b/frontend/lib/api/tableManagement.ts index 5953fd82..24ef25a0 100644 --- a/frontend/lib/api/tableManagement.ts +++ b/frontend/lib/api/tableManagement.ts @@ -13,7 +13,7 @@ export interface ColumnTypeInfo { dataType: string; dbType: string; webType: string; - inputType?: "direct" | "auto"; + inputType?: string; // text, number, entity, code, select, date, checkbox ๋“ฑ detailSettings: string; description?: string; isNullable: string; @@ -39,11 +39,11 @@ export interface TableInfo { columnCount: number; } -// ์ปฌ๋Ÿผ ์„ค์ • ํƒ€์ž… +// ์ปฌ๋Ÿผ ์„ค์ • ํƒ€์ž… (๋ฐฑ์—”๋“œ API์™€ ๋™์ผํ•œ ํ•„๋“œ๋ช… ์‚ฌ์šฉ) export interface ColumnSettings { columnName?: string; columnLabel: string; - webType: string; + inputType: string; // ๋ฐฑ์—”๋“œ์—์„œ inputType์œผ๋กœ ๋ฐ›์Œ detailSettings: string; codeCategory: string; codeValue: string; diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index e9bd91cb..fc6c2263 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -27,7 +27,6 @@ import { useScreenContextOptional } from "@/contexts/ScreenContext"; import { useSplitPanelContext, SplitPanelPosition } from "@/contexts/SplitPanelContext"; import { applyMappingRules } from "@/lib/utils/dataMapping"; import { apiClient } from "@/lib/api/client"; -import { useScreenMultiLang } from "@/contexts/ScreenMultiLangContext"; export interface ButtonPrimaryComponentProps extends ComponentRendererProps { config?: ButtonPrimaryConfig; @@ -108,7 +107,6 @@ export const ButtonPrimaryComponent: React.FC = ({ const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ const screenContext = useScreenContextOptional(); // ํ™”๋ฉด ์ปจํ…์ŠคํŠธ const splitPanelContext = useSplitPanelContext(); // ๋ถ„ํ•  ํŒจ๋„ ์ปจํ…์ŠคํŠธ - const { getTranslatedText } = useScreenMultiLang(); // ๋‹ค๊ตญ์–ด ์ปจํ…์ŠคํŠธ // ๐Ÿ†• ScreenContext์—์„œ splitPanelPosition ๊ฐ€์ ธ์˜ค๊ธฐ (์ค‘์ฒฉ ํ™”๋ฉด์—์„œ๋„ ์ž‘๋™) const splitPanelPosition = screenContext?.splitPanelPosition; @@ -301,20 +299,6 @@ export const ButtonPrimaryComponent: React.FC = ({ // ๐Ÿ†• modalDataStore์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ํ™•์ธ (๋ถ„ํ•  ํŒจ๋„ ๋“ฑ์—์„œ ์ €์žฅ๋จ) const [modalStoreData, setModalStoreData] = useState>({}); - // ๐Ÿ†• splitPanelContext?.selectedLeftData๋ฅผ ๋กœ์ปฌ ์ƒํƒœ๋กœ ์ถ”์  (๋ฆฌ๋ Œ๋”๋ง ๋ณด์žฅ) - const [trackedSelectedLeftData, setTrackedSelectedLeftData] = useState | null>(null); - - // splitPanelContext?.selectedLeftData ๋ณ€๊ฒฝ ๊ฐ์ง€ ๋ฐ ๋กœ์ปฌ ์ƒํƒœ ๋™๊ธฐํ™” - useEffect(() => { - const newData = splitPanelContext?.selectedLeftData ?? null; - setTrackedSelectedLeftData(newData); - // console.log("๐Ÿ”„ [ButtonPrimary] selectedLeftData ๋ณ€๊ฒฝ ๊ฐ์ง€:", { - // label: component.label, - // hasData: !!newData, - // dataKeys: newData ? Object.keys(newData) : [], - // }); - }, [splitPanelContext?.selectedLeftData, component.label]); - // modalDataStore ์ƒํƒœ ๊ตฌ๋… (์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ) useEffect(() => { const actionConfig = component.componentConfig?.action; @@ -373,8 +357,8 @@ export const ButtonPrimaryComponent: React.FC = ({ // 2. ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ์„ ํƒ ๋ฐ์ดํ„ฐ ํ™•์ธ if (rowSelectionSource === "auto" || rowSelectionSource === "splitPanelLeft") { - // SplitPanelContext์—์„œ ํ™•์ธ (trackedSelectedLeftData ์‚ฌ์šฉ์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง ๋ณด์žฅ) - if (trackedSelectedLeftData && Object.keys(trackedSelectedLeftData).length > 0) { + // SplitPanelContext์—์„œ ํ™•์ธ + if (splitPanelContext?.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0) { if (!hasSelection) { hasSelection = true; selectionCount = 1; @@ -413,7 +397,7 @@ export const ButtonPrimaryComponent: React.FC = ({ selectionCount, selectionSource, hasSplitPanelContext: !!splitPanelContext, - trackedSelectedLeftData: trackedSelectedLeftData, + selectedLeftData: splitPanelContext?.selectedLeftData, selectedRowsData: selectedRowsData?.length, selectedRows: selectedRows?.length, flowSelectedData: flowSelectedData?.length, @@ -445,7 +429,7 @@ export const ButtonPrimaryComponent: React.FC = ({ component.label, selectedRows, selectedRowsData, - trackedSelectedLeftData, + splitPanelContext?.selectedLeftData, flowSelectedData, splitPanelContext, modalStoreData, @@ -737,99 +721,61 @@ export const ButtonPrimaryComponent: React.FC = ({ return; } + if (!screenContext) { + toast.error("ํ™”๋ฉด ์ปจํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + return; + } + try { - let sourceData: any[] = []; + // 1. ์†Œ์Šค ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + let sourceProvider = screenContext.getDataProvider(dataTransferConfig.sourceComponentId); - // 1. ScreenContext์—์„œ DataProvider๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ๋„ - if (screenContext) { - let sourceProvider = screenContext.getDataProvider(dataTransferConfig.sourceComponentId); + // ๐Ÿ†• ์†Œ์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉด, ํ˜„์žฌ ํ™”๋ฉด์—์„œ ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ž๋™ ํƒ์ƒ‰ + // (์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ์˜ ๋‹ค๋ฅธ ์„น์…˜์œผ๋กœ ์ „ํ™˜ํ–ˆ์„ ๋•Œ ์ด์ „ ์ปดํฌ๋„ŒํŠธ ID๊ฐ€ ๋‚จ์•„์žˆ๋Š” ๊ฒฝ์šฐ ๋Œ€์‘) + if (!sourceProvider) { + console.log(`โš ๏ธ [ButtonPrimary] ์ง€์ •๋œ ์†Œ์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: ${dataTransferConfig.sourceComponentId}`); + console.log(`๐Ÿ” [ButtonPrimary] ํ˜„์žฌ ํ™”๋ฉด์—์„œ DataProvider ์ž๋™ ํƒ์ƒ‰...`); + + const allProviders = screenContext.getAllDataProviders(); + + // ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์šฐ์„  ํƒ์ƒ‰ + for (const [id, provider] of allProviders) { + if (provider.componentType === "table-list") { + sourceProvider = provider; + console.log(`โœ… [ButtonPrimary] ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ž๋™ ๋ฐœ๊ฒฌ: ${id}`); + break; + } + } + + // ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ DataProvider ์‚ฌ์šฉ + if (!sourceProvider && allProviders.size > 0) { + const firstEntry = allProviders.entries().next().value; + if (firstEntry) { + sourceProvider = firstEntry[1]; + console.log( + `โœ… [ButtonPrimary] ์ฒซ ๋ฒˆ์งธ DataProvider ์‚ฌ์šฉ: ${firstEntry[0]} (${sourceProvider.componentType})`, + ); + } + } - // ์†Œ์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉด, ํ˜„์žฌ ํ™”๋ฉด์—์„œ ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ž๋™ ํƒ์ƒ‰ if (!sourceProvider) { - console.log(`โš ๏ธ [ButtonPrimary] ์ง€์ •๋œ ์†Œ์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: ${dataTransferConfig.sourceComponentId}`); - console.log(`๐Ÿ” [ButtonPrimary] ํ˜„์žฌ ํ™”๋ฉด์—์„œ DataProvider ์ž๋™ ํƒ์ƒ‰...`); - - const allProviders = screenContext.getAllDataProviders(); - console.log(`๐Ÿ“‹ [ButtonPrimary] ๋“ฑ๋ก๋œ DataProvider ๋ชฉ๋ก:`, Array.from(allProviders.keys())); - - // ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์šฐ์„  ํƒ์ƒ‰ - for (const [id, provider] of allProviders) { - if (provider.componentType === "table-list") { - sourceProvider = provider; - console.log(`โœ… [ButtonPrimary] ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ ์ž๋™ ๋ฐœ๊ฒฌ: ${id}`); - break; - } - } - - // ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ DataProvider ์‚ฌ์šฉ - if (!sourceProvider && allProviders.size > 0) { - const firstEntry = allProviders.entries().next().value; - if (firstEntry) { - sourceProvider = firstEntry[1]; - console.log( - `โœ… [ButtonPrimary] ์ฒซ ๋ฒˆ์งธ DataProvider ์‚ฌ์šฉ: ${firstEntry[0]} (${sourceProvider.componentType})`, - ); - } - } - } - - if (sourceProvider) { - const rawSourceData = sourceProvider.getSelectedData(); - sourceData = Array.isArray(rawSourceData) ? rawSourceData : rawSourceData ? [rawSourceData] : []; - console.log("๐Ÿ“ฆ [ButtonPrimary] ScreenContext์—์„œ ์†Œ์Šค ๋ฐ์ดํ„ฐ ํš๋“:", { - rawSourceData, - sourceData, - count: sourceData.length - }); - } - } else { - console.log("โš ๏ธ [ButtonPrimary] ScreenContext๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. modalDataStore์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค."); - } - - // 2. ScreenContext์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ, modalDataStore์—์„œ fallback ์กฐํšŒ - if (sourceData.length === 0) { - console.log("๐Ÿ” [ButtonPrimary] modalDataStore์—์„œ ๋ฐ์ดํ„ฐ ํƒ์ƒ‰ ์‹œ๋„..."); - - try { - const { useModalDataStore } = await import("@/stores/modalDataStore"); - const dataRegistry = useModalDataStore.getState().dataRegistry; - - console.log("๐Ÿ“‹ [ButtonPrimary] modalDataStore ์ „์ฒด ํ‚ค:", Object.keys(dataRegistry)); - - // sourceTableName์ด ์ง€์ •๋˜์–ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒ - const sourceTableName = dataTransferConfig.sourceTableName || tableName; - - if (sourceTableName && dataRegistry[sourceTableName]) { - const modalData = dataRegistry[sourceTableName]; - sourceData = modalData.map((item: any) => item.originalData || item); - console.log(`โœ… [ButtonPrimary] modalDataStore์—์„œ ๋ฐ์ดํ„ฐ ๋ฐœ๊ฒฌ (${sourceTableName}):`, sourceData.length, "๊ฑด"); - } else { - // ํ…Œ์ด๋ธ”๋ช…์œผ๋กœ ๋ชป ์ฐพ์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ - const firstKey = Object.keys(dataRegistry)[0]; - if (firstKey && dataRegistry[firstKey]?.length > 0) { - const modalData = dataRegistry[firstKey]; - sourceData = modalData.map((item: any) => item.originalData || item); - console.log(`โœ… [ButtonPrimary] modalDataStore ์ฒซ ๋ฒˆ์งธ ํ‚ค์—์„œ ๋ฐ์ดํ„ฐ ๋ฐœ๊ฒฌ (${firstKey}):`, sourceData.length, "๊ฑด"); - } - } - } catch (err) { - console.warn("โš ๏ธ [ButtonPrimary] modalDataStore ์ ‘๊ทผ ์‹คํŒจ:", err); + toast.error("๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + return; } } - // 3. ์—ฌ์ „ํžˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ + const rawSourceData = sourceProvider.getSelectedData(); + + // ๐Ÿ†• ๋ฐฐ์—ด์ด ์•„๋‹Œ ๊ฒฝ์šฐ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ + const sourceData = Array.isArray(rawSourceData) ? rawSourceData : rawSourceData ? [rawSourceData] : []; + + console.log("๐Ÿ“ฆ ์†Œ์Šค ๋ฐ์ดํ„ฐ:", { rawSourceData, sourceData, isArray: Array.isArray(rawSourceData) }); + if (!sourceData || sourceData.length === 0) { - console.error("โŒ [ButtonPrimary] ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", { - hasScreenContext: !!screenContext, - sourceComponentId: dataTransferConfig.sourceComponentId, - sourceTableName: dataTransferConfig.sourceTableName || tableName, - }); - toast.warning("์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + toast.warning("์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); return; } - console.log("๐Ÿ“ฆ [ButtonPrimary] ์ตœ์ข… ์†Œ์Šค ๋ฐ์ดํ„ฐ:", { sourceData, count: sourceData.length }); - // 1.5. ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ฒ˜๋ฆฌ (์˜ˆ: ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’) let additionalData: Record = {}; @@ -1360,10 +1306,7 @@ export const ButtonPrimaryComponent: React.FC = ({ ...userStyle, }; - // ๋‹ค๊ตญ์–ด ์ ์šฉ: componentConfig.langKey๊ฐ€ ์žˆ์œผ๋ฉด ๋ฒˆ์—ญ ํ…์ŠคํŠธ ์‚ฌ์šฉ - const langKey = (component as any).componentConfig?.langKey; - const originalButtonText = processedConfig.text !== undefined ? processedConfig.text : component.label || "๋ฒ„ํŠผ"; - const buttonContent = getTranslatedText(langKey, originalButtonText); + const buttonContent = processedConfig.text !== undefined ? processedConfig.text : component.label || "๋ฒ„ํŠผ"; return ( <> diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index a84c90fd..cbc7ea4f 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -32,13 +32,11 @@ import { DialogFooter, DialogDescription, } from "@/components/ui/dialog"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { useTableOptions } from "@/contexts/TableOptionsContext"; import { TableFilter, ColumnVisibility, GroupSumConfig } from "@/types/table-options"; import { useAuth } from "@/hooks/useAuth"; import { useSplitPanel } from "./SplitPanelContext"; -import { useScreenMultiLang } from "@/contexts/ScreenMultiLangContext"; export interface SplitPanelLayoutComponentProps extends ComponentRendererProps { // ์ถ”๊ฐ€ props @@ -59,8 +57,6 @@ export const SplitPanelLayoutComponent: React.FC const componentConfig = (component.componentConfig || {}) as SplitPanelLayoutConfig; // ๐Ÿ†• ํ”„๋ฆฌ๋ทฐ์šฉ ํšŒ์‚ฌ ์ฝ”๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ (์ตœ๊ณ  ๊ด€๋ฆฌ์ž๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) const companyCode = (props as any).companyCode as string | undefined; - // ๐ŸŒ ๋‹ค๊ตญ์–ด ์ปจํ…์ŠคํŠธ - const { getTranslatedText } = useScreenMultiLang(); // ๊ธฐ๋ณธ ์„ค์ •๊ฐ’ const splitRatio = componentConfig.splitRatio || 30; @@ -141,13 +137,6 @@ export const SplitPanelLayoutComponent: React.FC if (item[underscoreKey] !== undefined) { return item[underscoreKey]; } - - // 6๏ธโƒฃ ๐Ÿ†• ๋ชจ๋“  ํ‚ค์—์„œ _fieldName์œผ๋กœ ๋๋‚˜๋Š” ํ‚ค ์ฐพ๊ธฐ - // ์˜ˆ: partner_id_customer_name (ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ customer_id๋กœ ์ถ”๋ก ํ–ˆ์ง€๋งŒ ์‹ค์ œ๋Š” partner_id์ธ ๊ฒฝ์šฐ) - const matchingKey = Object.keys(item).find((key) => key.endsWith(`_${fieldName}`)); - if (matchingKey && item[matchingKey] !== undefined) { - return item[matchingKey]; - } } return undefined; @@ -175,11 +164,6 @@ export const SplitPanelLayoutComponent: React.FC const [rightSearchQuery, setRightSearchQuery] = useState(""); const [isLoadingLeft, setIsLoadingLeft] = useState(false); const [isLoadingRight, setIsLoadingRight] = useState(false); - - // ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ ๊ด€๋ จ ์ƒํƒœ - const [activeTabIndex, setActiveTabIndex] = useState(0); // 0 = ๊ธฐ๋ณธ ํƒญ (์šฐ์ธก ํŒจ๋„), 1+ = ์ถ”๊ฐ€ ํƒญ - const [tabsData, setTabsData] = useState>({}); // ํƒญ๋ณ„ ๋ฐ์ดํ„ฐ ์บ์‹œ - const [tabsLoading, setTabsLoading] = useState>({}); // ํƒญ๋ณ„ ๋กœ๋”ฉ ์ƒํƒœ const [rightTableColumns, setRightTableColumns] = useState([]); // ์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด const [expandedItems, setExpandedItems] = useState>(new Set()); // ํŽผ์ณ์ง„ ํ•ญ๋ชฉ๋“ค const [leftColumnLabels, setLeftColumnLabels] = useState>({}); // ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ @@ -190,10 +174,6 @@ export const SplitPanelLayoutComponent: React.FC const [rightCategoryMappings, setRightCategoryMappings] = useState< Record> >({}); // ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ - - // ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ ๋ผ๋ฒจ ์บ์‹œ (CATEGORY_* ์ฝ”๋“œ -> ๋ผ๋ฒจ) - const [categoryCodeLabels, setCategoryCodeLabels] = useState>({}); - const { toast } = useToast(); // ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์ƒํƒœ @@ -227,12 +207,12 @@ export const SplitPanelLayoutComponent: React.FC const splitPanelId = `split-panel-${component.id}`; // ๋””๋ฒ„๊น…: Context ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ - // console.log("๐Ÿ”— [SplitPanelLayout] Context ์—ฐ๊ฒฐ ์ƒํƒœ:", { - // componentId: component.id, - // splitPanelId, - // hasRegisterFunc: typeof ctxRegisterSplitPanel === "function", - // splitPanelsSize: splitPanelContext.splitPanels?.size ?? "์—†์Œ", - // }); + console.log("๐Ÿ”— [SplitPanelLayout] Context ์—ฐ๊ฒฐ ์ƒํƒœ:", { + componentId: component.id, + splitPanelId, + hasRegisterFunc: typeof ctxRegisterSplitPanel === "function", + splitPanelsSize: splitPanelContext.splitPanels?.size ?? "์—†์Œ", + }); // Context์— ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ๋ก (์ขŒํ‘œ ์ •๋ณด ํฌํ•จ) - ๋งˆ์šดํŠธ ์‹œ 1ํšŒ๋งŒ ์‹คํ–‰ const ctxRegisterRef = useRef(ctxRegisterSplitPanel); @@ -257,15 +237,15 @@ export const SplitPanelLayoutComponent: React.FC isDragging: false, }; - // console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์— ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ๋ก:", { - // splitPanelId, - // panelInfo, - // }); + console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์— ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ๋ก:", { + splitPanelId, + panelInfo, + }); ctxRegisterRef.current(splitPanelId, panelInfo); return () => { - // console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์—์„œ ๋ถ„ํ•  ํŒจ๋„ ํ•ด์ œ:", splitPanelId); + console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์—์„œ ๋ถ„ํ•  ํŒจ๋„ ํ•ด์ œ:", splitPanelId); ctxUnregisterRef.current(splitPanelId); }; // ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰, ์œ„์น˜/ํฌ๊ธฐ ๋ณ€๊ฒฝ์€ ๋ณ„๋„ ์—…๋ฐ์ดํŠธ๋กœ ์ฒ˜๋ฆฌ @@ -333,11 +313,11 @@ export const SplitPanelLayoutComponent: React.FC // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ๋œ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ const summedLeftData = useMemo(() => { - // console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] leftGroupSumConfig:", leftGroupSumConfig); + console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] leftGroupSumConfig:", leftGroupSumConfig); // ๊ทธ๋ฃนํ•‘์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ๊ฑฐ๋‚˜ ๊ทธ๋ฃน ๊ธฐ์ค€ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ if (!leftGroupSumConfig?.enabled || !leftGroupSumConfig?.groupByColumn) { - // console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] ๊ทธ๋ฃนํ•‘ ๋น„ํ™œ์„ฑํ™” - ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜"); + console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] ๊ทธ๋ฃนํ•‘ ๋น„ํ™œ์„ฑํ™” - ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜"); return leftData; } @@ -625,41 +605,6 @@ export const SplitPanelLayoutComponent: React.FC return result; }, []); - // ๐Ÿ†• ๊ฐ„๋‹จํ•œ ๊ฐ’ ํฌ๋งทํŒ… ํ•จ์ˆ˜ (์ถ”๊ฐ€ ํƒญ์šฉ) - const formatValue = useCallback( - ( - value: any, - format?: { - type?: "number" | "currency" | "date" | "text"; - thousandSeparator?: boolean; - decimalPlaces?: number; - prefix?: string; - suffix?: string; - dateFormat?: string; - }, - ): string => { - if (value === null || value === undefined) return "-"; - - // ๋‚ ์งœ ํฌ๋งท - if (format?.type === "date" || format?.dateFormat) { - return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD"); - } - - // ์ˆซ์ž ํฌ๋งท - if ( - format?.type === "number" || - format?.type === "currency" || - format?.thousandSeparator || - format?.decimalPlaces !== undefined - ) { - return formatNumberValue(value, format); - } - - return String(value); - }, - [formatDateValue, formatNumberValue], - ); - // ์…€ ๊ฐ’ ํฌ๋งทํŒ… ํ•จ์ˆ˜ (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ฒ˜๋ฆฌ + ๋‚ ์งœ/์ˆซ์ž ํฌ๋งท) const formatCellValue = useCallback( ( @@ -722,14 +667,6 @@ export const SplitPanelLayoutComponent: React.FC ); } - // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ ํŒจํ„ด ๊ฐ์ง€ (CATEGORY_๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’) - if (typeof value === "string" && value.startsWith("CATEGORY_")) { - const cachedLabel = categoryCodeLabels[value]; - if (cachedLabel) { - return {cachedLabel}; - } - } - // ๐Ÿ†• ์ž๋™ ๋‚ ์งœ ๊ฐ์ง€ (ISO 8601 ํ˜•์‹ ๋˜๋Š” Date ๊ฐ์ฒด) if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) { return formatDateValue(value, "YYYY-MM-DD"); @@ -751,7 +688,7 @@ export const SplitPanelLayoutComponent: React.FC // ์ผ๋ฐ˜ ๊ฐ’ return String(value); }, - [formatDateValue, formatNumberValue, categoryCodeLabels], + [formatDateValue, formatNumberValue], ); // ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ @@ -821,8 +758,8 @@ export const SplitPanelLayoutComponent: React.FC } }); - // console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] additionalJoinColumns:", additionalJoinColumns); - // console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] configuredColumns:", configuredColumns); + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] additionalJoinColumns:", additionalJoinColumns); + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] configuredColumns:", configuredColumns); const result = await entityJoinApi.getTableDataWithJoins(leftTableName, { page: 1, @@ -835,10 +772,10 @@ export const SplitPanelLayoutComponent: React.FC }); // ๐Ÿ” ๋””๋ฒ„๊น…: API ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ํ‚ค ํ™•์ธ - // if (result.data && result.data.length > 0) { - // console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ํ‚ค:", Object.keys(result.data[0])); - // console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ:", result.data[0]); - // } + if (result.data && result.data.length > 0) { + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ํ‚ค:", Object.keys(result.data[0])); + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ:", result.data[0]); + } // ๊ฐ€๋‚˜๋‹ค์ˆœ ์ •๋ ฌ (์ขŒ์ธก ํŒจ๋„์˜ ํ‘œ์‹œ ์ปฌ๋Ÿผ ๊ธฐ์ค€) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; @@ -897,8 +834,7 @@ export const SplitPanelLayoutComponent: React.FC companyCodeOverride: companyCode, // ๐Ÿ†• ํ”„๋ฆฌ๋ทฐ์šฉ ํšŒ์‚ฌ ์ฝ”๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ }); - // result.data๊ฐ€ EntityJoinResponse์˜ ์‹ค์ œ ๋ฐฐ์—ด ํ•„๋“œ - const detail = result.data && result.data.length > 0 ? result.data[0] : null; + const detail = result.items && result.items.length > 0 ? result.items[0] : null; setRightData(detail); } else if (relationshipType === "join") { // ์กฐ์ธ ๋ชจ๋“œ: ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๊ด€๋ จ ๋ฐ์ดํ„ฐ (์—ฌ๋Ÿฌ ๊ฐœ) @@ -968,68 +904,26 @@ export const SplitPanelLayoutComponent: React.FC return; } - // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์ž๋™ ๊ฐ์ง€ ๋กœ์ง ๊ฐœ์„  - // 1. ์„ค์ •๋œ keys๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ - // 2. ์—†์œผ๋ฉด ํ…Œ์ด๋ธ” ํƒ€์ž…๊ด€๋ฆฌ์—์„œ ์ •์˜๋œ ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ์กฐํšŒ - let effectiveKeys = keys || []; - - if (effectiveKeys.length === 0 && leftTable && rightTableName) { - // ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์ž๋™ ๊ฐ์ง€ - console.log("๐Ÿ” [๋ถ„ํ• ํŒจ๋„] ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ์ž๋™ ๊ฐ์ง€ ์‹œ์ž‘:", leftTable, "->", rightTableName); - const { tableManagementApi } = await import("@/lib/api/tableManagement"); - const relResponse = await tableManagementApi.getTableEntityRelations(leftTable, rightTableName); - - if (relResponse.success && relResponse.data?.relations && relResponse.data.relations.length > 0) { - effectiveKeys = relResponse.data.relations.map((rel) => ({ - leftColumn: rel.leftColumn, - rightColumn: rel.rightColumn, - })); - console.log("โœ… [๋ถ„ํ• ํŒจ๋„] ์ž๋™ ๊ฐ์ง€๋œ ๊ด€๊ณ„:", effectiveKeys); - } - } - - if (effectiveKeys.length > 0 && leftTable) { + // ๐Ÿ†• ๋ณตํ•ฉํ‚ค ์ง€์› + if (keys && keys.length > 0 && leftTable) { // ๋ณตํ•ฉํ‚ค: ์—ฌ๋Ÿฌ ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋ง const { entityJoinApi } = await import("@/lib/api/entityJoin"); - // ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด ์ƒ์„ฑ (๋‹ค์ค‘ ๊ฐ’ ์ง€์›) - // ๐Ÿ†• ํ•ญ์ƒ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌํ•˜์—ฌ ๋ฐฑ์—”๋“œ์—์„œ ๋‹ค์ค‘ ๊ฐ’ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰์„ ์ง€์›ํ•˜๋„๋ก ํ•จ - // ์˜ˆ: ์ขŒ์ธก์—์„œ "2"๋ฅผ ์„ ํƒํ•ด๋„, ์šฐ์ธก์—์„œ "2,3"์„ ๊ฐ€์ง„ ํ–‰์ด ํ‘œ์‹œ๋˜๋„๋ก + // ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด ์ƒ์„ฑ const searchConditions: Record = {}; - effectiveKeys.forEach((key) => { + keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - const leftValue = leftItem[key.leftColumn]; - // ๋‹ค์ค‘ ๊ฐ’ ์ง€์›: ๋ชจ๋“  ๊ฐ’์„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋‹ค์ค‘ ๊ฐ’ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰ ํ™œ์„ฑํ™” - if (typeof leftValue === "string") { - if (leftValue.includes(",")) { - // "2,3" ํ˜•ํƒœ๋ฉด ๋ถ„๋ฆฌํ•ด์„œ ๋ฐฐ์—ด๋กœ - const values = leftValue - .split(",") - .map((v: string) => v.trim()) - .filter((v: string) => v); - searchConditions[key.rightColumn] = values; - console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋‹ค์ค‘ ๊ฐ’ ๊ฒ€์ƒ‰ (๋ถ„๋ฆฌ):", key.rightColumn, "=", values); - } else { - // ๋‹จ์ผ ๊ฐ’๋„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ (์šฐ์ธก์— "2,3" ๊ฐ™์€ ๋‹ค์ค‘ ๊ฐ’์ด ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ) - searchConditions[key.rightColumn] = [leftValue.trim()]; - console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋‹ค์ค‘ ๊ฐ’ ๊ฒ€์ƒ‰ (๋‹จ์ผ):", key.rightColumn, "=", [leftValue.trim()]); - } - } else { - // ์ˆซ์ž๋‚˜ ๋‹ค๋ฅธ ํƒ€์ž…์€ ๋ฐฐ์—ด๋กœ ๊ฐ์‹ธ๊ธฐ - searchConditions[key.rightColumn] = [leftValue]; - console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋‹ค์ค‘ ๊ฐ’ ๊ฒ€์ƒ‰ (๊ธฐํƒ€):", key.rightColumn, "=", [leftValue]); - } + searchConditions[key.rightColumn] = leftItem[key.leftColumn]; } }); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด:", searchConditions); - // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API๋กœ ๋ฐ์ดํ„ฐ ์กฐํšŒ (๐Ÿ†• deduplication ์ „๋‹ฌ) + // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API๋กœ ๋ฐ์ดํ„ฐ ์กฐํšŒ const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { search: searchConditions, enableEntityJoin: true, size: 1000, - deduplication: componentConfig.rightPanel?.deduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ „๋‹ฌ companyCodeOverride: companyCode, // ๐Ÿ†• ํ”„๋ฆฌ๋ทฐ์šฉ ํšŒ์‚ฌ ์ฝ”๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ }); @@ -1038,14 +932,10 @@ export const SplitPanelLayoutComponent: React.FC // ์ถ”๊ฐ€ dataFilter ์ ์šฉ let filteredData = result.data || []; const dataFilter = componentConfig.rightPanel?.dataFilter; - // filters ๋˜๋Š” conditions ๋ฐฐ์—ด ์ง€์› (DataFilterConfigPanel์€ filters ์‚ฌ์šฉ) - const filterConditions = dataFilter?.filters || dataFilter?.conditions || []; - if (dataFilter?.enabled && filterConditions.length > 0) { + if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { filteredData = filteredData.filter((item: any) => { - return filterConditions.every((cond: any) => { - // columnName ๋˜๋Š” column ์ง€์› - const columnName = cond.columnName || cond.column; - const value = item[columnName]; + return dataFilter.conditions.every((cond: any) => { + const value = item[cond.column]; const condValue = cond.value; switch (cond.operator) { case "equals": @@ -1054,12 +944,6 @@ export const SplitPanelLayoutComponent: React.FC return value !== condValue; case "contains": return String(value).includes(String(condValue)); - case "is_null": - case "NULL": - return value === null || value === undefined || value === ""; - case "is_not_null": - case "NOT NULL": - return value !== null && value !== undefined && value !== ""; default: return true; } @@ -1069,7 +953,7 @@ export const SplitPanelLayoutComponent: React.FC setRightData(filteredData); } else { - // ๋‹จ์ผํ‚ค (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) ๋˜๋Š” ๊ด€๊ณ„๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ + // ๋‹จ์ผํ‚ค (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; const rightColumn = componentConfig.rightPanel?.relation?.foreignKey; @@ -1087,9 +971,6 @@ export const SplitPanelLayoutComponent: React.FC componentConfig.rightPanel?.deduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ „๋‹ฌ ); setRightData(joinedData || []); // ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ (๋ฐฐ์—ด) - } else { - console.warn("โš ๏ธ [๋ถ„ํ• ํŒจ๋„] ํ…Œ์ด๋ธ” ๊ด€๊ณ„๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", leftTable, "->", rightTableName); - setRightData([]); } } } @@ -1113,294 +994,23 @@ export const SplitPanelLayoutComponent: React.FC ], ); - // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ ๋ผ๋ฒจ ๋กœ๋“œ (rightData ๋ณ€๊ฒฝ ์‹œ) - useEffect(() => { - const loadCategoryCodeLabels = async () => { - if (!rightData) return; - - const categoryCodes = new Set(); - - // rightData๊ฐ€ ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ (์กฐ์ธ ๋ชจ๋“œ) - const dataArray = Array.isArray(rightData) ? rightData : [rightData]; - - dataArray.forEach((row: Record) => { - if (row) { - Object.values(row).forEach((value) => { - if (typeof value === "string" && value.startsWith("CATEGORY_")) { - categoryCodes.add(value); - } - }); - } - }); - - // ์ƒˆ๋กœ์šด ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ๋งŒ ํ•„ํ„ฐ๋ง (์ด๋ฏธ ์บ์‹œ๋œ ๊ฒƒ ์ œ์™ธ) - const newCodes = Array.from(categoryCodes).filter((code) => !categoryCodeLabels[code]); - - if (newCodes.length > 0) { - try { - console.log("๐Ÿท๏ธ [SplitPanel] ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ ๋ผ๋ฒจ ์กฐํšŒ:", newCodes); - const response = await apiClient.post("/table-categories/labels-by-codes", { valueCodes: newCodes }); - if (response.data.success && response.data.data) { - console.log("๐Ÿท๏ธ [SplitPanel] ์นดํ…Œ๊ณ ๋ฆฌ ๋ผ๋ฒจ ์กฐํšŒ ๊ฒฐ๊ณผ:", response.data.data); - setCategoryCodeLabels((prev) => ({ - ...prev, - ...response.data.data, - })); - } - } catch (error) { - console.error("์นดํ…Œ๊ณ ๋ฆฌ ๋ผ๋ฒจ ์กฐํšŒ ์‹คํŒจ:", error); - } - } - }; - - loadCategoryCodeLabels(); - }, [rightData]); - - // ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ํ•จ์ˆ˜ - const loadTabData = useCallback( - async (tabIndex: number, leftItem: any) => { - console.log(`๐Ÿ“ฅ loadTabData ํ˜ธ์ถœ๋จ: tabIndex=${tabIndex}`, { - leftItem: leftItem ? Object.keys(leftItem) : null, - additionalTabs: componentConfig.rightPanel?.additionalTabs?.length, - isDesignMode, - }); - - const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1]; - - console.log("๐Ÿ“ฅ tabConfig:", { - tabIndex, - configIndex: tabIndex - 1, - tabConfig: tabConfig - ? { - tableName: tabConfig.tableName, - relation: tabConfig.relation, - dataFilter: tabConfig.dataFilter, - } - : null, - }); - - if (!tabConfig || !leftItem || isDesignMode) { - console.log("โš ๏ธ loadTabData ์ค‘๋‹จ:", { hasTabConfig: !!tabConfig, hasLeftItem: !!leftItem, isDesignMode }); - return; - } - - const tabTableName = tabConfig.tableName; - if (!tabTableName) return; - - setTabsLoading((prev) => ({ ...prev, [tabIndex]: true })); - try { - // ์กฐ์ธ ํ‚ค ํ™•์ธ - const keys = tabConfig.relation?.keys; - const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn; - const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn; - - console.log(`๐Ÿ”‘ [์ถ”๊ฐ€ํƒญ ${tabIndex}] ์กฐ์ธ ํ‚ค ๋ถ„์„:`, { - hasRelation: !!tabConfig.relation, - keys, - leftColumn, - rightColumn, - willUseJoin: !!(leftColumn && rightColumn), - }); - - let resultData: any[] = []; - - if (leftColumn && rightColumn) { - // ์กฐ์ธ ์กฐ๊ฑด์ด ์žˆ๋Š” ๊ฒฝ์šฐ - const { entityJoinApi } = await import("@/lib/api/entityJoin"); - const searchConditions: Record = {}; - - if (keys && keys.length > 0) { - // ๋ณตํ•ฉํ‚ค - keys.forEach((key) => { - if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - // operator: "equals"๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ •ํ™•ํ•œ ๊ฐ’ ๋งค์นญ (entity ํƒ€์ž… ์ปฌ๋Ÿผ์—์„œ ์ฝ”๋“œ๊ฐ’์œผ๋กœ ๊ฒ€์ƒ‰) - searchConditions[key.rightColumn] = { - value: leftItem[key.leftColumn], - operator: "equals", - }; - } - }); - } else { - // ๋‹จ์ผํ‚ค - const leftValue = leftItem[leftColumn]; - if (leftValue !== undefined) { - // operator: "equals"๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ •ํ™•ํ•œ ๊ฐ’ ๋งค์นญ (entity ํƒ€์ž… ์ปฌ๋Ÿผ์—์„œ ์ฝ”๋“œ๊ฐ’์œผ๋กœ ๊ฒ€์ƒ‰) - searchConditions[rightColumn] = { - value: leftValue, - operator: "equals", - }; - } - } - - console.log(`๐Ÿ”— [์ถ”๊ฐ€ํƒญ ${tabIndex}] ์กฐํšŒ ์กฐ๊ฑด:`, searchConditions); - - const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { - search: searchConditions, - enableEntityJoin: true, - size: 1000, - }); - - resultData = result.data || []; - } else { - // ์กฐ์ธ ์กฐ๊ฑด์ด ์—†๋Š” ๊ฒฝ์šฐ: ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ (๋…๋ฆฝ ํƒญ) - console.log(`๐Ÿ“‹ [์ถ”๊ฐ€ํƒญ ${tabIndex}] ์กฐ์ธ ์—†์ด ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ: ${tabTableName}`); - const { entityJoinApi } = await import("@/lib/api/entityJoin"); - const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { - enableEntityJoin: true, - size: 1000, - }); - resultData = result.data || []; - console.log(`๐Ÿ“‹ [์ถ”๊ฐ€ํƒญ ${tabIndex}] ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ ๊ฒฐ๊ณผ:`, resultData.length); - } - - // ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ ์šฉ - const dataFilter = tabConfig.dataFilter; - // filters ๋˜๋Š” conditions ๋ฐฐ์—ด ์ง€์› (DataFilterConfigPanel์€ filters ์‚ฌ์šฉ) - const filterConditions = dataFilter?.filters || dataFilter?.conditions || []; - - console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] ํ•„ํ„ฐ ์„ค์ •:`, { - enabled: dataFilter?.enabled, - filterConditions, - dataBeforeFilter: resultData.length, - }); - - if (dataFilter?.enabled && filterConditions.length > 0) { - const beforeCount = resultData.length; - resultData = resultData.filter((item: any) => { - return filterConditions.every((cond: any) => { - // columnName ๋˜๋Š” column ์ง€์› - const columnName = cond.columnName || cond.column; - const value = item[columnName]; - const condValue = cond.value; - - let result = true; - switch (cond.operator) { - case "equals": - result = value === condValue; - break; - case "notEquals": - result = value !== condValue; - break; - case "contains": - result = String(value).includes(String(condValue)); - break; - case "is_null": - case "NULL": - result = value === null || value === undefined || value === ""; - break; - case "is_not_null": - case "NOT NULL": - result = value !== null && value !== undefined && value !== ""; - break; - default: - result = true; - } - - // ์ฒซ 5๊ฐœ ํ•ญ๋ชฉ๋งŒ ๋กœ๊ทธ ์ถœ๋ ฅ - if (resultData.indexOf(item) < 5) { - console.log(` ํ•„ํ„ฐ ์ฒดํฌ: ${columnName}=${value}, operator=${cond.operator}, result=${result}`); - } - - return result; - }); - }); - console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] ํ•„ํ„ฐ ์ ์šฉ ํ›„: ${beforeCount} โ†’ ${resultData.length}`); - } - - // ์ค‘๋ณต ์ œ๊ฑฐ ์ ์šฉ - const deduplication = tabConfig.deduplication; - if (deduplication?.enabled && deduplication.groupByColumn) { - const groupedMap = new Map(); - resultData.forEach((item) => { - const key = String(item[deduplication.groupByColumn] || ""); - const existing = groupedMap.get(key); - if (!existing) { - groupedMap.set(key, item); - } else { - // keepStrategy์— ๋”ฐ๋ผ ์œ ์ง€ํ•  ํ•ญ๋ชฉ ๊ฒฐ์ • - const sortCol = deduplication.sortColumn || "start_date"; - const existingVal = existing[sortCol]; - const newVal = item[sortCol]; - if (deduplication.keepStrategy === "latest" && newVal > existingVal) { - groupedMap.set(key, item); - } else if (deduplication.keepStrategy === "earliest" && newVal < existingVal) { - groupedMap.set(key, item); - } - } - }); - resultData = Array.from(groupedMap.values()); - } - - console.log(`๐Ÿ”— [์ถ”๊ฐ€ํƒญ ${tabIndex}] ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ:`, resultData.length); - setTabsData((prev) => ({ ...prev, [tabIndex]: resultData })); - } catch (error) { - console.error(`์ถ”๊ฐ€ํƒญ ${tabIndex} ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:`, error); - toast({ - title: "๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", - description: "ํƒญ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - variant: "destructive", - }); - } finally { - setTabsLoading((prev) => ({ ...prev, [tabIndex]: false })); - } - }, - [componentConfig.rightPanel?.additionalTabs, isDesignMode, toast], - ); - // ์ขŒ์ธก ํ•ญ๋ชฉ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ const handleLeftItemSelect = useCallback( (item: any) => { setSelectedLeftItem(item); setExpandedRightItems(new Set()); // ์ขŒ์ธก ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์‹œ ์šฐ์ธก ํ™•์žฅ ์ดˆ๊ธฐํ™” - setTabsData({}); // ๋ชจ๋“  ํƒญ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” - - // ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ - if (activeTabIndex === 0) { - loadRightData(item); - } else { - loadTabData(activeTabIndex, item); - } + loadRightData(item); // ๐Ÿ†• modalDataStore์— ์„ ํƒ๋œ ์ขŒ์ธก ํ•ญ๋ชฉ ์ €์žฅ (๋‹จ์ผ ์„ ํƒ) const leftTableName = componentConfig.leftPanel?.tableName; if (leftTableName && !isDesignMode) { import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(leftTableName, [item]); - // console.log(`โœ… ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ์„ ํƒ: ${leftTableName}`, item); + console.log(`โœ… ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ์„ ํƒ: ${leftTableName}`, item); }); } }, - [loadRightData, loadTabData, activeTabIndex, componentConfig.leftPanel?.tableName, isDesignMode], - ); - - // ๐Ÿ†• ํƒญ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ - const handleTabChange = useCallback( - (newTabIndex: number) => { - console.log(`๐Ÿ”„ ํƒญ ๋ณ€๊ฒฝ: ${activeTabIndex} โ†’ ${newTabIndex}`, { - selectedLeftItem: !!selectedLeftItem, - tabsData: Object.keys(tabsData), - hasTabData: !!tabsData[newTabIndex], - }); - - setActiveTabIndex(newTabIndex); - - // ์„ ํƒ๋œ ์ขŒ์ธก ํ•ญ๋ชฉ์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ํƒญ์˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ - if (selectedLeftItem) { - if (newTabIndex === 0) { - // ๊ธฐ๋ณธ ํƒญ: ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๋กœ๋“œ - if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) { - loadRightData(selectedLeftItem); - } - } else { - // ์ถ”๊ฐ€ ํƒญ: ํ•ญ์ƒ ์ƒˆ๋กœ ๋กœ๋“œ (ํ•„ํ„ฐ ์„ค์ • ๋ณ€๊ฒฝ ๋ฐ˜์˜์„ ์œ„ํ•ด) - console.log(`๐Ÿ”„ ์ถ”๊ฐ€ ํƒญ ${newTabIndex} ๋ฐ์ดํ„ฐ ๋กœ๋“œ (ํ•ญ์ƒ ์ƒˆ๋กœ๊ณ ์นจ)`); - loadTabData(newTabIndex, selectedLeftItem); - } - } else { - console.log("โš ๏ธ ์ขŒ์ธก ํ•ญ๋ชฉ์ด ์„ ํƒ๋˜์ง€ ์•Š์•„ ํƒญ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜์ง€ ์•Š์Œ"); - } - }, - [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData, activeTabIndex], + [loadRightData, componentConfig.leftPanel?.tableName, isDesignMode], ); // ์šฐ์ธก ํ•ญ๋ชฉ ํ™•์žฅ/์ถ•์†Œ ํ† ๊ธ€ @@ -1594,7 +1204,7 @@ export const SplitPanelLayoutComponent: React.FC } }); setLeftColumnLabels(labels); - // console.log("โœ… ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); + console.log("โœ… ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); } catch (error) { console.error("์ขŒ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ ์‹คํŒจ:", error); } @@ -1623,7 +1233,7 @@ export const SplitPanelLayoutComponent: React.FC } }); setRightColumnLabels(labels); - // console.log("โœ… ์šฐ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); + console.log("โœ… ์šฐ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); } catch (error) { console.error("์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ ์‹คํŒจ:", error); } @@ -1665,7 +1275,7 @@ export const SplitPanelLayoutComponent: React.FC }; }); mappings[columnName] = valueMap; - // console.log(`โœ… ์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${columnName}]:`, valueMap); + console.log(`โœ… ์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${columnName}]:`, valueMap); } } catch (error) { console.error(`์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ์กฐํšŒ ์‹คํŒจ [${columnName}]:`, error); @@ -1703,7 +1313,7 @@ export const SplitPanelLayoutComponent: React.FC } }); - // console.log("๐Ÿ” ์šฐ์ธก ํŒจ๋„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”:", Array.from(tablesToLoad)); + console.log("๐Ÿ” ์šฐ์ธก ํŒจ๋„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”:", Array.from(tablesToLoad)); // ๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ for (const tableName of tablesToLoad) { @@ -1796,22 +1406,36 @@ export const SplitPanelLayoutComponent: React.FC // ์ˆ˜์ • ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ const handleEditClick = useCallback( - async (panel: "left" | "right", item: any) => { - // ๐Ÿ†• ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์˜ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ - const currentTabConfig = - activeTabIndex === 0 - ? componentConfig.rightPanel - : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]; + (panel: "left" | "right", item: any) => { // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ํ™•์ธ - if (panel === "right" && currentTabConfig?.editButton?.mode === "modal") { - const modalScreenId = currentTabConfig?.editButton?.modalScreenId; + if (panel === "right" && componentConfig.rightPanel?.editButton?.mode === "modal") { + const modalScreenId = componentConfig.rightPanel?.editButton?.modalScreenId; if (modalScreenId) { // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ - const rightTableName = currentTabConfig?.tableName || ""; + const rightTableName = componentConfig.rightPanel?.tableName || ""; + + // Primary Key ์ฐพ๊ธฐ (์šฐ์„ ์ˆœ์œ„: id > ID > ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ) + let primaryKeyName = "id"; + let primaryKeyValue: any; + + if (item.id !== undefined && item.id !== null) { + primaryKeyName = "id"; + primaryKeyValue = item.id; + } else if (item.ID !== undefined && item.ID !== null) { + primaryKeyName = "ID"; + primaryKeyValue = item.ID; + } else { + // ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ๋ฅผ Primary Key๋กœ ๊ฐ„์ฃผ + const firstKey = Object.keys(item)[0]; + primaryKeyName = firstKey; + primaryKeyValue = item[firstKey]; + } console.log("โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { tableName: rightTableName, + primaryKeyName, + primaryKeyValue, screenId: modalScreenId, fullItem: item, }); @@ -1822,99 +1446,23 @@ export const SplitPanelLayoutComponent: React.FC }); // ๐Ÿ†• groupByColumns ์ถ”์ถœ - const groupByColumns = currentTabConfig?.editButton?.groupByColumns || []; + const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - groupByColumns ํ™•์ธ:", { groupByColumns, - editButtonConfig: currentTabConfig?.editButton, + editButtonConfig: componentConfig.rightPanel?.editButton, hasGroupByColumns: groupByColumns.length > 0, }); - // ๐Ÿ†• groupByColumns ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ ์กฐํšŒ (API ์ง์ ‘ ํ˜ธ์ถœ) - let allRelatedRecords = [item]; // ๊ธฐ๋ณธ๊ฐ’: ํ˜„์žฌ ์•„์ดํ…œ๋งŒ - - if (groupByColumns.length > 0) { - // groupByColumns ๊ฐ’์œผ๋กœ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ƒ์„ฑ - const matchConditions: Record = {}; - groupByColumns.forEach((col: string) => { - if (item[col] !== undefined && item[col] !== null) { - matchConditions[col] = item[col]; - } - }); - - console.log("๐Ÿ” [SplitPanel] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ์กฐํšŒ ์‹œ์ž‘:", { - ํ…Œ์ด๋ธ”: rightTableName, - ์กฐ๊ฑด: matchConditions, - }); - - if (Object.keys(matchConditions).length > 0) { - // ๐Ÿ†• deduplication ์—†์ด ์›๋ณธ ๋ฐ์ดํ„ฐ ๋‹ค์‹œ ์กฐํšŒ (API ์ง์ ‘ ํ˜ธ์ถœ) - try { - const { entityJoinApi } = await import("@/lib/api/entityJoin"); - - // ๐Ÿ”ง dataFilter๋กœ ์ •ํ™• ๋งค์นญ ์กฐ๊ฑด ์ƒ์„ฑ (search๋Š” LIKE ๊ฒ€์ƒ‰์ด๋ผ ๋ถ€์ •ํ™•) - const exactMatchFilters = Object.entries(matchConditions).map(([key, value]) => ({ - id: `exact-${key}`, - columnName: key, - operator: "equals", - value: value, - valueType: "text", - })); - - console.log("๐Ÿ” [SplitPanel] ์ •ํ™• ๋งค์นญ ํ•„ํ„ฐ:", exactMatchFilters); - - const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { - // search ๋Œ€์‹  dataFilter ์‚ฌ์šฉ (์ •ํ™• ๋งค์นญ) - dataFilter: { - enabled: true, - matchType: "all", - filters: exactMatchFilters, - }, - enableEntityJoin: true, - size: 1000, - // ๐Ÿ”ง ๋ช…์‹œ์ ์œผ๋กœ deduplication ๋น„ํ™œ์„ฑํ™” (๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ) - deduplication: { enabled: false, groupByColumn: "", keepStrategy: "latest" }, - }); - - // ๐Ÿ” ๋””๋ฒ„๊น…: API ์‘๋‹ต ๊ตฌ์กฐ ํ™•์ธ - console.log("๐Ÿ” [SplitPanel] API ์‘๋‹ต ์ „์ฒด:", result); - console.log("๐Ÿ” [SplitPanel] result.data:", result.data); - console.log("๐Ÿ” [SplitPanel] result ํƒ€์ž…:", typeof result); - - // result ์ž์ฒด๊ฐ€ ๋ฐฐ์—ด์ผ ์ˆ˜๋„ ์žˆ์Œ (entityJoinApi ์‘๋‹ต ๊ตฌ์กฐ์— ๋”ฐ๋ผ) - const dataArray = Array.isArray(result) ? result : result.data || []; - - if (dataArray.length > 0) { - allRelatedRecords = dataArray; - console.log("โœ… [SplitPanel] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ์กฐํšŒ ์™„๋ฃŒ:", { - ์กฐ๊ฑด: matchConditions, - ๊ฒฐ๊ณผ์ˆ˜: allRelatedRecords.length, - ๋ ˆ์ฝ”๋“œ๋“ค: allRelatedRecords.map((r: any) => ({ - id: r.id, - supplier_item_code: r.supplier_item_code, - })), - }); - } else { - console.warn("โš ๏ธ [SplitPanel] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ์กฐํšŒ ๊ฒฐ๊ณผ ์—†์Œ, ํ˜„์žฌ ์•„์ดํ…œ๋งŒ ์‚ฌ์šฉ"); - } - } catch (error) { - console.error("โŒ [SplitPanel] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ์กฐํšŒ ์‹คํŒจ:", error); - allRelatedRecords = [item]; - } - } else { - console.warn("โš ๏ธ [SplitPanel] groupByColumns ๊ฐ’์ด ์—†์Œ, ํ˜„์žฌ ์•„์ดํ…œ๋งŒ ์‚ฌ์šฉ"); - } - } - - // ๐Ÿ”ง ์ˆ˜์ •: URL ํŒŒ๋ผ๋ฏธํ„ฐ ๋Œ€์‹  editData๋กœ ์ง์ ‘ ์ „๋‹ฌ - // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ด๋ธ”์˜ Primary Key๊ฐ€ ๋ฌด์—‡์ด๋“  ์ƒ๊ด€์—†์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์ •ํ™•ํžˆ ์ „๋‹ฌ๋จ + // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ID + groupByColumns ์ „๋‹ฌ) window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId: modalScreenId, - editData: allRelatedRecords, // ๐Ÿ†• ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ ์ „๋‹ฌ (๋ฐฐ์—ด) urlParams: { - mode: "edit", // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ ํ‘œ์‹œ + mode: "edit", + editId: primaryKeyValue, + tableName: rightTableName, ...(groupByColumns.length > 0 && { groupByColumns: JSON.stringify(groupByColumns), }), @@ -1923,10 +1471,10 @@ export const SplitPanelLayoutComponent: React.FC }), ); - console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ (editData ์ง์ ‘ ์ „๋‹ฌ):", { + console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ:", { screenId: modalScreenId, - editData: allRelatedRecords, - recordCount: allRelatedRecords.length, + editId: primaryKeyValue, + tableName: rightTableName, groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "์—†์Œ", }); @@ -1940,7 +1488,7 @@ export const SplitPanelLayoutComponent: React.FC setEditModalFormData({ ...item }); setShowEditModal(true); }, - [componentConfig, activeTabIndex], + [componentConfig], ); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ €์žฅ @@ -2040,18 +1588,13 @@ export const SplitPanelLayoutComponent: React.FC // ์‚ญ์ œ ํ™•์ธ const handleDeleteConfirm = useCallback(async () => { - // ๐Ÿ†• ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์˜ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ - const currentTabConfig = - activeTabIndex === 0 - ? componentConfig.rightPanel - : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]; - // ์šฐ์ธก ํŒจ๋„ ์‚ญ์ œ ์‹œ ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ํ™•์ธ - let tableName = deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : currentTabConfig?.tableName; + let tableName = + deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName; // ์šฐ์ธก ํŒจ๋„ + ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ๋ชจ๋“œ์ธ ๊ฒฝ์šฐ - if (deleteModalPanel === "right" && currentTabConfig?.addConfig?.targetTable) { - tableName = currentTabConfig.addConfig.targetTable; + if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) { + tableName = componentConfig.rightPanel.addConfig.targetTable; console.log("๐Ÿ”— ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ๋ชจ๋“œ: ์‚ญ์ œ ๋Œ€์ƒ ํ…Œ์ด๋ธ” =", tableName); } @@ -2076,89 +1619,47 @@ export const SplitPanelLayoutComponent: React.FC try { console.log("๐Ÿ—‘๏ธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ:", { tableName, primaryKey }); - // ๐Ÿ” ๊ทธ๋ฃน ์‚ญ์ œ ์„ค์ • ํ™•์ธ (editButton.groupByColumns ๋˜๋Š” deduplication) - const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; - const deduplication = componentConfig.rightPanel?.dataFilter?.deduplication; - - console.log("๐Ÿ” ์‚ญ์ œ ์„ค์ • ๋””๋ฒ„๊น…:", { + // ๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ๋””๋ฒ„๊น… + console.log("๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ๋””๋ฒ„๊น…:", { panel: deleteModalPanel, - groupByColumns, - deduplication, - deduplicationEnabled: deduplication?.enabled, + dataFilter: componentConfig.rightPanel?.dataFilter, + deduplication: componentConfig.rightPanel?.dataFilter?.deduplication, + enabled: componentConfig.rightPanel?.dataFilter?.deduplication?.enabled, }); let result; - // ๐Ÿ”ง ์šฐ์ธก ํŒจ๋„ ์‚ญ์ œ ์‹œ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด ํ™•์ธ - if (deleteModalPanel === "right") { - // 1. groupByColumns๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ (ํŒจ๋„ ์„ค์ •์—์„œ ์„ ํƒ๋œ ์ปฌ๋Ÿผ๋“ค) - if (groupByColumns.length > 0) { - const filterConditions: Record = {}; + // ๐Ÿ”ง ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, groupByColumn ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ + if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) { + const deduplication = componentConfig.rightPanel.dataFilter.deduplication; + const groupByColumn = deduplication.groupByColumn; - // ์„ ํƒ๋œ ์ปฌ๋Ÿผ๋“ค์˜ ๊ฐ’์„ ํ•„ํ„ฐ ์กฐ๊ฑด์œผ๋กœ ์ถ”๊ฐ€ - for (const col of groupByColumns) { - if (deleteModalItem[col] !== undefined && deleteModalItem[col] !== null) { - filterConditions[col] = deleteModalItem[col]; - } - } - - // ๐Ÿ”’ ์•ˆ์ „์žฅ์น˜: ์กฐ์ธ ๋ชจ๋“œ์—์„œ ์ขŒ์ธก ํŒจ๋„์˜ ํ‚ค ๊ฐ’๋„ ํ•„ํ„ฐ ์กฐ๊ฑด์— ํฌํ•จ - // (๋‹ค๋ฅธ ๊ฑฐ๋ž˜์ฒ˜์˜ ๊ฐ™์€ ํ’ˆ๋ชฉ์ด ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€) - if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { - const leftColumn = componentConfig.rightPanel.join?.leftColumn; - const rightColumn = componentConfig.rightPanel.join?.rightColumn; - if (leftColumn && rightColumn && selectedLeftItem[leftColumn]) { - // rightColumn์ด filterConditions์— ์—†์œผ๋ฉด ์ถ”๊ฐ€ - if (!filterConditions[rightColumn]) { - filterConditions[rightColumn] = selectedLeftItem[leftColumn]; - console.log(`๐Ÿ”’ ์•ˆ์ „์žฅ์น˜: ${rightColumn} = ${selectedLeftItem[leftColumn]} ์ถ”๊ฐ€`); - } - } - } - - // ํ•„ํ„ฐ ์กฐ๊ฑด์ด ์žˆ์œผ๋ฉด ๊ทธ๋ฃน ์‚ญ์ œ - if (Object.keys(filterConditions).length > 0) { - console.log(`๐Ÿ”— ๊ทธ๋ฃน ์‚ญ์ œ (groupByColumns): ${groupByColumns.join(", ")} ๊ธฐ์ค€`); - console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); - - result = await dataApi.deleteGroupRecords(tableName, filterConditions); - } else { - // ํ•„ํ„ฐ ์กฐ๊ฑด์ด ์—†์œผ๋ฉด ๋‹จ์ผ ์‚ญ์ œ - console.log("โš ๏ธ groupByColumns ๊ฐ’์ด ์—†์–ด ๋‹จ์ผ ์‚ญ์ œ๋กœ ์ „ํ™˜"); - result = await dataApi.deleteRecord(tableName, primaryKey); - } - } - // 2. ์ค‘๋ณต ์ œ๊ฑฐ(deduplication)๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ - else if (deduplication?.enabled && deduplication?.groupByColumn) { - const groupByColumn = deduplication.groupByColumn; + if (groupByColumn && deleteModalItem[groupByColumn]) { const groupValue = deleteModalItem[groupByColumn]; + console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); - if (groupValue) { - console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); + // groupByColumn ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์‚ญ์ œ + const filterConditions: Record = { + [groupByColumn]: groupValue, + }; - const filterConditions: Record = { - [groupByColumn]: groupValue, - }; - - // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) - if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { - const leftColumn = componentConfig.rightPanel.join.leftColumn; - const rightColumn = componentConfig.rightPanel.join.rightColumn; - filterConditions[rightColumn] = selectedLeftItem[leftColumn]; - } - - console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); - result = await dataApi.deleteGroupRecords(tableName, filterConditions); - } else { - result = await dataApi.deleteRecord(tableName, primaryKey); + // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) + if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { + const leftColumn = componentConfig.rightPanel.join.leftColumn; + const rightColumn = componentConfig.rightPanel.join.rightColumn; + filterConditions[rightColumn] = selectedLeftItem[leftColumn]; } - } - // 3. ๊ทธ ์™ธ: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ - else { + + console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); + + // ๊ทธ๋ฃน ์‚ญ์ œ API ํ˜ธ์ถœ + result = await dataApi.deleteGroupRecords(tableName, filterConditions); + } else { + // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ result = await dataApi.deleteRecord(tableName, primaryKey); } } else { - // ์ขŒ์ธก ํŒจ๋„: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ + // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ result = await dataApi.deleteRecord(tableName, primaryKey); } @@ -2181,12 +1682,7 @@ export const SplitPanelLayoutComponent: React.FC setRightData(null); } } else if (deleteModalPanel === "right" && selectedLeftItem) { - // ๐Ÿ†• ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ผ ์ƒˆ๋กœ๊ณ ์นจ - if (activeTabIndex === 0) { - loadRightData(selectedLeftItem); - } else { - loadTabData(activeTabIndex, selectedLeftItem); - } + loadRightData(selectedLeftItem); } } else { toast({ @@ -2210,17 +1706,7 @@ export const SplitPanelLayoutComponent: React.FC variant: "destructive", }); } - }, [ - deleteModalPanel, - componentConfig, - deleteModalItem, - toast, - selectedLeftItem, - loadLeftData, - loadRightData, - activeTabIndex, - loadTabData, - ]); + }, [deleteModalPanel, componentConfig, deleteModalItem, toast, selectedLeftItem, loadLeftData, loadRightData]); // ํ•ญ๋ชฉ๋ณ„ ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ (์ขŒ์ธก ํ•ญ๋ชฉ์˜ + ๋ฒ„ํŠผ - ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€) const handleItemAddClick = useCallback( @@ -2460,7 +1946,7 @@ export const SplitPanelLayoutComponent: React.FC useEffect(() => { const handleRefreshTable = () => { if (!isDesignMode) { - // console.log("๐Ÿ”„ [SplitPanel] refreshTable ์ด๋ฒคํŠธ ์ˆ˜์‹  - ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ"); + console.log("๐Ÿ”„ [SplitPanel] refreshTable ์ด๋ฒคํŠธ ์ˆ˜์‹  - ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ"); loadLeftData(); // ์„ ํƒ๋œ ํ•ญ๋ชฉ์ด ์žˆ์œผ๋ฉด ์šฐ์ธก ํŒจ๋„๋„ ์ƒˆ๋กœ๊ณ ์นจ if (selectedLeftItem) { @@ -2562,7 +2048,7 @@ export const SplitPanelLayoutComponent: React.FC >
- {getTranslatedText(componentConfig.leftPanel?.langKey, componentConfig.leftPanel?.title || "์ขŒ์ธก ํŒจ๋„")} + {componentConfig.leftPanel?.title || "์ขŒ์ธก ํŒจ๋„"} {!isDesignMode && componentConfig.leftPanel?.showAdd && (