ERP-node/frontend/components/admin/dashboard/ChartConfigPanel.tsx

207 lines
7.9 KiB
TypeScript
Raw Normal View History

'use client';
import React, { useState, useCallback } from 'react';
import { ChartConfig, QueryResult } from './types';
interface ChartConfigPanelProps {
config?: ChartConfig;
queryResult?: QueryResult;
onConfigChange: (config: ChartConfig) => void;
}
/**
*
* -
* -
* -
*/
export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartConfigPanelProps) {
const [currentConfig, setCurrentConfig] = useState<ChartConfig>(config || {});
// 설정 업데이트
const updateConfig = useCallback((updates: Partial<ChartConfig>) => {
const newConfig = { ...currentConfig, ...updates };
setCurrentConfig(newConfig);
onConfigChange(newConfig);
}, [currentConfig, onConfigChange]);
// 사용 가능한 컬럼 목록
const availableColumns = queryResult?.columns || [];
const sampleData = queryResult?.rows?.[0] || {};
return (
<div className="space-y-4">
<h4 className="text-lg font-semibold text-gray-800"> </h4>
{/* 쿼리 결과가 없을 때 */}
{!queryResult && (
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<div className="text-yellow-800 text-sm">
💡 SQL .
</div>
</div>
)}
{/* 데이터 필드 매핑 */}
{queryResult && (
<>
{/* 차트 제목 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700"> </label>
<input
type="text"
value={currentConfig.title || ''}
onChange={(e) => updateConfig({ title: e.target.value })}
placeholder="차트 제목을 입력하세요"
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
/>
</div>
{/* X축 설정 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
X축 ()
<span className="text-red-500 ml-1">*</span>
</label>
<select
value={currentConfig.xAxis || ''}
onChange={(e) => updateConfig({ xAxis: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value=""></option>
{availableColumns.map((col) => (
<option key={col} value={col}>
{col} {sampleData[col] && `(예: ${sampleData[col]})`}
</option>
))}
</select>
</div>
{/* Y축 설정 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Y축 ()
<span className="text-red-500 ml-1">*</span>
</label>
<select
value={currentConfig.yAxis || ''}
onChange={(e) => updateConfig({ yAxis: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value=""></option>
{availableColumns.map((col) => (
<option key={col} value={col}>
{col} {sampleData[col] && `(예: ${sampleData[col]})`}
</option>
))}
</select>
</div>
{/* 집계 함수 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700"> </label>
<select
value={currentConfig.aggregation || 'sum'}
onChange={(e) => updateConfig({ aggregation: e.target.value as any })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value="sum"> (SUM)</option>
<option value="avg"> (AVG)</option>
<option value="count"> (COUNT)</option>
<option value="max"> (MAX)</option>
<option value="min"> (MIN)</option>
</select>
</div>
{/* 그룹핑 필드 (선택사항) */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
()
</label>
<select
value={currentConfig.groupBy || ''}
onChange={(e) => updateConfig({ groupBy: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value=""></option>
{availableColumns.map((col) => (
<option key={col} value={col}>
{col}
</option>
))}
</select>
</div>
{/* 차트 색상 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700"> </label>
<div className="grid grid-cols-4 gap-2">
{[
['#3B82F6', '#EF4444', '#10B981', '#F59E0B'], // 기본
['#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'], // 밝은
['#1F2937', '#374151', '#6B7280', '#9CA3AF'], // 회색
['#DC2626', '#EA580C', '#CA8A04', '#65A30D'], // 따뜻한
].map((colorSet, setIdx) => (
<button
key={setIdx}
onClick={() => updateConfig({ colors: colorSet })}
className={`
h-8 rounded border-2 flex
${JSON.stringify(currentConfig.colors) === JSON.stringify(colorSet)
? 'border-gray-800' : 'border-gray-300'}
`}
>
{colorSet.map((color, idx) => (
<div
key={idx}
className="flex-1 first:rounded-l last:rounded-r"
style={{ backgroundColor: color }}
/>
))}
</button>
))}
</div>
</div>
{/* 범례 표시 */}
<div className="flex items-center gap-2">
<input
type="checkbox"
id="showLegend"
checked={currentConfig.showLegend !== false}
onChange={(e) => updateConfig({ showLegend: e.target.checked })}
className="rounded"
/>
<label htmlFor="showLegend" className="text-sm text-gray-700">
</label>
</div>
{/* 설정 미리보기 */}
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-sm font-medium text-gray-700 mb-2">📋 </div>
<div className="text-xs text-gray-600 space-y-1">
<div><strong>X축:</strong> {currentConfig.xAxis || '미설정'}</div>
<div><strong>Y축:</strong> {currentConfig.yAxis || '미설정'}</div>
<div><strong>:</strong> {currentConfig.aggregation || 'sum'}</div>
{currentConfig.groupBy && (
<div><strong>:</strong> {currentConfig.groupBy}</div>
)}
<div><strong> :</strong> {queryResult.rows.length}</div>
</div>
</div>
{/* 필수 필드 확인 */}
{(!currentConfig.xAxis || !currentConfig.yAxis) && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
<div className="text-red-800 text-sm">
X축과 Y축을 .
</div>
</div>
)}
</>
)}
</div>
);
}