From 350ddcd3b895bdd365d63951eac09f790f744d7d Mon Sep 17 00:00:00 2001 From: hjjeong Date: Thu, 14 May 2026 15:12:34 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=E2=80=94=20DataGrid=20=ED=96=89=20id=20=EB=A7=A4?= =?UTF-8?q?=ED=95=91=20=EB=88=84=EB=9D=BD=20+=20selectedId=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 증상: 프로젝트관리 진행관리/WBS 그리드의 모든 행이 회색(bg-accent) 으로 표시. 원인 추적 결과 DataGrid 의 isSelected 평가식 `selectedId === row.id` 가 selectedId 도 row.id 도 둘 다 undefined 일 때 `undefined === undefined` = true 가 되어 모든 행이 selected 상태로 잡힘 (memory: feedback_datagrid_id_mapping 함정의 또 다른 발현 — 영업관리는 항상 id 매핑이 있어 우연히 회피). 수정: - project/progress/page.tsx, project/wbs-template/page.tsx - setRows 에서 `id: r.objid ?? "...-${i}"` 매핑 추가 - 부모 wrapper 도 영업관리 패턴 `overflow-hidden` + DataGrid 직접 자식으로 통일 - components/common/DataGrid.tsx - isSelected 가드 — selectedId/row.id 가 nullish 면 무조건 false 처리. 향후 다른 페이지에서 id 매핑 누락 시에도 그리드 색 폭주는 차단. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../COMPANY_16/project/progress/page.tsx | 53 ++++++++-------- .../COMPANY_16/project/wbs-template/page.tsx | 61 +++++++++---------- frontend/components/common/DataGrid.tsx | 5 +- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/frontend/app/(main)/COMPANY_16/project/progress/page.tsx b/frontend/app/(main)/COMPANY_16/project/progress/page.tsx index 207fd6a1..99c8f260 100644 --- a/frontend/app/(main)/COMPANY_16/project/progress/page.tsx +++ b/frontend/app/(main)/COMPANY_16/project/progress/page.tsx @@ -126,7 +126,8 @@ export default function ProjectProgressPage() { setLoading(true); try { const data = await projectMgmtApi.list(filter); - setRows(data); + // DataGrid row 키 — objid 없을 수 있어 인덱스 fallback (id 누락 시 모든 행이 selected 로 잡힘) + setRows(data.map((r, i) => ({ ...r, id: r.objid ?? `prog-${i}` })) as any); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패"); } finally { setLoading(false); } @@ -165,7 +166,7 @@ export default function ProjectProgressPage() { }, [rows]); return ( -
+
-
- { - if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } - const exportRows = rows.map((r) => { - const out: Record = {}; - GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); - return out; - }); - exportToExcel(exportRows, "진행관리.xlsx", "진행관리"); - }} - showChart - /> -
+ { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "진행관리.xlsx", "진행관리"); + }} + showChart + />
diff --git a/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx b/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx index b0918928..48592599 100644 --- a/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx +++ b/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx @@ -53,7 +53,8 @@ export default function WbsTemplatePage() { setLoading(true); try { const data = await wbsTemplateApi.list(product || undefined); - setRows(data); + // DataGrid row 키 — objid 없을 수 있어 인덱스 fallback (id 누락 시 모든 행이 selected 로 잡힘) + setRows(data.map((r, i) => ({ ...r, id: r.objid ?? `tpl-${i}` })) as any); setCheckedIds([]); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패"); @@ -126,7 +127,7 @@ export default function WbsTemplatePage() { }, [rows]); return ( -
+
-
- fetchList(filterProduct)} - onDownload={() => { - if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } - const exportRows = rows.map((r) => { - const out: Record = {}; - GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); - return out; - }); - exportToExcel(exportRows, "WBS_템플릿.xlsx", "WBS_템플릿"); - }} - showChart - /> -
+ fetchList(filterProduct)} + onDownload={() => { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "WBS_템플릿.xlsx", "WBS_템플릿"); + }} + showChart + /> {/* 통합 팝업 */} ) : paginatedData.map((row, rowIdx) => { - const isSelected = selectedId === row.id || (showCheckbox && checkedIds.includes(row.id)); + // selectedId 또는 row.id 가 nullish 면 비교 결과를 무조건 false 로 — 둘 다 undefined 일 때 `undefined === undefined` 가 true 가 되어 모든 행이 selected 로 잡히는 함정 차단 + const isSelected = + (selectedId != null && row.id != null && selectedId === row.id) || + (showCheckbox && row.id != null && checkedIds.includes(row.id)); // sticky 셀에 alpha 없는 단색 배경 사용 (반투명이면 뒤 셀이 비침). // selected → bg-accent / hover(non-selected) → group-hover로 muted 적용 / 기본 → bg-background const stickyBgClass = isSelected ? "bg-accent" : "bg-background group-hover:bg-muted";