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

207 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}