fix: 피벗 ConfigPanel — FeatureChipGrid 로 교체 + PivotView 빈 그리드 회피

이전 commit (3ed53a670, 57ffbcbbc) 의 두 잘못 수정.

1. ConfigPanel — CPSwitch 8개 → FeatureChipGrid 1개

   CP 시스템에 다중 boolean 토글 묶음용 FeatureChipGrid 가 이미 있는데
   (CPExtras.tsx:208, InvLegacyDivider/Button/Text/InvRepeater 가 사용),
   CPRow + CPSwitch 8개로 따로 만든 게 잘못. cp 시스템 본래 패턴 따름.

   - 8 토글 (chartEnabled / fieldChooserEnabled / rowGrandTotals /
     columnGrandTotals / mergeCells / alternateRowColors / exportExcel /
     exportPdf) 을 평면 key 로 정의
   - source = nested config 에서 평면 boolean 객체 변환
   - onToggle = 평면 key 받아 nested patch (switch 분기)
   - 각 chip 에 desc 추가 (hover tooltip, FeatureChipGrid 가 portal 로 표시)

2. PivotView — data 영역 0개면 안내 (빈 0 그리드 회피)

   hasActiveFields 분기를 강화. 기존: row/column/data 중 하나만 있어도
   true → row 영역에만 컬럼이 들어간 옛 잘못된 매핑이 빈 0 그리드를 표시
   하는 회귀 (Image #6).

   변경: data 영역 컬럼 ≥1 이어야 의미있는 피벗. data 0개면 "필드를
   배치하세요" 안내 + FieldChooser 버튼 fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DDD1542
2026-04-29 16:54:30 +09:00
parent 57ffbcbbc7
commit 2d11d222db
2 changed files with 78 additions and 117 deletions
@@ -282,121 +282,82 @@ export const InvTableConfigPanel: React.FC<InvTableConfigPanelProps> = ({
{displayMode === "pivot" && (
<CPSection title="④ 피벗 설정" desc="본체 분석 UI에서 사용할 표시 옵션">
{/* 피벗 필드 배치는 PivotView 본체의 FieldPanel/FieldChooser가 담당한다. ConfigPanel은 메타 옵션만 관리한다. */}
<CPRow label="차트 표시">
<CPSwitch
value={current.pivotChart?.enabled ?? false}
onChange={(v) =>
patch({
pivotChart: {
type: current.pivotChart?.type ?? "bar",
position: current.pivotChart?.position ?? "bottom",
...current.pivotChart,
enabled: v,
},
})
}
/>
</CPRow>
<CPRow label="필드 선택기">
<CPSwitch
value={current.pivotFieldChooser?.enabled ?? false}
onChange={(v) =>
patch({
pivotFieldChooser: {
...current.pivotFieldChooser,
enabled: v,
},
})
}
/>
</CPRow>
<CPRow label="행 총계">
<CPSwitch
value={current.pivotTotals?.showRowGrandTotals ?? false}
onChange={(v) =>
patch({
pivotTotals: {
...current.pivotTotals,
showRowGrandTotals: v,
},
})
}
/>
</CPRow>
<CPRow label="열 총계">
<CPSwitch
value={current.pivotTotals?.showColumnGrandTotals ?? false}
onChange={(v) =>
patch({
pivotTotals: {
...current.pivotTotals,
showColumnGrandTotals: v,
},
})
}
/>
</CPRow>
<CPRow label="셀 병합">
<CPSwitch
value={current.pivotStyle?.mergeCells ?? false}
onChange={(v) =>
patch({
pivotStyle: {
theme: current.pivotStyle?.theme ?? "default",
headerStyle: current.pivotStyle?.headerStyle ?? "default",
cellPadding: current.pivotStyle?.cellPadding ?? "normal",
borderStyle: current.pivotStyle?.borderStyle ?? "light",
{/* 피벗 필드 배치는 PivotView 본체의 FieldPanel/FieldChooser가 담당한다. ConfigPanel은 메타 토글만 관리한다. */}
<FeatureChipGrid
items={[
{ key: "chartEnabled", label: "차트 표시", desc: "본체 안에 피벗 차트(bar/line/pie 등) 패널을 함께 표시합니다." },
{ key: "fieldChooserEnabled", label: "필드 선택기", desc: "본체에서 row/column/data/filter 영역에 컬럼을 드래그-앤-드롭 배치하는 모달을 활성화." },
{ key: "rowGrandTotals", label: "행 총계", desc: "각 행의 합계 행을 자동 표시합니다. row 영역 컬럼이 있을 때 의미가 있습니다." },
{ key: "columnGrandTotals", label: "열 총계", desc: "각 열의 합계 열을 자동 표시합니다. column 영역 컬럼이 있을 때 의미가 있습니다." },
{ key: "mergeCells", label: "셀 병합", desc: "같은 값의 인접 셀을 병합해 가독성을 높입니다." },
{ key: "alternateRowColors", label: "행 교대 색", desc: "홀수/짝수 행을 다른 톤으로 교대 표시 (Zebra)." },
{ key: "exportExcel", label: "엑셀 내보내기", desc: "본체 툴바에 .xlsx 다운로드 버튼을 활성화." },
{ key: "exportPdf", label: "PDF 내보내기", desc: "본체 툴바에 PDF 다운로드 버튼을 활성화." },
]}
source={{
chartEnabled: current.pivotChart?.enabled ?? false,
fieldChooserEnabled: current.pivotFieldChooser?.enabled ?? false,
rowGrandTotals: current.pivotTotals?.showRowGrandTotals ?? false,
columnGrandTotals: current.pivotTotals?.showColumnGrandTotals ?? false,
mergeCells: current.pivotStyle?.mergeCells ?? false,
alternateRowColors: current.pivotStyle?.alternateRowColors ?? false,
exportExcel: current.pivotExportConfig?.excel ?? false,
exportPdf: current.pivotExportConfig?.pdf ?? false,
}}
onToggle={(key, value) => {
switch (key) {
case "chartEnabled":
patch({
pivotChart: {
type: current.pivotChart?.type ?? "bar",
position: current.pivotChart?.position ?? "bottom",
...current.pivotChart,
enabled: value,
},
});
return;
case "fieldChooserEnabled":
patch({
pivotFieldChooser: { ...current.pivotFieldChooser, enabled: value },
});
return;
case "rowGrandTotals":
patch({
pivotTotals: { ...current.pivotTotals, showRowGrandTotals: value },
});
return;
case "columnGrandTotals":
patch({
pivotTotals: { ...current.pivotTotals, showColumnGrandTotals: value },
});
return;
case "mergeCells":
case "alternateRowColors": {
const baseStyle = {
theme: current.pivotStyle?.theme ?? "default" as const,
headerStyle: current.pivotStyle?.headerStyle ?? "default" as const,
cellPadding: current.pivotStyle?.cellPadding ?? "normal" as const,
borderStyle: current.pivotStyle?.borderStyle ?? "light" as const,
...current.pivotStyle,
mergeCells: v,
},
})
};
patch({
pivotStyle: { ...baseStyle, [key]: value },
});
return;
}
case "exportExcel":
patch({
pivotExportConfig: { ...current.pivotExportConfig, excel: value },
});
return;
case "exportPdf":
patch({
pivotExportConfig: { ...current.pivotExportConfig, pdf: value },
});
return;
}
/>
</CPRow>
<CPRow label="행 교대 색">
<CPSwitch
value={current.pivotStyle?.alternateRowColors ?? false}
onChange={(v) =>
patch({
pivotStyle: {
theme: current.pivotStyle?.theme ?? "default",
headerStyle: current.pivotStyle?.headerStyle ?? "default",
cellPadding: current.pivotStyle?.cellPadding ?? "normal",
borderStyle: current.pivotStyle?.borderStyle ?? "light",
...current.pivotStyle,
alternateRowColors: v,
},
})
}
/>
</CPRow>
<CPRow label="엑셀 내보내기">
<CPSwitch
value={current.pivotExportConfig?.excel ?? false}
onChange={(v) =>
patch({
pivotExportConfig: {
...current.pivotExportConfig,
excel: v,
},
})
}
/>
</CPRow>
<CPRow label="PDF 내보내기">
<CPSwitch
value={current.pivotExportConfig?.pdf ?? false}
onChange={(v) =>
patch({
pivotExportConfig: {
...current.pivotExportConfig,
pdf: v,
},
})
}
/>
</CPRow>
}}
/>
</CPSection>
)}
@@ -1045,10 +1045,10 @@ export const PivotView: React.FC<PivotGridProps> = ({
);
}
// 필드 미설정 (행, 열, 데이터 영역에 필드가 있는지 확인)
const hasActiveFields = fields.some(
(f) => f.visible !== false && ["row", "column", "data"].includes(f.area)
);
// 필드 미설정 — data 영역 (집계 대상) 이 1개 이상 있어야 의미있는 피벗.
// row/column 만 있고 data 가 비어있으면 빈 0 그리드가 생성되므로 안내로 fallback.
const hasDataField = fields.some((f) => f.visible !== false && f.area === "data");
const hasActiveFields = hasDataField;
if (!hasActiveFields) {
return (
<div