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