피벗 테스트만 하면 됨 기능은 완료
This commit is contained in:
@@ -547,6 +547,62 @@ export const PivotGridConfigPanel: React.FC<PivotGridConfigPanelProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">행 총계 위치</Label>
|
||||
<Select
|
||||
value={config.totals?.rowGrandTotalPosition || "bottom"}
|
||||
onValueChange={(v) =>
|
||||
updateConfig({ totals: { ...config.totals, rowGrandTotalPosition: v as "top" | "bottom" } })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-6 w-16 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="top">상단</SelectItem>
|
||||
<SelectItem value="bottom">하단</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">열 총계 위치</Label>
|
||||
<Select
|
||||
value={config.totals?.columnGrandTotalPosition || "right"}
|
||||
onValueChange={(v) =>
|
||||
updateConfig({ totals: { ...config.totals, columnGrandTotalPosition: v as "left" | "right" } })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-6 w-16 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="left">좌측</SelectItem>
|
||||
<SelectItem value="right">우측</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">행 소계</Label>
|
||||
<Switch
|
||||
checked={config.totals?.showRowTotals !== false}
|
||||
onCheckedChange={(v) =>
|
||||
updateConfig({ totals: { ...config.totals, showRowTotals: v } })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">열 소계</Label>
|
||||
<Switch
|
||||
checked={config.totals?.showColumnTotals !== false}
|
||||
onCheckedChange={(v) =>
|
||||
updateConfig({ totals: { ...config.totals, showColumnTotals: v } })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">줄무늬</Label>
|
||||
<Switch
|
||||
@@ -557,6 +613,16 @@ export const PivotGridConfigPanel: React.FC<PivotGridConfigPanelProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">셀 병합</Label>
|
||||
<Switch
|
||||
checked={config.style?.mergeCells === true}
|
||||
onCheckedChange={(v) =>
|
||||
updateConfig({ style: { ...config.style, mergeCells: v } })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">CSV 내보내기</Label>
|
||||
<Switch
|
||||
@@ -566,6 +632,16 @@ export const PivotGridConfigPanel: React.FC<PivotGridConfigPanelProps> = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-2 rounded-md bg-muted/30">
|
||||
<Label className="text-xs">상태 저장</Label>
|
||||
<Switch
|
||||
checked={config.saveState === true}
|
||||
onCheckedChange={(v) =>
|
||||
updateConfig({ saveState: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -593,6 +669,126 @@ export const PivotGridConfigPanel: React.FC<PivotGridConfigPanelProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 조건부 서식 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium text-muted-foreground">조건부 서식</Label>
|
||||
<div className="space-y-2">
|
||||
{(config.style?.conditionalFormats || []).map((rule, index) => (
|
||||
<div key={rule.id} className="flex items-center gap-2 p-2 rounded-md bg-muted/30">
|
||||
<Select
|
||||
value={rule.type}
|
||||
onValueChange={(v) => {
|
||||
const newFormats = [...(config.style?.conditionalFormats || [])];
|
||||
newFormats[index] = { ...rule, type: v as any };
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 w-24 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="colorScale">색상 스케일</SelectItem>
|
||||
<SelectItem value="dataBar">데이터 바</SelectItem>
|
||||
<SelectItem value="iconSet">아이콘 세트</SelectItem>
|
||||
<SelectItem value="cellValue">셀 값 조건</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{rule.type === "colorScale" && (
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="color"
|
||||
value={rule.colorScale?.minColor || "#ff0000"}
|
||||
onChange={(e) => {
|
||||
const newFormats = [...(config.style?.conditionalFormats || [])];
|
||||
newFormats[index] = { ...rule, colorScale: { ...rule.colorScale, minColor: e.target.value, maxColor: rule.colorScale?.maxColor || "#00ff00" } };
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
className="w-6 h-6 rounded cursor-pointer"
|
||||
title="최소값 색상"
|
||||
/>
|
||||
<span className="text-xs">→</span>
|
||||
<input
|
||||
type="color"
|
||||
value={rule.colorScale?.maxColor || "#00ff00"}
|
||||
onChange={(e) => {
|
||||
const newFormats = [...(config.style?.conditionalFormats || [])];
|
||||
newFormats[index] = { ...rule, colorScale: { ...rule.colorScale, minColor: rule.colorScale?.minColor || "#ff0000", maxColor: e.target.value } };
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
className="w-6 h-6 rounded cursor-pointer"
|
||||
title="최대값 색상"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{rule.type === "dataBar" && (
|
||||
<input
|
||||
type="color"
|
||||
value={rule.dataBar?.color || "#3b82f6"}
|
||||
onChange={(e) => {
|
||||
const newFormats = [...(config.style?.conditionalFormats || [])];
|
||||
newFormats[index] = { ...rule, dataBar: { color: e.target.value } };
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
className="w-6 h-6 rounded cursor-pointer"
|
||||
title="바 색상"
|
||||
/>
|
||||
)}
|
||||
|
||||
{rule.type === "iconSet" && (
|
||||
<Select
|
||||
value={rule.iconSet?.type || "traffic"}
|
||||
onValueChange={(v) => {
|
||||
const newFormats = [...(config.style?.conditionalFormats || [])];
|
||||
newFormats[index] = { ...rule, iconSet: { type: v as any, thresholds: [33, 67] } };
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 w-20 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="arrows">화살표</SelectItem>
|
||||
<SelectItem value="traffic">신호등</SelectItem>
|
||||
<SelectItem value="rating">별점</SelectItem>
|
||||
<SelectItem value="flags">깃발</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 ml-auto"
|
||||
onClick={() => {
|
||||
const newFormats = (config.style?.conditionalFormats || []).filter((_, i) => i !== index);
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
const newFormats = [
|
||||
...(config.style?.conditionalFormats || []),
|
||||
{ id: `cf_${Date.now()}`, type: "colorScale" as const, colorScale: { minColor: "#ff0000", maxColor: "#00ff00" } }
|
||||
];
|
||||
updateConfig({ style: { ...config.style, conditionalFormats: newFormats } });
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
조건부 서식 추가
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user