ERP-node/frontend/components/admin/dashboard/widget-sections/CustomMetricSection.tsx

261 lines
9.9 KiB
TypeScript

"use client";
import React from "react";
import { CustomMetricConfig, QueryResult } from "../types";
import { Label } from "@/components/ui/label";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertCircle, Plus, X } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
interface CustomMetricSectionProps {
queryResult: QueryResult | null;
config: CustomMetricConfig;
onConfigChange: (updates: Partial<CustomMetricConfig>) => void;
}
/**
* 통계 카드 설정 섹션
* - 쿼리 결과를 받아서 어떻게 통계를 낼지 설정
* - 컬럼 선택, 계산 방식(합계/평균/개수 등), 표시 방식
* - 필터 조건 추가 가능
*/
export function CustomMetricSection({ queryResult, config, onConfigChange }: CustomMetricSectionProps) {
console.log("⚙️ [CustomMetricSection] 렌더링:", { config, queryResult });
// 초기값 설정 (aggregation이 없으면 기본값 "sum" 설정)
React.useEffect(() => {
if (queryResult && queryResult.columns && queryResult.columns.length > 0 && !config.aggregation) {
console.log("🔧 기본 aggregation 설정: sum");
onConfigChange({ aggregation: "sum" });
}
}, [queryResult, config.aggregation, onConfigChange]);
// 쿼리 결과가 없으면 안내 메시지
if (!queryResult || !queryResult.columns || queryResult.columns.length === 0) {
return (
<div className="bg-background rounded-lg p-3 shadow-sm">
<Label className="mb-2 block text-xs font-semibold"> </Label>
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
.
</AlertDescription>
</Alert>
</div>
);
}
// 필터 추가
const addFilter = () => {
const newFilters = [
...(config.filters || []),
{ column: queryResult.columns[0] || "", operator: "=" as const, value: "" },
];
onConfigChange({ filters: newFilters });
};
// 필터 제거
const removeFilter = (index: number) => {
const newFilters = [...(config.filters || [])];
newFilters.splice(index, 1);
onConfigChange({ filters: newFilters });
};
// 필터 업데이트
const updateFilter = (index: number, field: string, value: string) => {
const newFilters = [...(config.filters || [])];
newFilters[index] = { ...newFilters[index], [field]: value };
onConfigChange({ filters: newFilters });
};
// 통계 설정
return (
<div className="bg-background space-y-4 rounded-lg p-3 shadow-sm">
<div>
<Label className="mb-2 block text-xs font-semibold"> </Label>
<p className="text-muted-foreground text-xs"> </p>
</div>
{/* 1. 필터 조건 (선택사항) */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium"> ()</Label>
<Button onClick={addFilter} variant="outline" size="sm" className="h-7 gap-1 text-xs">
<Plus className="h-3 w-3" />
</Button>
</div>
{config.filters && config.filters.length > 0 ? (
<div className="space-y-2">
{config.filters.map((filter, index) => (
<div key={index} className="bg-muted/50 flex items-center gap-2 rounded-md border p-2">
{/* 컬럼 선택 */}
<Select value={filter.column} onValueChange={(value) => updateFilter(index, "column", value)}>
<SelectTrigger className="h-8 flex-1 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{queryResult.columns.map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
</SelectItem>
))}
</SelectContent>
</Select>
{/* 연산자 선택 */}
<Select value={filter.operator} onValueChange={(value) => updateFilter(index, "operator", value)}>
<SelectTrigger className="h-8 w-[100px] text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="=" className="text-xs">
(=)
</SelectItem>
<SelectItem value="!=" className="text-xs">
()
</SelectItem>
<SelectItem value=">" className="text-xs">
(&gt;)
</SelectItem>
<SelectItem value="<" className="text-xs">
(&lt;)
</SelectItem>
<SelectItem value=">=" className="text-xs">
()
</SelectItem>
<SelectItem value="<=" className="text-xs">
()
</SelectItem>
<SelectItem value="contains" className="text-xs">
</SelectItem>
<SelectItem value="not_contains" className="text-xs">
</SelectItem>
</SelectContent>
</Select>
{/* 값 입력 */}
<Input
value={filter.value}
onChange={(e) => updateFilter(index, "value", e.target.value)}
placeholder="값"
className="h-8 flex-1 text-xs"
/>
{/* 삭제 버튼 */}
<Button onClick={() => removeFilter(index)} variant="ghost" size="icon" className="h-8 w-8">
<X className="h-3 w-3" />
</Button>
</div>
))}
</div>
) : (
<p className="text-muted-foreground text-xs"> ( )</p>
)}
</div>
{/* 2. 계산할 컬럼 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select value={config.valueColumn || ""} onValueChange={(value) => onConfigChange({ valueColumn: value })}>
<SelectTrigger className="h-9 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{queryResult.columns.map((col) => (
<SelectItem key={col} value={col} className="text-xs">
{col}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 3. 계산 방식 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select
value={config.aggregation || "sum"}
onValueChange={(value) => {
console.log("📐 계산 방식 변경:", value);
onConfigChange({ aggregation: value as "sum" | "avg" | "count" | "min" | "max" });
}}
>
<SelectTrigger className="h-9 text-xs">
<SelectValue placeholder="계산 방식" />
</SelectTrigger>
<SelectContent>
<SelectItem value="sum" className="text-xs">
(SUM)
</SelectItem>
<SelectItem value="avg" className="text-xs">
(AVG)
</SelectItem>
<SelectItem value="count" className="text-xs">
(COUNT)
</SelectItem>
<SelectItem value="min" className="text-xs">
(MIN)
</SelectItem>
<SelectItem value="max" className="text-xs">
(MAX)
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 4. 카드 제목 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Input
value={config.title || ""}
onChange={(e) => onConfigChange({ title: e.target.value })}
placeholder="예: 총 매출액"
className="h-9 text-xs"
/>
</div>
{/* 5. 표시 단위 (선택사항) */}
<div className="space-y-2">
<Label className="text-xs font-medium"> ()</Label>
<Input
value={config.unit || ""}
onChange={(e) => onConfigChange({ unit: e.target.value })}
placeholder="예: 원, 건, %"
className="h-9 text-xs"
/>
</div>
{/* 미리보기 */}
{config.valueColumn && config.aggregation && (
<div className="bg-muted/50 space-y-1 rounded-md border p-3">
<p className="text-muted-foreground text-xs font-semibold"> </p>
{/* 필터 조건 표시 */}
{config.filters && config.filters.length > 0 && (
<div className="space-y-1">
<p className="text-xs font-medium">:</p>
{config.filters.map((filter, idx) => (
<p key={idx} className="text-muted-foreground text-xs">
· {filter.column} {filter.operator} &quot;{filter.value}&quot;
</p>
))}
</div>
)}
{/* 계산 표시 */}
<p className="text-xs font-medium">
{config.title || "통계 제목"}: {config.aggregation?.toUpperCase()}({config.valueColumn})
{config.unit ? ` ${config.unit}` : ""}
</p>
</div>
)}
</div>
);
}