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

188 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. 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 { DashboardElement, ChartDataSource, ChartConfig, QueryResult, ClockConfig } from "./types";
import { QueryEditor } from "./QueryEditor";
import { ChartConfigPanel } from "./ChartConfigPanel";
import { ClockConfigModal } from "./widgets/ClockConfigModal";
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]);
// 시계 위젯 설정 저장
const handleClockConfigSave = useCallback(
(clockConfig: ClockConfig) => {
const updatedElement: DashboardElement = {
...element,
clockConfig,
};
onSave(updatedElement);
},
[element, onSave],
);
// 모달이 열려있지 않으면 렌더링하지 않음
if (!isOpen) return null;
// 시계 위젯은 자체 설정 UI를 가지고 있으므로 모달 표시하지 않음
if (element.type === "widget" && element.subtype === "clock") {
return null;
}
// 이전 코드 호환성 유지 (아래 주석 처리된 코드는 제거 예정)
if (false && element.type === "widget" && element.subtype === "clock") {
return (
<ClockConfigModal
config={
element.clockConfig || {
style: "digital",
timezone: "Asia/Seoul",
showDate: true,
showSeconds: true,
format24h: true,
theme: "light",
}
}
onSave={handleClockConfigSave}
onClose={onClose}
/>
);
}
return (
<div className="bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black">
<div className="flex h-[80vh] w-full max-w-4xl flex-col rounded-lg bg-white shadow-xl">
{/* 모달 헤더 */}
<div className="flex items-center justify-between border-b border-gray-200 p-6">
<div>
<h2 className="text-xl font-semibold text-gray-800">{element.title} </h2>
<p className="text-muted-foreground mt-1 text-sm"> </p>
</div>
<button onClick={onClose} className="hover:text-muted-foreground text-2xl text-gray-400">
×
</button>
</div>
{/* 탭 네비게이션 */}
<div className="flex border-b border-gray-200">
<button
onClick={() => setActiveTab("query")}
className={`border-b-2 px-6 py-3 text-sm font-medium 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={`border-b-2 px-6 py-3 text-sm font-medium transition-colors ${
activeTab === "chart"
? "border-primary text-primary bg-accent"
: "border-transparent text-gray-500 hover:text-gray-700"
} `}
>
📊
{queryResult && (
<span className="ml-2 rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-800">
{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 items-center justify-between border-t border-gray-200 p-6">
<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="text-muted-foreground rounded-lg border border-gray-300 px-4 py-2 hover:bg-gray-50"
>
</button>
<button
onClick={handleSave}
disabled={!dataSource.query || !chartConfig.xAxis || !chartConfig.yAxis}
className="bg-accent0 rounded-lg px-4 py-2 text-white hover:bg-blue-600 disabled:cursor-not-allowed disabled:bg-gray-300"
>
</button>
</div>
</div>
</div>
</div>
);
}