From 21c0c2b95c347e48870e43977f37d550bd43e3be Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Fri, 27 Feb 2026 14:00:06 +0900 Subject: [PATCH] fix: Enhance layout loading logic in screen management - Updated the ScreenManagementService to allow SUPER_ADMIN or users with companyCode as "*" to load layouts based on the screen's company code. - Improved layout loading in ScreenViewPage and EditModal components by implementing fallback mechanisms to ensure a valid layout is always set. - Added console warnings for better debugging when layout loading fails, enhancing error visibility and user experience. - Refactored label display logic in various components to ensure consistent behavior across input types. --- .../src/services/screenManagementService.ts | 4 +- .../app/(main)/screens/[screenId]/page.tsx | 20 ++- frontend/components/screen/EditModal.tsx | 23 +++- .../screen/InteractiveScreenViewer.tsx | 7 +- .../screen/InteractiveScreenViewerDynamic.tsx | 2 +- frontend/components/v2/V2Date.tsx | 2 +- frontend/components/v2/V2Input.tsx | 2 +- frontend/components/v2/V2Select.tsx | 2 +- .../table-list/TableListConfigPanel.tsx | 124 +++++++++++++++++- .../v2-table-list/TableListConfigPanel.tsx | 124 ++++++++++++++++++ frontend/lib/utils/buttonActions.ts | 14 +- 11 files changed, 304 insertions(+), 20 deletions(-) diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 6f412de5..74506a39 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -5083,8 +5083,8 @@ export class ScreenManagementService { let layout: { layout_data: any } | null = null; // ๐Ÿ†• ๊ธฐ๋ณธ ๋ ˆ์ด์–ด(layer_id=1)๋ฅผ ์šฐ์„  ๋กœ๋“œ - // SUPER_ADMIN์ธ ๊ฒฝ์šฐ: ํ™”๋ฉด์˜ ํšŒ์‚ฌ ์ฝ”๋“œ๋กœ ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ - if (isSuperAdmin) { + // SUPER_ADMIN์ด๊ฑฐ๋‚˜ companyCode๊ฐ€ "*"์ธ ๊ฒฝ์šฐ: ํ™”๋ฉด์˜ ํšŒ์‚ฌ ์ฝ”๋“œ๋กœ ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ + if (isSuperAdmin || companyCode === "*") { // 1. ํ™”๋ฉด ์ •์˜์˜ ํšŒ์‚ฌ ์ฝ”๋“œ + ๊ธฐ๋ณธ ๋ ˆ์ด์–ด layout = await queryOne<{ layout_data: any }>( `SELECT layout_data FROM screen_layouts_v2 diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 160883ad..d1e07abe 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -179,7 +179,25 @@ function ScreenViewPage() { } else { // V1 ๋ ˆ์ด์•„์›ƒ ๋˜๋Š” ๋นˆ ๋ ˆ์ด์•„์›ƒ const layoutData = await screenApi.getLayout(screenId); - setLayout(layoutData); + if (layoutData?.components?.length > 0) { + setLayout(layoutData); + } else { + console.warn("[ScreenViewPage] getLayout ์‹คํŒจ, getLayerLayout(1) fallback:", screenId); + const baseLayerData = await screenApi.getLayerLayout(screenId, 1); + if (baseLayerData && isValidV2Layout(baseLayerData)) { + const converted = convertV2ToLegacy(baseLayerData); + if (converted) { + setLayout({ + ...converted, + screenResolution: baseLayerData.screenResolution || converted.screenResolution, + } as LayoutData); + } else { + setLayout(layoutData); + } + } else { + setLayout(layoutData); + } + } } } catch (layoutError) { console.warn("๋ ˆ์ด์•„์›ƒ ๋กœ๋“œ ์‹คํŒจ, ๋นˆ ๋ ˆ์ด์•„์›ƒ ์‚ฌ์šฉ:", layoutError); diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index fe6ba4fa..ec36096d 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -413,9 +413,28 @@ export const EditModal: React.FC = ({ className }) => { // V2 ์—†์œผ๋ฉด ๊ธฐ์กด API fallback if (!layoutData) { + console.warn("[EditModal] V2 ๋ ˆ์ด์•„์›ƒ ์—†์Œ, getLayout fallback ์‹œ๋„:", screenId); layoutData = await screenApi.getLayout(screenId); } + // getLayout๋„ ์‹คํŒจํ•˜๋ฉด ๊ธฐ๋ณธ ๋ ˆ์ด์–ด(layer_id=1) ์ง์ ‘ ๋กœ๋“œ + if (!layoutData || !layoutData.components || layoutData.components.length === 0) { + console.warn("[EditModal] getLayout๋„ ์‹คํŒจ, getLayerLayout(1) ์ตœ์ข… fallback:", screenId); + try { + const baseLayerData = await screenApi.getLayerLayout(screenId, 1); + if (baseLayerData && isValidV2Layout(baseLayerData)) { + layoutData = convertV2ToLegacy(baseLayerData); + if (layoutData) { + layoutData.screenResolution = baseLayerData.screenResolution || layoutData.screenResolution; + } + } else if (baseLayerData?.components) { + layoutData = baseLayerData; + } + } catch (fallbackErr) { + console.error("[EditModal] getLayerLayout(1) fallback ์‹คํŒจ:", fallbackErr); + } + } + if (screenInfo && layoutData) { const components = layoutData.components || []; @@ -1440,7 +1459,7 @@ export const EditModal: React.FC = ({ className }) => { -
+
{loading ? (
@@ -1455,7 +1474,7 @@ export const EditModal: React.FC = ({ className }) => { >
= ( // ๋ผ๋ฒจ ํ‘œ์‹œ ์—ฌ๋ถ€ ๊ณ„์‚ฐ const shouldShowLabel = - !hideLabel && // hideLabel์ด true๋ฉด ๋ผ๋ฒจ ์ˆจ๊น€ - (component.style?.labelDisplay ?? true) && + !hideLabel && + (component.style?.labelDisplay ?? true) !== false && + component.style?.labelDisplay !== "false" && (component.label || component.style?.labelText) && - !templateTypes.includes(component.type); // ํ…œํ”Œ๋ฆฟ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ผ๋ฒจ ํ‘œ์‹œ ์•ˆํ•จ + !templateTypes.includes(component.type); const labelText = component.style?.labelText || component.label || ""; diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index a35c5ed2..253c886d 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -1109,7 +1109,7 @@ export const InteractiveScreenViewerDynamic: React.FC((props, ref) => { } }; - const showLabel = label && style?.labelDisplay !== false; + const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false"; const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index d76802e8..219fa275 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -962,7 +962,7 @@ export const V2Input = forwardRef((props, ref) => }; const actualLabel = label || style?.labelText; - const showLabel = actualLabel && style?.labelDisplay === true; + const showLabel = actualLabel && style?.labelDisplay !== false && style?.labelDisplay !== "false"; const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; diff --git a/frontend/components/v2/V2Select.tsx b/frontend/components/v2/V2Select.tsx index e7dbfd86..690791d5 100644 --- a/frontend/components/v2/V2Select.tsx +++ b/frontend/components/v2/V2Select.tsx @@ -1135,7 +1135,7 @@ export const V2Select = forwardRef( } }; - const showLabel = label && style?.labelDisplay !== false; + const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false"; const componentWidth = size?.width || style?.width; const componentHeight = size?.height || style?.height; diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index 8cd8b0c5..f3a28c4c 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -10,7 +10,7 @@ import { TableListConfig, ColumnConfig } from "./types"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableTypeApi } from "@/lib/api/screen"; import { tableManagementApi } from "@/lib/api/tableManagement"; -import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock, Database, Table2, Link2 } from "lucide-react"; +import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock, Database, Table2, Link2, GripVertical, Pencil } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { cn } from "@/lib/utils"; @@ -1213,6 +1213,34 @@ export const TableListConfigPanel: React.FC = ({ )} + {/* ์„ ํƒ๋œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ */} + {config.columns && config.columns.length > 0 && ( +
+
+

์ปฌ๋Ÿผ ์ˆœ์„œ / ์„ค์ •

+

+ ์„ ํƒ๋œ ์ปฌ๋Ÿผ์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ํ‘œ์‹œ๋ช…์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค +

+
+
+
+ {[...(config.columns || [])] + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) + .map((column, idx) => ( + moveColumn(column.columnName, direction)} + onRemove={() => removeColumn(column.columnName)} + onUpdate={(updates) => updateColumn(column.columnName, updates)} + /> + ))} +
+
+ )} + {/* ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ์„ค์ • */}
@@ -1240,3 +1268,97 @@ export const TableListConfigPanel: React.FC = ({
); }; + +/** + * ์„ ํƒ๋œ ์ปฌ๋Ÿผ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ + * ์ˆœ์„œ ์ด๋™, ์‚ญ์ œ, ํ‘œ์‹œ๋ช… ์ˆ˜์ • ๊ธฐ๋Šฅ ์ œ๊ณต + */ +const SelectedColumnItem: React.FC<{ + column: ColumnConfig; + index: number; + total: number; + onMove: (direction: "up" | "down") => void; + onRemove: () => void; + onUpdate: (updates: Partial) => void; +}> = ({ column, index, total, onMove, onRemove, onUpdate }) => { + const [isEditing, setIsEditing] = useState(false); + const [editValue, setEditValue] = useState(column.displayName || column.columnName); + + const handleSave = () => { + const trimmed = editValue.trim(); + if (trimmed && trimmed !== column.displayName) { + onUpdate({ displayName: trimmed }); + } + setIsEditing(false); + }; + + return ( +
+ + + {index + 1} + + {isEditing ? ( + setEditValue(e.target.value)} + onBlur={handleSave} + onKeyDown={(e) => { + if (e.key === "Enter") handleSave(); + if (e.key === "Escape") { + setEditValue(column.displayName || column.columnName); + setIsEditing(false); + } + }} + className="h-5 flex-1 px-1 text-xs" + autoFocus + /> + ) : ( + + )} + +
+ + + +
+
+ ); +}; diff --git a/frontend/lib/registry/components/v2-table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/v2-table-list/TableListConfigPanel.tsx index 35f15596..ad250a16 100644 --- a/frontend/lib/registry/components/v2-table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/v2-table-list/TableListConfigPanel.tsx @@ -22,6 +22,8 @@ import { Database, Table2, Link2, + GripVertical, + Pencil, } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; @@ -1458,6 +1460,34 @@ export const TableListConfigPanel: React.FC = ({ )} + {/* ์„ ํƒ๋œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ */} + {config.columns && config.columns.length > 0 && ( +
+
+

์ปฌ๋Ÿผ ์ˆœ์„œ / ์„ค์ •

+

+ ์„ ํƒ๋œ ์ปฌ๋Ÿผ์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ํ‘œ์‹œ๋ช…์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค +

+
+
+
+ {[...(config.columns || [])] + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) + .map((column, idx) => ( + moveColumn(column.columnName, direction)} + onRemove={() => removeColumn(column.columnName)} + onUpdate={(updates) => updateColumn(column.columnName, updates)} + /> + ))} +
+
+ )} + {/* ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ์„ค์ • */}
@@ -1484,3 +1514,97 @@ export const TableListConfigPanel: React.FC = ({
); }; + +/** + * ์„ ํƒ๋œ ์ปฌ๋Ÿผ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ + * ์ˆœ์„œ ์ด๋™, ์‚ญ์ œ, ํ‘œ์‹œ๋ช… ์ˆ˜์ • ๊ธฐ๋Šฅ ์ œ๊ณต + */ +const SelectedColumnItem: React.FC<{ + column: ColumnConfig; + index: number; + total: number; + onMove: (direction: "up" | "down") => void; + onRemove: () => void; + onUpdate: (updates: Partial) => void; +}> = ({ column, index, total, onMove, onRemove, onUpdate }) => { + const [isEditing, setIsEditing] = useState(false); + const [editValue, setEditValue] = useState(column.displayName || column.columnName); + + const handleSave = () => { + const trimmed = editValue.trim(); + if (trimmed && trimmed !== column.displayName) { + onUpdate({ displayName: trimmed }); + } + setIsEditing(false); + }; + + return ( +
+ + + {index + 1} + + {isEditing ? ( + setEditValue(e.target.value)} + onBlur={handleSave} + onKeyDown={(e) => { + if (e.key === "Enter") handleSave(); + if (e.key === "Escape") { + setEditValue(column.displayName || column.columnName); + setIsEditing(false); + } + }} + className="h-5 flex-1 px-1 text-xs" + autoFocus + /> + ) : ( + + )} + +
+ + + +
+
+ ); +}; diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 054b257f..2ed4db87 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -3173,16 +3173,16 @@ export class ButtonActionExecutor { return false; } - // 1. ํ™”๋ฉด ์„ค๋ช… ๊ฐ€์ ธ์˜ค๊ธฐ - let description = config.modalDescription || ""; - if (!description) { + // 1. ํ™”๋ฉด ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์ œ๋ชฉ/์„ค๋ช…์ด ๋ฏธ์„ค์ • ์‹œ ํ™”๋ฉด๋ช…์—์„œ ๊ฐ€์ ธ์˜ด) + let screenInfo: any = null; + if (!config.modalTitle || !config.modalDescription) { try { - const screenInfo = await screenApi.getScreen(config.targetScreenId); - description = screenInfo?.description || ""; + screenInfo = await screenApi.getScreen(config.targetScreenId); } catch (error) { - console.warn("ํ™”๋ฉด ์„ค๋ช…์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค:", error); + console.warn("ํ™”๋ฉด ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค:", error); } } + let description = config.modalDescription || screenInfo?.description || ""; // 2. ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋ฐ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ let selectedData: any[] = []; @@ -3288,7 +3288,7 @@ export class ButtonActionExecutor { } // 3. ๋™์  ๋ชจ๋‹ฌ ์ œ๋ชฉ ์ƒ์„ฑ - let finalTitle = config.modalTitle || "ํ™”๋ฉด"; + let finalTitle = config.modalTitle || screenInfo?.screenName || "๋ฐ์ดํ„ฐ ๋“ฑ๋ก"; // ๋ธ”๋ก ๊ธฐ๋ฐ˜ ์ œ๋ชฉ ์ฒ˜๋ฆฌ if (config.modalTitleBlocks?.length) {