시계 위젯 구현
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { DashboardElement, ChartDataSource, ChartConfig, QueryResult } from './types';
|
||||
import { QueryEditor } from './QueryEditor';
|
||||
import { ChartConfigPanel } from './ChartConfigPanel';
|
||||
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;
|
||||
@@ -20,13 +21,11 @@ interface ElementConfigModalProps {
|
||||
*/
|
||||
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 || {}
|
||||
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 [activeTab, setActiveTab] = useState<"query" | "chart">("query");
|
||||
|
||||
// 데이터 소스 변경 처리
|
||||
const handleDataSourceChange = useCallback((newDataSource: ChartDataSource) => {
|
||||
@@ -43,7 +42,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||
setQueryResult(result);
|
||||
// 쿼리 결과가 나오면 자동으로 차트 설정 탭으로 이동
|
||||
if (result.rows.length > 0) {
|
||||
setActiveTab('chart');
|
||||
setActiveTab("chart");
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -58,26 +57,51 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||
onClose();
|
||||
}, [element, dataSource, chartConfig, onSave, onClose]);
|
||||
|
||||
// 시계 위젯 설정 저장
|
||||
const handleClockConfigSave = useCallback(
|
||||
(clockConfig: ClockConfig) => {
|
||||
const updatedElement: DashboardElement = {
|
||||
...element,
|
||||
clockConfig,
|
||||
};
|
||||
onSave(updatedElement);
|
||||
},
|
||||
[element, onSave],
|
||||
);
|
||||
|
||||
// 모달이 열려있지 않으면 렌더링하지 않음
|
||||
if (!isOpen) return null;
|
||||
|
||||
// 시계 위젯인 경우 시계 설정 모달 표시
|
||||
if (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="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="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="flex h-[80vh] w-full max-w-4xl flex-col rounded-lg bg-white shadow-xl">
|
||||
{/* 모달 헤더 */}
|
||||
<div className="flex justify-between items-center p-6 border-b border-gray-200">
|
||||
<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-sm text-muted-foreground mt-1">
|
||||
데이터 소스와 차트 설정을 구성하세요
|
||||
</p>
|
||||
<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="text-gray-400 hover:text-muted-foreground text-2xl"
|
||||
>
|
||||
<button onClick={onClose} className="hover:text-muted-foreground text-2xl text-gray-400">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -85,28 +109,26 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||
{/* 탭 네비게이션 */}
|
||||
<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'}
|
||||
`}
|
||||
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={`
|
||||
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'}
|
||||
`}
|
||||
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 px-2 py-0.5 bg-green-100 text-green-800 text-xs rounded-full">
|
||||
<span className="ml-2 rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-800">
|
||||
{queryResult.rows.length}
|
||||
</span>
|
||||
)}
|
||||
@@ -115,7 +137,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||
|
||||
{/* 탭 내용 */}
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
{activeTab === 'query' && (
|
||||
{activeTab === "query" && (
|
||||
<QueryEditor
|
||||
dataSource={dataSource}
|
||||
onDataSourceChange={handleDataSourceChange}
|
||||
@@ -123,41 +145,32 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'chart' && (
|
||||
<ChartConfigPanel
|
||||
config={chartConfig}
|
||||
queryResult={queryResult}
|
||||
onConfigChange={handleChartConfigChange}
|
||||
/>
|
||||
{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="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}
|
||||
💾 쿼리: {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"
|
||||
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="
|
||||
px-4 py-2 bg-accent0 text-white rounded-lg
|
||||
hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed
|
||||
"
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user