170 lines
5.5 KiB
TypeScript
170 lines
5.5 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useCallback } from 'react';
|
||
import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from './types';
|
||
import { QueryEditor } from './QueryEditor';
|
||
import { ChartConfigPanel } from './ChartConfigPanel';
|
||
|
||
interface ElementConfigModalProps {
|
||
element: DashboardElement;
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onSave: (element: DashboardElement) => void;
|
||
}
|
||
|
||
/**
|
||
* 요소 설정 모달 컴포넌트
|
||
* - 차트/위젯 데이터 소스 설정
|
||
* - 쿼리 에디터 통합
|
||
* - 차트 설정 패널 통합
|
||
*/
|
||
export function ElementConfigModal({ element, isOpen, onClose, onSave }: ElementConfigModalProps) {
|
||
const [dataSource, setDataSource] = useState<ChartDataSource>(
|
||
element.dataSource || { type: 'database', refreshInterval: 30000 }
|
||
);
|
||
const [chartConfig, setChartConfig] = useState<ChartConfig>(
|
||
element.chartConfig || {}
|
||
);
|
||
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
|
||
const [activeTab, setActiveTab] = useState<'query' | 'chart'>('query');
|
||
|
||
// 데이터 소스 변경 처리
|
||
const handleDataSourceChange = useCallback((newDataSource: ChartDataSource) => {
|
||
setDataSource(newDataSource);
|
||
}, []);
|
||
|
||
// 차트 설정 변경 처리
|
||
const handleChartConfigChange = useCallback((newConfig: ChartConfig) => {
|
||
setChartConfig(newConfig);
|
||
}, []);
|
||
|
||
// 쿼리 테스트 결과 처리
|
||
const handleQueryTest = useCallback((result: QueryResult) => {
|
||
setQueryResult(result);
|
||
// 쿼리 결과가 나오면 자동으로 차트 설정 탭으로 이동
|
||
if (result.rows.length > 0) {
|
||
setActiveTab('chart');
|
||
}
|
||
}, []);
|
||
|
||
// 저장 처리
|
||
const handleSave = useCallback(() => {
|
||
const updatedElement: DashboardElement = {
|
||
...element,
|
||
dataSource,
|
||
chartConfig,
|
||
};
|
||
onSave(updatedElement);
|
||
onClose();
|
||
}, [element, dataSource, chartConfig, onSave, onClose]);
|
||
|
||
// 모달이 열려있지 않으면 렌더링하지 않음
|
||
if (!isOpen) return null;
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-white rounded-lg shadow-xl w-full max-w-4xl h-[80vh] flex flex-col">
|
||
{/* 모달 헤더 */}
|
||
<div className="flex justify-between items-center p-6 border-b border-gray-200">
|
||
<div>
|
||
<h2 className="text-xl font-semibold text-gray-800">
|
||
{element.title} 설정
|
||
</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
데이터 소스와 차트 설정을 구성하세요
|
||
</p>
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
className="text-gray-400 hover:text-muted-foreground text-2xl"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
{/* 탭 네비게이션 */}
|
||
<div className="flex border-b border-gray-200">
|
||
<button
|
||
onClick={() => setActiveTab('query')}
|
||
className={`
|
||
px-6 py-3 text-sm font-medium border-b-2 transition-colors
|
||
${activeTab === 'query'
|
||
? 'border-primary text-primary bg-accent'
|
||
: 'border-transparent text-gray-500 hover:text-gray-700'}
|
||
`}
|
||
>
|
||
📝 쿼리 & 데이터
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('chart')}
|
||
className={`
|
||
px-6 py-3 text-sm font-medium border-b-2 transition-colors
|
||
${activeTab === 'chart'
|
||
? 'border-primary text-primary bg-accent'
|
||
: 'border-transparent text-gray-500 hover:text-gray-700'}
|
||
`}
|
||
>
|
||
📊 차트 설정
|
||
{queryResult && (
|
||
<span className="ml-2 px-2 py-0.5 bg-green-100 text-green-800 text-xs rounded-full">
|
||
{queryResult.rows.length}
|
||
</span>
|
||
)}
|
||
</button>
|
||
</div>
|
||
|
||
{/* 탭 내용 */}
|
||
<div className="flex-1 overflow-auto p-6">
|
||
{activeTab === 'query' && (
|
||
<QueryEditor
|
||
dataSource={dataSource}
|
||
onDataSourceChange={handleDataSourceChange}
|
||
onQueryTest={handleQueryTest}
|
||
/>
|
||
)}
|
||
|
||
{activeTab === 'chart' && (
|
||
<ChartConfigPanel
|
||
config={chartConfig}
|
||
queryResult={queryResult}
|
||
onConfigChange={handleChartConfigChange}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* 모달 푸터 */}
|
||
<div className="flex justify-between items-center p-6 border-t border-gray-200">
|
||
<div className="text-sm text-gray-500">
|
||
{dataSource.query && (
|
||
<>
|
||
💾 쿼리: {dataSource.query.length > 50
|
||
? `${dataSource.query.substring(0, 50)}...`
|
||
: dataSource.query}
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex gap-3">
|
||
<button
|
||
onClick={onClose}
|
||
className="px-4 py-2 text-muted-foreground border border-gray-300 rounded-lg hover:bg-gray-50"
|
||
>
|
||
취소
|
||
</button>
|
||
<button
|
||
onClick={handleSave}
|
||
disabled={!dataSource.query || (!chartConfig.xAxis || !chartConfig.yAxis)}
|
||
className="
|
||
px-4 py-2 bg-accent0 text-white rounded-lg
|
||
hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed
|
||
"
|
||
>
|
||
저장
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|