From 8a865ac1f487185677bc7a38f9d1a6fc47ccde27 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 14:29:19 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EC=9D=B4=EC=83=81=ED=95=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95=20=ED=94=BC=EB=B2=97=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pivot-grid/PivotGridComponent.tsx | 77 ++++++++++++++----- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx index bdc00019..ccfbde88 100644 --- a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx +++ b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx @@ -526,11 +526,16 @@ export const PivotGridComponent: React.FC = ({ }); return result; - }, [filteredData, fields, pivotState.expandedRowPaths, pivotState.expandedColumnPaths]); + }, [ + filteredData, + fields, + JSON.stringify(pivotState.expandedRowPaths), + JSON.stringify(pivotState.expandedColumnPaths) + ]); // ๐Ÿ†• ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ์ฒซ ๋ ˆ๋ฒจ ์ž๋™ ํ™•์žฅ useEffect(() => { - if (pivotResult && pivotResult.flatRows.length > 0) { + if (pivotResult && pivotResult.flatRows.length > 0 && !isInitialExpanded) { console.log("๐Ÿ”ถ ํ”ผ๋ฒ— ๊ฒฐ๊ณผ ์ƒ์„ฑ๋จ:", { flatRowsCount: pivotResult.flatRows.length, expandedRowPaths: pivotState.expandedRowPaths.length, @@ -542,10 +547,10 @@ export const PivotGridComponent: React.FC = ({ console.log("๐Ÿ”ถ ์ฒซ ๋ ˆ๋ฒจ ํ–‰ (level 0, hasChildren):", firstLevelRows.map(r => ({ path: r.path, caption: r.caption }))); - // ์ดˆ๊ธฐ ํ™•์žฅ์ด ์•ˆ ๋˜์–ด ์žˆ๊ณ , ์ฒซ ๋ ˆ๋ฒจ ํ–‰์ด ์žˆ์œผ๋ฉด ์ž๋™ ํ™•์žฅ - if (!isInitialExpanded && firstLevelRows.length > 0) { + // ์ฒซ ๋ ˆ๋ฒจ ํ–‰์ด ์žˆ์œผ๋ฉด ์ž๋™ ํ™•์žฅ + if (firstLevelRows.length > 0) { const firstLevelPaths = firstLevelRows.map(row => row.path); - console.log("๐Ÿ”ถ ์ดˆ๊ธฐ ์ž๋™ ํ™•์žฅ ์‹คํ–‰:", firstLevelPaths); + console.log("๐Ÿ”ถ ์ดˆ๊ธฐ ์ž๋™ ํ™•์žฅ ์‹คํ–‰ (ํ•œ ๋ฒˆ๋งŒ):", firstLevelPaths); setPivotState(prev => ({ ...prev, expandedRowPaths: firstLevelPaths, @@ -553,7 +558,7 @@ export const PivotGridComponent: React.FC = ({ setIsInitialExpanded(true); } } - }, [pivotResult, isInitialExpanded, pivotState.expandedRowPaths.length]); + }, [pivotResult, isInitialExpanded]); // ์กฐ๊ฑด๋ถ€ ์„œ์‹์šฉ ์ „์ฒด ๊ฐ’ ์ˆ˜์ง‘ const allCellValues = useMemo(() => { @@ -749,31 +754,61 @@ export const PivotGridComponent: React.FC = ({ [onExpandChange] ); - // ์ „์ฒด ํ™•์žฅ + // ์ „์ฒด ํ™•์žฅ (์žฌ๊ท€์ ์œผ๋กœ ๋ชจ๋“  ๋ ˆ๋ฒจ ํ™•์žฅ) const handleExpandAll = useCallback(() => { - if (!pivotResult) return; + if (!pivotResult) { + console.log("โŒ [handleExpandAll] pivotResult๊ฐ€ ์—†์Œ"); + return; + } + // ๐Ÿ†• ์žฌ๊ท€์ ์œผ๋กœ ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ๊ฒฝ๋กœ ์ƒ์„ฑ const allRowPaths: string[][] = []; - pivotResult.flatRows.forEach((row) => { - if (row.hasChildren) { - allRowPaths.push(row.path); + const rowFields = fields.filter((f) => f.area === "row" && f.visible !== false); + + // ๋ฐ์ดํ„ฐ์—์„œ ๋ชจ๋“  ๊ณ ์œ ํ•œ ๊ฒฝ๋กœ ์ถ”์ถœ + const pathSet = new Set(); + filteredData.forEach((item) => { + for (let depth = 1; depth <= rowFields.length; depth++) { + const path = rowFields.slice(0, depth).map((f) => String(item[f.field] ?? "")); + const pathKey = JSON.stringify(path); + pathSet.add(pathKey); } }); + // Set์„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ + pathSet.forEach((pathKey) => { + allRowPaths.push(JSON.parse(pathKey)); + }); + + console.log("๐Ÿ”ท [handleExpandAll] ํ™•์žฅํ•  ํ–‰:", { + totalRows: pivotResult.flatRows.length, + rowsWithChildren: allRowPaths.length, + paths: allRowPaths.slice(0, 5), // ์ฒ˜์Œ 5๊ฐœ๋งŒ ๋กœ๊ทธ + }); + setPivotState((prev) => ({ ...prev, expandedRowPaths: allRowPaths, expandedColumnPaths: [], })); - }, [pivotResult]); + }, [pivotResult, fields, filteredData]); // ์ „์ฒด ์ถ•์†Œ const handleCollapseAll = useCallback(() => { - setPivotState((prev) => ({ - ...prev, - expandedRowPaths: [], - expandedColumnPaths: [], - })); + console.log("๐Ÿ”ท [handleCollapseAll] ์ „์ฒด ์ถ•์†Œ ์‹คํ–‰"); + + setPivotState((prev) => { + console.log("๐Ÿ”ท [handleCollapseAll] ์ด์ „ ์ƒํƒœ:", { + expandedRowPaths: prev.expandedRowPaths.length, + expandedColumnPaths: prev.expandedColumnPaths.length, + }); + + return { + ...prev, + expandedRowPaths: [], + expandedColumnPaths: [], + }; + }); }, []); // ์…€ ํด๋ฆญ @@ -1391,8 +1426,8 @@ export const PivotGridComponent: React.FC = ({ variant="ghost" size="sm" className="h-7 px-2" - onClick={handleExpandAll} - title="์ „์ฒด ํ™•์žฅ" + onClick={handleCollapseAll} + title="์ „์ฒด ์ถ•์†Œ" > @@ -1401,8 +1436,8 @@ export const PivotGridComponent: React.FC = ({ variant="ghost" size="sm" className="h-7 px-2" - onClick={handleCollapseAll} - title="์ „์ฒด ์ถ•์†Œ" + onClick={handleExpandAll} + title="์ „์ฒด ํ™•์žฅ" > From 02eee979ea306061a118bb0c491e6e013e2ce3f0 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 15:17:49 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=EA=B3=A0=EC=B9=98=EA=B8=B0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pivot-grid/PivotGridComponent.tsx | 53 ++++++++--- .../pivot-grid/components/FieldPanel.tsx | 88 ++++++++++++++++--- 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx index ccfbde88..c30472d8 100644 --- a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx +++ b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx @@ -432,10 +432,20 @@ export const PivotGridComponent: React.FC = ({ // ํ•„ํ„ฐ ์˜์—ญ ํ•„๋“œ const filterFields = useMemo( - () => - fields + () => { + const result = fields .filter((f) => f.area === "filter" && f.visible !== false) - .sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0)), + .sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0)); + + console.log("๐Ÿ”ท [filterFields] ํ•„ํ„ฐ ํ•„๋“œ ๊ณ„์‚ฐ:", { + totalFields: fields.length, + filterFieldsCount: result.length, + filterFieldNames: result.map(f => f.field), + allFieldAreas: fields.map(f => ({ field: f.field, area: f.area, visible: f.visible })), + }); + + return result; + }, [fields] ); @@ -715,7 +725,15 @@ export const PivotGridComponent: React.FC = ({ // ํ•„๋“œ ๋ณ€๊ฒฝ const handleFieldsChange = useCallback( (newFields: PivotFieldConfig[]) => { + console.log("๐Ÿ”ท [handleFieldsChange] ํ•„๋“œ ๋ณ€๊ฒฝ:", { + totalFields: newFields.length, + filterFields: newFields.filter(f => f.area === "filter").length, + filterFieldNames: newFields.filter(f => f.area === "filter").map(f => f.field), + changedFields: newFields.filter(f => f.area === "filter"), + }); + console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ์ „"); setFields(newFields); + console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ํ›„"); }, [] ); @@ -1023,10 +1041,12 @@ export const PivotGridComponent: React.FC = ({ console.log("ํ”ผ๋ฒ— ์ƒํƒœ๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); }, [saveStateToStorage]); - // ์ƒํƒœ ์ดˆ๊ธฐํ™” + // ์ƒํƒœ ์ดˆ๊ธฐํ™” (ํ™•์žฅ/์ถ•์†Œ, ์ •๋ ฌ, ํ•„ํ„ฐ๋งŒ ์ดˆ๊ธฐํ™”, ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€) const handleResetState = useCallback(() => { + // ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ์ƒํƒœ ์ œ๊ฑฐ localStorage.removeItem(stateStorageKey); - setFields(initialFields); + + // ํ™•์žฅ/์ถ•์†Œ, ์ •๋ ฌ, ํ•„ํ„ฐ ์ƒํƒœ๋งŒ ์ดˆ๊ธฐํ™” setPivotState({ expandedRowPaths: [], expandedColumnPaths: [], @@ -1037,7 +1057,10 @@ export const PivotGridComponent: React.FC = ({ setColumnWidths({}); setSelectedCell(null); setSelectionRange(null); - }, [stateStorageKey, initialFields]); + + // ๐Ÿ†• ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€ (initialFields๋กœ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Œ) + console.log("๐Ÿ”ท ํ”ผ๋ฒ— ์ƒํƒœ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค (ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€)"); + }, [stateStorageKey]); // ํ•„๋“œ ์ˆจ๊ธฐ๊ธฐ/ํ‘œ์‹œ ์ƒํƒœ const [hiddenFields, setHiddenFields] = useState>(new Set()); @@ -1617,19 +1640,25 @@ export const PivotGridComponent: React.FC = ({ } /> diff --git a/frontend/lib/registry/components/pivot-grid/components/FieldPanel.tsx b/frontend/lib/registry/components/pivot-grid/components/FieldPanel.tsx index fed43afb..08dca70e 100644 --- a/frontend/lib/registry/components/pivot-grid/components/FieldPanel.tsx +++ b/frontend/lib/registry/components/pivot-grid/components/FieldPanel.tsx @@ -25,6 +25,7 @@ import { horizontalListSortingStrategy, useSortable, } from "@dnd-kit/sortable"; +import { useDroppable } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; import { cn } from "@/lib/utils"; import { PivotFieldConfig, PivotAreaType } from "../types"; @@ -244,22 +245,31 @@ const DroppableArea: React.FC = ({ const areaFields = fields.filter((f) => f.area === area && f.visible !== false); const fieldIds = areaFields.map((f) => `${area}-${f.field}`); + // ๐Ÿ†• ๋“œ๋กญ ๊ฐ€๋Šฅ ์˜์—ญ ์„ค์ • + const { setNodeRef, isOver: isOverDroppable } = useDroppable({ + id: area, // "filter", "column", "row", "data" + }); + + const finalIsOver = isOver || isOverDroppable; + return (
{/* ์˜์—ญ ํ—ค๋” */} -
+
{icon} {title} {areaFields.length > 0 && ( - + {areaFields.length} )} @@ -267,11 +277,16 @@ const DroppableArea: React.FC = ({ {/* ํ•„๋“œ ๋ชฉ๋ก */} -
+
{areaFields.length === 0 ? ( - - ํ•„๋“œ๋ฅผ ์—ฌ๊ธฐ๋กœ ๋“œ๋ž˜๊ทธ - +
+ + โ† ํ•„๋“œ๋ฅผ ์—ฌ๊ธฐ๋กœ ๋“œ๋ž˜๊ทธํ•˜์„ธ์š” + +
) : ( areaFields.map((field) => ( = ({ return; } - // ๋“œ๋กญ ์˜์—ญ ๊ฐ์ง€ + // ๋“œ๋กญ ์˜์—ญ ๊ฐ์ง€ (์˜์—ญ ์ž์ฒด์˜ ID๋ฅผ ์šฐ์„  ํ™•์ธ) const overId = over.id as string; + + // 1. overId๊ฐ€ ์˜์—ญ ์ž์ฒด์ธ ๊ฒฝ์šฐ (filter, column, row, data) + if (["filter", "column", "row", "data"].includes(overId)) { + setOverArea(overId as PivotAreaType); + console.log("๐Ÿ”ท [handleDragOver] ์˜์—ญ ๊ฐ์ง€:", overId); + return; + } + + // 2. overId๊ฐ€ ํ•„๋“œ์ธ ๊ฒฝ์šฐ (์˜ˆ: row-part_name) const targetArea = overId.split("-")[0] as PivotAreaType; if (["filter", "column", "row", "data"].includes(targetArea)) { setOverArea(targetArea); + console.log("๐Ÿ”ท [handleDragOver] ํ•„๋“œ ์˜์—ญ ๊ฐ์ง€:", targetArea); } }; // ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; + const currentOverArea = overArea; // handleDragOver์—์„œ ๊ฐ์ง€ํ•œ ์˜์—ญ ์ €์žฅ setActiveId(null); setOverArea(null); - if (!over) return; + if (!over) { + console.log("๐Ÿ”ท [FieldPanel] ๋“œ๋กญ ๋Œ€์ƒ ์—†์Œ"); + return; + } const activeId = active.id as string; const overId = over.id as string; + console.log("๐Ÿ”ท [FieldPanel] ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ:", { + activeId, + overId, + detectedOverArea: currentOverArea, + }); + // ํ•„๋“œ ์ •๋ณด ํŒŒ์‹ฑ const [sourceArea, sourceField] = activeId.split("-") as [ PivotAreaType, string ]; - const [targetArea] = overId.split("-") as [PivotAreaType, string]; + + // targetArea ๊ฒฐ์ •: handleDragOver์—์„œ ๊ฐ์ง€ํ•œ ์˜์—ญ ์šฐ์„  ์‚ฌ์šฉ + let targetArea: PivotAreaType; + if (currentOverArea) { + targetArea = currentOverArea; + } else if (["filter", "column", "row", "data"].includes(overId)) { + targetArea = overId as PivotAreaType; + } else { + targetArea = overId.split("-")[0] as PivotAreaType; + } + + console.log("๐Ÿ”ท [FieldPanel] ํŒŒ์‹ฑ ๊ฒฐ๊ณผ:", { + sourceArea, + sourceField, + targetArea, + usedOverArea: !!currentOverArea, + }); // ๊ฐ™์€ ์˜์—ญ ๋‚ด ์ •๋ ฌ if (sourceArea === targetArea) { @@ -396,6 +447,12 @@ export const FieldPanel: React.FC = ({ // ๋‹ค๋ฅธ ์˜์—ญ์œผ๋กœ ์ด๋™ if (["filter", "column", "row", "data"].includes(targetArea)) { + console.log("๐Ÿ”ท [FieldPanel] ์˜์—ญ ์ด๋™:", { + field: sourceField, + from: sourceArea, + to: targetArea, + }); + const newFields = fields.map((f) => { if (f.field === sourceField && f.area === sourceArea) { return { @@ -406,6 +463,13 @@ export const FieldPanel: React.FC = ({ } return f; }); + + console.log("๐Ÿ”ท [FieldPanel] ๋ณ€๊ฒฝ๋œ ํ•„๋“œ:", { + totalFields: newFields.length, + filterFields: newFields.filter(f => f.area === "filter").length, + changedField: newFields.find(f => f.field === sourceField), + }); + onFieldsChange(newFields); } }; From b97b0cc7d7316947906ba52b7c01d4a21925e6fd Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 16 Jan 2026 15:52:35 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=ED=99=94=EB=A9=B4=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=20=EA=B4=80=EB=A0=A8=20API=EC=97=90=EC=84=9C=20Authen?= =?UTF-8?q?ticatedRequest=20=ED=83=80=EC=9E=85=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ํ™”๋ฉด ๊ทธ๋ฃน ๋ชฉ๋ก ์กฐํšŒ, ์ƒ์„ธ ์กฐํšŒ, ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ API์—์„œ Request ํƒ€์ž…์„ AuthenticatedRequest๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ช…ํ™•ํžˆ ์ฒ˜๋ฆฌ - companyCode๋ฅผ req.user?.companyCode || "*"๋กœ ์„ค์ •ํ•˜์—ฌ ๊ธฐ๋ณธ๊ฐ’ ์ฒ˜๋ฆฌ ๊ฐœ์„  - ๊ด€๋ จ API์˜ ์ผ๊ด€์„ฑ ์žˆ๋Š” ํƒ€์ž… ์‚ฌ์šฉ์œผ๋กœ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ --- .../src/controllers/screenGroupController.ts | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/backend-node/src/controllers/screenGroupController.ts b/backend-node/src/controllers/screenGroupController.ts index b89ef902..43ccce32 100644 --- a/backend-node/src/controllers/screenGroupController.ts +++ b/backend-node/src/controllers/screenGroupController.ts @@ -1,6 +1,7 @@ import { Request, Response } from "express"; import { getPool } from "../database/db"; import { logger } from "../utils/logger"; +import { AuthenticatedRequest } from "../types/auth"; import { syncScreenGroupsToMenu, syncMenuToScreenGroups, @@ -16,9 +17,9 @@ const pool = getPool(); // ============================================================ // ํ™”๋ฉด ๊ทธ๋ฃน ๋ชฉ๋ก ์กฐํšŒ -export const getScreenGroups = async (req: Request, res: Response) => { +export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { page = 1, size = 20, searchTerm } = req.query; const offset = (parseInt(page as string) - 1) * parseInt(size as string); @@ -90,10 +91,10 @@ export const getScreenGroups = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ƒ์„ธ ์กฐํšŒ -export const getScreenGroup = async (req: Request, res: Response) => { +export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; let query = ` SELECT sg.*, @@ -136,10 +137,10 @@ export const getScreenGroup = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ƒ์„ฑ -export const createScreenGroup = async (req: Request, res: Response) => { +export const createScreenGroup = async (req: AuthenticatedRequest, res: Response) => { try { - const userCompanyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const userCompanyCode = req.user?.companyCode || "*"; + const userId = req.user?.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) { @@ -210,10 +211,10 @@ export const createScreenGroup = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ๊ทธ๋ฃน ์ˆ˜์ • -export const updateScreenGroup = async (req: Request, res: Response) => { +export const updateScreenGroup = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const userCompanyCode = (req.user as any).companyCode; + const userCompanyCode = req.user?.companyCode || "*"; const { group_name, group_code, main_table_name, description, icon, display_order, is_active, parent_group_id, target_company_code } = req.body; // ํšŒ์‚ฌ ์ฝ”๋“œ ๊ฒฐ์ •: ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํšŒ์‚ฌ๋ฅผ ์„ ํƒํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํšŒ์‚ฌ๋กœ, ์•„๋‹ˆ๋ฉด ํ˜„์žฌ ๊ทธ๋ฃน์˜ ํšŒ์‚ฌ ์œ ์ง€ @@ -299,11 +300,11 @@ export const updateScreenGroup = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ๊ทธ๋ฃน ์‚ญ์ œ -export const deleteScreenGroup = async (req: Request, res: Response) => { +export const deleteScreenGroup = async (req: AuthenticatedRequest, res: Response) => { const client = await pool.connect(); try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; await client.query('BEGIN'); @@ -366,10 +367,10 @@ export const deleteScreenGroup = async (req: Request, res: Response) => { // ============================================================ // ๊ทธ๋ฃน์— ํ™”๋ฉด ์ถ”๊ฐ€ -export const addScreenToGroup = async (req: Request, res: Response) => { +export const addScreenToGroup = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const companyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { group_id, screen_id, screen_role, display_order, is_default } = req.body; if (!group_id || !screen_id) { @@ -406,10 +407,10 @@ export const addScreenToGroup = async (req: Request, res: Response) => { }; // ๊ทธ๋ฃน์—์„œ ํ™”๋ฉด ์ œ๊ฑฐ -export const removeScreenFromGroup = async (req: Request, res: Response) => { +export const removeScreenFromGroup = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; let query = `DELETE FROM screen_group_screens WHERE id = $1`; const params: any[] = [id]; @@ -437,10 +438,10 @@ export const removeScreenFromGroup = async (req: Request, res: Response) => { }; // ๊ทธ๋ฃน ๋‚ด ํ™”๋ฉด ์ˆœ์„œ/์—ญํ•  ์ˆ˜์ • -export const updateScreenInGroup = async (req: Request, res: Response) => { +export const updateScreenInGroup = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { screen_role, display_order, is_default } = req.body; let query = ` @@ -476,9 +477,9 @@ export const updateScreenInGroup = async (req: Request, res: Response) => { // ============================================================ // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ๋ชฉ๋ก ์กฐํšŒ -export const getFieldJoins = async (req: Request, res: Response) => { +export const getFieldJoins = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { screen_id } = req.query; let query = ` @@ -517,10 +518,10 @@ export const getFieldJoins = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์ƒ์„ฑ -export const createFieldJoin = async (req: Request, res: Response) => { +export const createFieldJoin = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const companyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { screen_id, layout_id, component_id, field_name, save_table, save_column, join_table, join_column, display_column, @@ -558,10 +559,10 @@ export const createFieldJoin = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์ˆ˜์ • -export const updateFieldJoin = async (req: Request, res: Response) => { +export const updateFieldJoin = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { layout_id, component_id, field_name, save_table, save_column, join_table, join_column, display_column, @@ -603,10 +604,10 @@ export const updateFieldJoin = async (req: Request, res: Response) => { }; // ํ™”๋ฉด ํ•„๋“œ ์กฐ์ธ ์‚ญ์ œ -export const deleteFieldJoin = async (req: Request, res: Response) => { +export const deleteFieldJoin = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; let query = `DELETE FROM screen_field_joins WHERE id = $1`; const params: any[] = [id]; @@ -637,9 +638,9 @@ export const deleteFieldJoin = async (req: Request, res: Response) => { // ============================================================ // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ชฉ๋ก ์กฐํšŒ -export const getDataFlows = async (req: Request, res: Response) => { +export const getDataFlows = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { group_id, source_screen_id } = req.query; let query = ` @@ -687,10 +688,10 @@ export const getDataFlows = async (req: Request, res: Response) => { }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ƒ์„ฑ -export const createDataFlow = async (req: Request, res: Response) => { +export const createDataFlow = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const companyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { group_id, source_screen_id, source_action, target_screen_id, target_action, data_mapping, flow_type, flow_label, condition_expression, is_active @@ -726,10 +727,10 @@ export const createDataFlow = async (req: Request, res: Response) => { }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ˆ˜์ • -export const updateDataFlow = async (req: Request, res: Response) => { +export const updateDataFlow = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { group_id, source_screen_id, source_action, target_screen_id, target_action, data_mapping, flow_type, flow_label, condition_expression, is_active @@ -769,10 +770,10 @@ export const updateDataFlow = async (req: Request, res: Response) => { }; // ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์‚ญ์ œ -export const deleteDataFlow = async (req: Request, res: Response) => { +export const deleteDataFlow = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; let query = `DELETE FROM screen_data_flows WHERE id = $1`; const params: any[] = [id]; @@ -803,9 +804,9 @@ export const deleteDataFlow = async (req: Request, res: Response) => { // ============================================================ // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ -export const getTableRelations = async (req: Request, res: Response) => { +export const getTableRelations = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { screen_id, group_id } = req.query; let query = ` @@ -852,10 +853,10 @@ export const getTableRelations = async (req: Request, res: Response) => { }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์ƒ์„ฑ -export const createTableRelation = async (req: Request, res: Response) => { +export const createTableRelation = async (req: AuthenticatedRequest, res: Response) => { try { - const companyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const companyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { group_id, screen_id, table_name, relation_type, crud_operations, description, is_active } = req.body; if (!screen_id || !table_name) { @@ -885,10 +886,10 @@ export const createTableRelation = async (req: Request, res: Response) => { }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์ˆ˜์ • -export const updateTableRelation = async (req: Request, res: Response) => { +export const updateTableRelation = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; const { group_id, table_name, relation_type, crud_operations, description, is_active } = req.body; let query = ` @@ -920,10 +921,10 @@ export const updateTableRelation = async (req: Request, res: Response) => { }; // ํ™”๋ฉด-ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์‚ญ์ œ -export const deleteTableRelation = async (req: Request, res: Response) => { +export const deleteTableRelation = async (req: AuthenticatedRequest, res: Response) => { try { const { id } = req.params; - const companyCode = (req.user as any).companyCode; + const companyCode = req.user?.companyCode || "*"; let query = `DELETE FROM screen_table_relations WHERE id = $1`; const params: any[] = [id]; @@ -953,7 +954,7 @@ export const deleteTableRelation = async (req: Request, res: Response) => { // ============================================================ // ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์š”์•ฝ ์กฐํšŒ (์œ„์ ฏ ํƒ€์ž…๋ณ„ ๊ฐœ์ˆ˜, ๋ผ๋ฒจ ๋ชฉ๋ก) -export const getScreenLayoutSummary = async (req: Request, res: Response) => { +export const getScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => { try { const { screenId } = req.params; @@ -1021,7 +1022,7 @@ export const getScreenLayoutSummary = async (req: Request, res: Response) => { }; // ์—ฌ๋Ÿฌ ํ™”๋ฉด์˜ ๋ ˆ์ด์•„์›ƒ ์š”์•ฝ ์ผ๊ด„ ์กฐํšŒ (๋ฏธ๋‹ˆ์–ด์ฒ˜ ๋ Œ๋”๋ง์šฉ ์ขŒํ‘œ ํฌํ•จ) -export const getMultipleScreenLayoutSummary = async (req: Request, res: Response) => { +export const getMultipleScreenLayoutSummary = async (req: AuthenticatedRequest, res: Response) => { try { const { screenIds } = req.body; @@ -1221,7 +1222,7 @@ export const getMultipleScreenLayoutSummary = async (req: Request, res: Response // ============================================================ // ์—ฌ๋Ÿฌ ํ™”๋ฉด์˜ ์„œ๋ธŒ ํ…Œ์ด๋ธ” ์ •๋ณด ์กฐํšŒ (๋ฉ”์ธ ํ…Œ์ด๋ธ” โ†’ ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๊ด€๊ณ„) -export const getScreenSubTables = async (req: Request, res: Response) => { +export const getScreenSubTables = async (req: AuthenticatedRequest, res: Response) => { try { const { screenIds } = req.body; @@ -2060,10 +2061,10 @@ export const getScreenSubTables = async (req: Request, res: Response) => { * ํ™”๋ฉด๊ด€๋ฆฌ โ†’ ๋ฉ”๋‰ด ๋™๊ธฐํ™” * screen_groups๋ฅผ menu_info๋กœ ๋™๊ธฐํ™” */ -export const syncScreenGroupsToMenuController = async (req: Request, res: Response) => { +export const syncScreenGroupsToMenuController = async (req: AuthenticatedRequest, res: Response) => { try { - const userCompanyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const userCompanyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { targetCompanyCode } = req.body; // ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํšŒ์‚ฌ๋ฅผ ์ง€์ •ํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํšŒ์‚ฌ๋กœ @@ -2111,10 +2112,10 @@ export const syncScreenGroupsToMenuController = async (req: Request, res: Respon * ๋ฉ”๋‰ด โ†’ ํ™”๋ฉด๊ด€๋ฆฌ ๋™๊ธฐํ™” * menu_info๋ฅผ screen_groups๋กœ ๋™๊ธฐํ™” */ -export const syncMenuToScreenGroupsController = async (req: Request, res: Response) => { +export const syncMenuToScreenGroupsController = async (req: AuthenticatedRequest, res: Response) => { try { - const userCompanyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const userCompanyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; const { targetCompanyCode } = req.body; // ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํšŒ์‚ฌ๋ฅผ ์ง€์ •ํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํšŒ์‚ฌ๋กœ @@ -2161,9 +2162,9 @@ export const syncMenuToScreenGroupsController = async (req: Request, res: Respon /** * ๋™๊ธฐํ™” ์ƒํƒœ ์กฐํšŒ */ -export const getSyncStatusController = async (req: Request, res: Response) => { +export const getSyncStatusController = async (req: AuthenticatedRequest, res: Response) => { try { - const userCompanyCode = (req.user as any).companyCode; + const userCompanyCode = req.user?.companyCode || "*"; const { targetCompanyCode } = req.query; // ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํšŒ์‚ฌ๋ฅผ ์ง€์ •ํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํšŒ์‚ฌ๋กœ @@ -2200,10 +2201,10 @@ export const getSyncStatusController = async (req: Request, res: Response) => { * ์ „์ฒด ํšŒ์‚ฌ ๋™๊ธฐํ™” * ๋ชจ๋“  ํšŒ์‚ฌ์— ๋Œ€ํ•ด ์–‘๋ฐฉํ–ฅ ๋™๊ธฐํ™” ์ˆ˜ํ–‰ (์ตœ๊ณ  ๊ด€๋ฆฌ์ž๋งŒ) */ -export const syncAllCompaniesController = async (req: Request, res: Response) => { +export const syncAllCompaniesController = async (req: AuthenticatedRequest, res: Response) => { try { - const userCompanyCode = (req.user as any).companyCode; - const userId = (req.user as any).userId; + const userCompanyCode = req.user?.companyCode || "*"; + const userId = req.user?.userId || ""; // ์ตœ๊ณ  ๊ด€๋ฆฌ์ž๋งŒ ์ „์ฒด ๋™๊ธฐํ™” ๊ฐ€๋Šฅ if (userCompanyCode !== "*") { From 44979851049cae0a316c192e33089f53195765df Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 16:11:34 +0900 Subject: [PATCH 4/7] Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into lhj From 351ecbb35d5c728449f7f056985287b31d2c260d Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 16:24:43 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=EB=B0=B0=ED=8F=AC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=95=88=EB=82=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/pivot-grid/PivotGridComponent.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx index c30472d8..f7c03b7d 100644 --- a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx +++ b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx @@ -725,14 +725,18 @@ export const PivotGridComponent: React.FC = ({ // ํ•„๋“œ ๋ณ€๊ฒฝ const handleFieldsChange = useCallback( (newFields: PivotFieldConfig[]) => { + // ๐Ÿ†• visible: false ํ•„๋“œ ์ œ๊ฑฐ (FieldChooser์—์„œ "์‚ฌ์šฉ ์•ˆํ•จ"์œผ๋กœ ์„ค์ •ํ•œ ํ•„๋“œ) + const visibleFields = newFields.filter(f => f.visible !== false); + console.log("๐Ÿ”ท [handleFieldsChange] ํ•„๋“œ ๋ณ€๊ฒฝ:", { totalFields: newFields.length, - filterFields: newFields.filter(f => f.area === "filter").length, - filterFieldNames: newFields.filter(f => f.area === "filter").map(f => f.field), - changedFields: newFields.filter(f => f.area === "filter"), + visibleFields: visibleFields.length, + removedFields: newFields.length - visibleFields.length, + filterFields: visibleFields.filter(f => f.area === "filter").length, + filterFieldNames: visibleFields.filter(f => f.area === "filter").map(f => f.field), }); console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ์ „"); - setFields(newFields); + setFields(visibleFields); console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ํ›„"); }, [] From d1631d15ffe6002560aa6d4cf15dfca1b09060af Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 16:49:59 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=EC=95=88=EB=8B=AB=ED=9E=88=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pivot-grid/components/FieldChooser.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/lib/registry/components/pivot-grid/components/FieldChooser.tsx b/frontend/lib/registry/components/pivot-grid/components/FieldChooser.tsx index 89fe5128..a948aba0 100644 --- a/frontend/lib/registry/components/pivot-grid/components/FieldChooser.tsx +++ b/frontend/lib/registry/components/pivot-grid/components/FieldChooser.tsx @@ -267,11 +267,13 @@ export const FieldChooser: React.FC = ({ const existingConfig = selectedFields.find((f) => f.field === field.field); if (area === "none") { - // ํ•„๋“œ ์ œ๊ฑฐ ๋˜๋Š” ์ˆจ๊ธฐ๊ธฐ + // ๐Ÿ†• ํ•„๋“œ ์™„์ „ ์ œ๊ฑฐ (visible: false ๋Œ€์‹  ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐ) if (existingConfig) { - const newFields = selectedFields.map((f) => - f.field === field.field ? { ...f, visible: false } : f - ); + const newFields = selectedFields.filter((f) => f.field !== field.field); + console.log("๐Ÿ”ท [FieldChooser] ํ•„๋“œ ์ œ๊ฑฐ:", { + removedField: field.field, + remainingFields: newFields.length, + }); onFieldsChange(newFields); } } else { @@ -282,6 +284,10 @@ export const FieldChooser: React.FC = ({ ? { ...f, area, visible: true } : f ); + console.log("๐Ÿ”ท [FieldChooser] ํ•„๋“œ ์˜์—ญ ๋ณ€๊ฒฝ:", { + field: field.field, + newArea: area, + }); onFieldsChange(newFields); } else { // ์ƒˆ ํ•„๋“œ ์ถ”๊ฐ€ @@ -294,6 +300,10 @@ export const FieldChooser: React.FC = ({ summaryType: area === "data" ? "sum" : undefined, areaIndex: selectedFields.filter((f) => f.area === area).length, }; + console.log("๐Ÿ”ท [FieldChooser] ํ•„๋“œ ์ถ”๊ฐ€:", { + field: field.field, + area, + }); onFieldsChange([...selectedFields, newField]); } } From 2a3cc7ba006ecf3ea92df4928624fd1057a45c32 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Fri, 16 Jan 2026 17:39:35 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=EB=B0=B0=ED=8F=AC=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EB=90=98=EA=B2=8C=20=EA=B3=A0=EC=B3=90=EB=86=93=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pivot-grid/PivotGridComponent.tsx | 101 +++++++++--------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx index f7c03b7d..9135231c 100644 --- a/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx +++ b/frontend/lib/registry/components/pivot-grid/PivotGridComponent.tsx @@ -384,20 +384,36 @@ export const PivotGridComponent: React.FC = ({ localStorage.setItem(stateStorageKey, JSON.stringify(stateToSave)); }, [fields, pivotState, sortConfig, columnWidths, stateStorageKey]); - // ์ƒํƒœ ๋ณต์› (localStorage) + // ์ƒํƒœ ๋ณต์› (localStorage) - ํ”„๋กœ๋•์…˜ ์•ˆ์ „์„ฑ ๊ฐ•ํ™” useEffect(() => { if (typeof window === "undefined") return; - const savedState = localStorage.getItem(stateStorageKey); - if (savedState) { - try { - const parsed = JSON.parse(savedState); - if (parsed.fields) setFields(parsed.fields); - if (parsed.pivotState) setPivotState(parsed.pivotState); - if (parsed.sortConfig) setSortConfig(parsed.sortConfig); - if (parsed.columnWidths) setColumnWidths(parsed.columnWidths); - } catch (e) { - console.warn("ํ”ผ๋ฒ— ์ƒํƒœ ๋ณต์› ์‹คํŒจ:", e); + + try { + const savedState = localStorage.getItem(stateStorageKey); + if (!savedState) return; + + const parsed = JSON.parse(savedState); + + // ํ•„๋“œ ๋ณต์› ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (์ค‘์š”!) + if (parsed.fields && Array.isArray(parsed.fields) && parsed.fields.length > 0) { + // ์ €์žฅ๋œ ํ•„๋“œ๊ฐ€ ํ˜„์žฌ ๋ฐ์ดํ„ฐ์™€ ํ˜ธํ™˜๋˜๋Š”์ง€ ํ™•์ธ + const validFields = parsed.fields.filter((f: PivotFieldConfig) => + f && typeof f.field === "string" && typeof f.area === "string" + ); + + if (validFields.length > 0) { + setFields(validFields); + } } + + // ๋‚˜๋จธ์ง€ ์ƒํƒœ ๋ณต์› + if (parsed.pivotState) setPivotState(parsed.pivotState); + if (parsed.sortConfig) setSortConfig(parsed.sortConfig); + if (parsed.columnWidths) setColumnWidths(parsed.columnWidths); + } catch (e) { + console.warn("ํ”ผ๋ฒ— ์ƒํƒœ ๋ณต์› ์‹คํŒจ, localStorage ์ดˆ๊ธฐํ™”:", e); + // ์†์ƒ๋œ ์ƒํƒœ๋Š” ์ œ๊ฑฐ + localStorage.removeItem(stateStorageKey); } }, [stateStorageKey]); @@ -512,15 +528,15 @@ export const PivotGridComponent: React.FC = ({ return null; } - const visibleFields = fields.filter((f) => f.visible !== false); + // FieldChooser์—์„œ ์ด๋ฏธ ํ•„๋“œ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๋ฏ€๋กœ visible ํ•„ํ„ฐ๋ง ๋ถˆํ•„์š” // ํ–‰, ์—ด, ๋ฐ์ดํ„ฐ ์˜์—ญ์— ํ•„๋“œ๊ฐ€ ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด null ๋ฐ˜ํ™˜ (ํ•„ํ„ฐ๋Š” ์ œ์™ธ) - if (visibleFields.filter((f) => ["row", "column", "data"].includes(f.area)).length === 0) { + if (fields.filter((f) => ["row", "column", "data"].includes(f.area)).length === 0) { return null; } const result = processPivotData( filteredData, - visibleFields, + fields, pivotState.expandedRowPaths, pivotState.expandedColumnPaths ); @@ -536,32 +552,18 @@ export const PivotGridComponent: React.FC = ({ }); return result; - }, [ - filteredData, - fields, - JSON.stringify(pivotState.expandedRowPaths), - JSON.stringify(pivotState.expandedColumnPaths) - ]); + }, [filteredData, fields, pivotState.expandedRowPaths, pivotState.expandedColumnPaths]); - // ๐Ÿ†• ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ์ฒซ ๋ ˆ๋ฒจ ์ž๋™ ํ™•์žฅ + // ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ์ฒซ ๋ ˆ๋ฒจ ์ž๋™ ํ™•์žฅ useEffect(() => { if (pivotResult && pivotResult.flatRows.length > 0 && !isInitialExpanded) { - console.log("๐Ÿ”ถ ํ”ผ๋ฒ— ๊ฒฐ๊ณผ ์ƒ์„ฑ๋จ:", { - flatRowsCount: pivotResult.flatRows.length, - expandedRowPaths: pivotState.expandedRowPaths.length, - isInitialExpanded, - }); - // ์ฒซ ๋ ˆ๋ฒจ ํ–‰๋“ค์˜ ๊ฒฝ๋กœ ์ˆ˜์ง‘ (level 0์ธ ํ–‰๋“ค) - const firstLevelRows = pivotResult.flatRows.filter(row => row.level === 0 && row.hasChildren); - - console.log("๐Ÿ”ถ ์ฒซ ๋ ˆ๋ฒจ ํ–‰ (level 0, hasChildren):", firstLevelRows.map(r => ({ path: r.path, caption: r.caption }))); + const firstLevelRows = pivotResult.flatRows.filter((row) => row.level === 0 && row.hasChildren); // ์ฒซ ๋ ˆ๋ฒจ ํ–‰์ด ์žˆ์œผ๋ฉด ์ž๋™ ํ™•์žฅ if (firstLevelRows.length > 0) { - const firstLevelPaths = firstLevelRows.map(row => row.path); - console.log("๐Ÿ”ถ ์ดˆ๊ธฐ ์ž๋™ ํ™•์žฅ ์‹คํ–‰ (ํ•œ ๋ฒˆ๋งŒ):", firstLevelPaths); - setPivotState(prev => ({ + const firstLevelPaths = firstLevelRows.map((row) => row.path); + setPivotState((prev) => ({ ...prev, expandedRowPaths: firstLevelPaths, })); @@ -725,19 +727,16 @@ export const PivotGridComponent: React.FC = ({ // ํ•„๋“œ ๋ณ€๊ฒฝ const handleFieldsChange = useCallback( (newFields: PivotFieldConfig[]) => { - // ๐Ÿ†• visible: false ํ•„๋“œ ์ œ๊ฑฐ (FieldChooser์—์„œ "์‚ฌ์šฉ ์•ˆํ•จ"์œผ๋กœ ์„ค์ •ํ•œ ํ•„๋“œ) - const visibleFields = newFields.filter(f => f.visible !== false); - + // FieldChooser์—์„œ ์ด๋ฏธ ํ•„๋“œ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๋ฏ€๋กœ ์ถ”๊ฐ€ ํ•„ํ„ฐ๋ง ๋ถˆํ•„์š” console.log("๐Ÿ”ท [handleFieldsChange] ํ•„๋“œ ๋ณ€๊ฒฝ:", { totalFields: newFields.length, - visibleFields: visibleFields.length, - removedFields: newFields.length - visibleFields.length, - filterFields: visibleFields.filter(f => f.area === "filter").length, - filterFieldNames: visibleFields.filter(f => f.area === "filter").map(f => f.field), + filterFields: newFields.filter(f => f.area === "filter").length, + filterFieldNames: newFields.filter(f => f.area === "filter").map(f => f.field), + rowFields: newFields.filter(f => f.area === "row").length, + columnFields: newFields.filter(f => f.area === "column").length, + dataFields: newFields.filter(f => f.area === "data").length, }); - console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ์ „"); - setFields(visibleFields); - console.log("๐Ÿ”ท [handleFieldsChange] setFields ํ˜ธ์ถœ ํ›„"); + setFields(newFields); }, [] ); @@ -945,6 +944,8 @@ export const PivotGridComponent: React.FC = ({ // ์ธ์‡„ ๊ธฐ๋Šฅ (PDF ๋‚ด๋ณด๋‚ด๊ธฐ๋ณด๋‹ค ๋จผ์ € ์ •์˜ํ•ด์•ผ ํ•จ) const handlePrint = useCallback(() => { + if (typeof window === "undefined") return; + const printContent = tableRef.current; if (!printContent) return; @@ -1047,8 +1048,10 @@ export const PivotGridComponent: React.FC = ({ // ์ƒํƒœ ์ดˆ๊ธฐํ™” (ํ™•์žฅ/์ถ•์†Œ, ์ •๋ ฌ, ํ•„ํ„ฐ๋งŒ ์ดˆ๊ธฐํ™”, ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€) const handleResetState = useCallback(() => { - // ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ์ƒํƒœ ์ œ๊ฑฐ - localStorage.removeItem(stateStorageKey); + // ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ์ƒํƒœ ์ œ๊ฑฐ (SSR ๋ณดํ˜ธ) + if (typeof window !== "undefined") { + localStorage.removeItem(stateStorageKey); + } // ํ™•์žฅ/์ถ•์†Œ, ์ •๋ ฌ, ํ•„ํ„ฐ ์ƒํƒœ๋งŒ ์ดˆ๊ธฐํ™” setPivotState({ @@ -1061,9 +1064,6 @@ export const PivotGridComponent: React.FC = ({ setColumnWidths({}); setSelectedCell(null); setSelectionRange(null); - - // ๐Ÿ†• ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€ (initialFields๋กœ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Œ) - console.log("๐Ÿ”ท ํ”ผ๋ฒ— ์ƒํƒœ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค (ํ•„๋“œ ์„ค์ •์€ ์œ ์ง€)"); }, [stateStorageKey]); // ํ•„๋“œ ์ˆจ๊ธฐ๊ธฐ/ํ‘œ์‹œ ์ƒํƒœ @@ -1081,11 +1081,6 @@ export const PivotGridComponent: React.FC = ({ }); }, []); - // ์ˆจ๊ฒจ์ง„ ํ•„๋“œ ์ œ์™ธํ•œ ํ™œ์„ฑ ํ•„๋“œ๋“ค - const visibleFields = useMemo(() => { - return fields.filter((f) => !hiddenFields.has(f.field)); - }, [fields, hiddenFields]); - // ์ˆจ๊ฒจ์ง„ ํ•„๋“œ ๋ชฉ๋ก const hiddenFieldsList = useMemo(() => { return fields.filter((f) => hiddenFields.has(f.field));