137 lines
5.2 KiB
TypeScript
137 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* FooterAggregateModal — 테이블 푸터 집계 설정 모달
|
|
*
|
|
* design-system.md Shell 패턴 적용.
|
|
* 특정 열을 클릭하면 열리며, 해당 열의 집계 유형(합계/평균/개수/수식)을 설정.
|
|
*/
|
|
|
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
import { Dialog, DialogContent, DialogTitle, DialogDescription } from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useUnsavedChangesGuard, UnsavedChangesDialog } from "@/components/common/UnsavedChangesGuard";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Calculator, X } from "lucide-react";
|
|
import type { ComponentConfig } from "@/types/report";
|
|
|
|
type TableColumn = NonNullable<ComponentConfig["tableColumns"]>[number];
|
|
|
|
interface FooterAggregateModalProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
column: TableColumn | null;
|
|
columnIndex: number;
|
|
onSave: (idx: number, updates: Partial<TableColumn>) => void;
|
|
}
|
|
|
|
export function FooterAggregateModal({ open, onOpenChange, column, columnIndex, onSave }: FooterAggregateModalProps) {
|
|
const [summaryType, setSummaryType] = useState<"SUM" | "AVG" | "COUNT" | "NONE">("NONE");
|
|
const [formula, setFormula] = useState("");
|
|
const initialSnapshotRef = useRef<string>("");
|
|
|
|
const hasChanges = useCallback(() => {
|
|
const current = JSON.stringify({ summaryType, formula });
|
|
return current !== initialSnapshotRef.current;
|
|
}, [summaryType, formula]);
|
|
|
|
const guard = useUnsavedChangesGuard({
|
|
hasChanges,
|
|
onClose: () => onOpenChange(false),
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (column) {
|
|
const initType = column.summaryType || "NONE";
|
|
const initFormula = column.formula || "";
|
|
setSummaryType(initType);
|
|
setFormula(initFormula);
|
|
initialSnapshotRef.current = JSON.stringify({ summaryType: initType, formula: initFormula });
|
|
}
|
|
}, [column]);
|
|
|
|
const handleSave = () => {
|
|
onSave(columnIndex, {
|
|
summaryType,
|
|
formula: summaryType === "NONE" ? undefined : formula || undefined,
|
|
});
|
|
onOpenChange(false);
|
|
};
|
|
|
|
if (!column) return null;
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onOpenChange={guard.handleOpenChange}>
|
|
<DialogContent className="flex h-auto max-w-lg flex-col overflow-hidden p-0 [&>button]:hidden">
|
|
<DialogTitle className="sr-only">계산 방식 설정</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
{column.field || column.header} 열의 계산 방식을 설정합니다
|
|
</DialogDescription>
|
|
|
|
{/* Header */}
|
|
<div className="border-border flex items-center justify-between border-b px-6 py-4">
|
|
<div className="flex items-center gap-2">
|
|
<Calculator className="h-4 w-4 text-blue-600" />
|
|
<h2 className="text-foreground text-base font-semibold">계산 방식 설정</h2>
|
|
</div>
|
|
<Button variant="ghost" size="icon" onClick={guard.tryClose} className="h-8 w-8">
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="space-y-3 px-6 py-4">
|
|
<p className="text-muted-foreground text-xs">
|
|
<span className="font-mono font-medium text-blue-700">{column.field || column.header}</span> 열의 계산
|
|
방식을 설정합니다.
|
|
</p>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-foreground text-xs font-medium">계산 방식 *</Label>
|
|
<Select value={summaryType} onValueChange={(v) => setSummaryType(v as typeof summaryType)}>
|
|
<SelectTrigger className="h-9 text-sm">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="NONE">없음</SelectItem>
|
|
<SelectItem value="SUM">합계</SelectItem>
|
|
<SelectItem value="AVG">평균</SelectItem>
|
|
<SelectItem value="COUNT">개수</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{summaryType !== "NONE" && (
|
|
<div className="space-y-2">
|
|
<Label className="text-foreground text-xs font-medium">계산식 (선택)</Label>
|
|
<Input
|
|
value={formula}
|
|
onChange={(e) => setFormula(e.target.value)}
|
|
placeholder="예: {price} * {qty}"
|
|
className="h-9 font-mono text-sm"
|
|
/>
|
|
<p className="text-muted-foreground text-[10px]">비워두면 기본 계산이 적용됩니다.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="border-border flex items-center justify-end gap-2 border-t px-6 py-4">
|
|
<Button variant="outline" onClick={guard.tryClose}>
|
|
취소
|
|
</Button>
|
|
<Button className="bg-blue-600 hover:bg-blue-700" onClick={handleSave}>
|
|
확인
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<UnsavedChangesDialog guard={guard} />
|
|
</>
|
|
);
|
|
}
|