차트 구현 1차 완료(바 차트 작동)

This commit is contained in:
dohyeons
2025-10-14 15:25:11 +09:00
parent 3db7feb36b
commit 4cc5f1344f
21 changed files with 1783 additions and 667 deletions

View File

@@ -22,21 +22,21 @@ interface ElementConfigModalProps {
/**
* 요소 설정 모달 컴포넌트 (리팩토링)
* - 3단계 플로우: 데이터 소스 선택 → 데이터 설정 차트 설정
* - 2단계 플로우: 데이터 소스 선택 → 데이터 설정 차트 설정
* - 새로운 데이터 소스 컴포넌트 통합
*/
export function ElementConfigModal({ element, isOpen, onClose, onSave }: ElementConfigModalProps) {
const [dataSource, setDataSource] = useState<ChartDataSource>(
element.dataSource || { type: "database", connectionType: "current", refreshInterval: 30 },
element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 },
);
const [chartConfig, setChartConfig] = useState<ChartConfig>(element.chartConfig || {});
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1);
const [currentStep, setCurrentStep] = useState<1 | 2>(1);
// 모달이 열릴 때 초기화
useEffect(() => {
if (isOpen) {
setDataSource(element.dataSource || { type: "database", connectionType: "current", refreshInterval: 30 });
setDataSource(element.dataSource || { type: "database", connectionType: "current", refreshInterval: 0 });
setChartConfig(element.chartConfig || {});
setQueryResult(null);
setCurrentStep(1);
@@ -49,13 +49,13 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
setDataSource({
type: "database",
connectionType: "current",
refreshInterval: 30,
refreshInterval: 0,
});
} else {
setDataSource({
type: "api",
method: "GET",
refreshInterval: 30,
refreshInterval: 0,
});
}
}, []);
@@ -73,45 +73,19 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
// 쿼리 테스트 결과 처리
const handleQueryTest = useCallback((result: QueryResult) => {
setQueryResult(result);
// 쿼리 결과가 나오면 자동으로 3단계로 이동
if (result.rows.length > 0) {
setCurrentStep(3);
}
}, []);
// 다음 단계로 이동 (검증 포함)
// 다음 단계로 이동
const handleNext = useCallback(() => {
if (currentStep === 1) {
// 1단계: 데이터 소스 타입 선택 완료
setCurrentStep(2);
} else if (currentStep === 2) {
// 2단계: 데이터 설정 완료 - 검증
const validation = validateDataSource(
dataSource.type,
dataSource.connectionType,
dataSource.externalConnectionId,
dataSource.query,
dataSource.endpoint,
);
if (!validation.valid) {
alert(validation.message);
return;
}
// 쿼리 결과가 있으면 3단계로 이동
if (queryResult && queryResult.rows.length > 0) {
setCurrentStep(3);
} else {
alert("먼저 데이터를 테스트하여 결과를 확인하세요");
}
}
}, [currentStep, dataSource, queryResult]);
}, [currentStep]);
// 이전 단계로 이동
const handlePrev = useCallback(() => {
if (currentStep > 1) {
setCurrentStep((prev) => (prev - 1) as 1 | 2 | 3);
setCurrentStep((prev) => (prev - 1) as 1 | 2);
}
}, [currentStep]);
@@ -139,6 +113,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
// 저장 가능 여부 확인
const canSave =
currentStep === 2 &&
queryResult &&
queryResult.rows.length > 0 &&
chartConfig.xAxis &&
@@ -146,12 +121,18 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="flex h-[85vh] w-full max-w-5xl flex-col rounded-xl border bg-white shadow-2xl">
<div
className={`flex flex-col rounded-xl border bg-white shadow-2xl ${
currentStep === 1 ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl"
}`}
>
{/* 모달 헤더 */}
<div className="flex items-center justify-between border-b p-6">
<div>
<h2 className="text-xl font-semibold text-gray-900">{element.title} </h2>
<p className="mt-1 text-sm text-gray-500">3 </p>
<p className="mt-1 text-sm text-gray-500">
{currentStep === 1 ? "데이터 소스를 선택하세요" : "쿼리를 실행하고 차트를 설정하세요"}
</p>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
<X className="h-5 w-5" />
@@ -162,13 +143,11 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
<div className="border-b bg-gray-50 px-6 py-4">
<div className="mb-4 flex items-center justify-between">
<div className="text-sm font-medium text-gray-700">
{currentStep} / 3: {currentStep === 1 && "데이터 소스 선택"}
{currentStep === 2 && "데이터 설정"}
{currentStep === 3 && "차트 설정"}
{currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
</div>
<Badge variant="secondary">{Math.round((currentStep / 3) * 100)}% </Badge>
<Badge variant="secondary">{Math.round((currentStep / 2) * 100)}% </Badge>
</div>
<Progress value={(currentStep / 3) * 100} className="h-2" />
<Progress value={(currentStep / 2) * 100} className="h-2" />
</div>
{/* 단계별 내용 */}
@@ -178,24 +157,43 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
)}
{currentStep === 2 && (
<div className="space-y-6">
{dataSource.type === "database" ? (
<>
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
<QueryEditor
dataSource={dataSource}
onDataSourceChange={handleDataSourceUpdate}
onQueryTest={handleQueryTest}
/>
</>
) : (
<ApiConfig dataSource={dataSource} onChange={handleDataSourceUpdate} onTestResult={handleQueryTest} />
)}
</div>
)}
<div className="grid grid-cols-2 gap-6">
{/* 왼쪽: 데이터 설정 */}
<div className="space-y-6">
{dataSource.type === "database" ? (
<>
<DatabaseConfig dataSource={dataSource} onChange={handleDataSourceUpdate} />
<QueryEditor
dataSource={dataSource}
onDataSourceChange={handleDataSourceUpdate}
onQueryTest={handleQueryTest}
/>
</>
) : (
<ApiConfig dataSource={dataSource} onChange={handleDataSourceUpdate} onTestResult={handleQueryTest} />
)}
</div>
{currentStep === 3 && (
<ChartConfigPanel config={chartConfig} queryResult={queryResult} onConfigChange={handleChartConfigChange} />
{/* 오른쪽: 차트 설정 */}
<div>
{queryResult && queryResult.rows.length > 0 ? (
<ChartConfigPanel
config={chartConfig}
queryResult={queryResult}
onConfigChange={handleChartConfigChange}
chartType={element.subtype}
/>
) : (
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
<div>
<div className="mb-2 text-4xl">📊</div>
<div className="text-sm font-medium text-gray-700"> </div>
<div className="mt-1 text-xs text-gray-500"> </div>
</div>
</div>
)}
</div>
</div>
)}
</div>
@@ -219,7 +217,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
<Button variant="outline" onClick={onClose}>
</Button>
{currentStep < 3 ? (
{currentStep === 1 ? (
<Button onClick={handleNext}>
<ChevronRight className="ml-2 h-4 w-4" />