Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/dashboard

This commit is contained in:
dohyeons
2025-10-16 10:56:34 +09:00
28 changed files with 471 additions and 271 deletions

View File

@@ -220,13 +220,7 @@ export function DashboardSidebar() {
subtype="booking-alert"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="🔧"
title="정비 일정 관리"
type="widget"
subtype="maintenance"
onDragStart={handleDragStart}
/>
{/* 정비 일정 관리 위젯 제거 - 커스텀 목록 카드로 대체 가능 */}
<DraggableItem
icon="📂"
title="문서 다운로드"

View File

@@ -31,6 +31,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
const [chartConfig, setChartConfig] = useState<ChartConfig>(element.chartConfig || {});
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
const [currentStep, setCurrentStep] = useState<1 | 2>(1);
const [customTitle, setCustomTitle] = useState<string>(element.customTitle || "");
// 차트 설정이 필요 없는 위젯 (쿼리/API만 필요)
const isSimpleWidget =
@@ -56,6 +57,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
setChartConfig(element.chartConfig || {});
setQueryResult(null);
setCurrentStep(1);
setCustomTitle(element.customTitle || "");
}
}, [isOpen, element]);
@@ -119,13 +121,14 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
...element,
dataSource,
chartConfig,
customTitle: customTitle.trim() || undefined, // 빈 문자열이면 undefined
};
console.log(" 저장할 element:", updatedElement);
onSave(updatedElement);
onClose();
}, [element, dataSource, chartConfig, onSave, onClose]);
}, [element, dataSource, chartConfig, customTitle, onSave, onClose]);
// 모달이 열려있지 않으면 렌더링하지 않음
if (!isOpen) return null;
@@ -147,28 +150,32 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
chartConfig.yAxis &&
(typeof chartConfig.yAxis === "string" || (Array.isArray(chartConfig.yAxis) && chartConfig.yAxis.length > 0));
const canSave = isSimpleWidget
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능
currentStep === 2 && queryResult && queryResult.rows.length > 0
: isMapWidget
? // 지도 위젯: 위도/경도 매핑 필요
currentStep === 2 &&
queryResult &&
queryResult.rows.length > 0 &&
chartConfig.latitudeColumn &&
chartConfig.longitudeColumn
: // 차트: 기존 로직 (2단계에서 차트 설정 필요)
currentStep === 2 &&
queryResult &&
queryResult.rows.length > 0 &&
chartConfig.xAxis &&
(isPieChart || isApiSource
? // 파이/도넛 차트 또는 REST API
chartConfig.aggregation === "count"
? true // count는 Y축 없어도 됨
: hasYAxis // 다른 집계(sum, avg, max, min) 또는 집계 없음 → Y축 필수
: // 일반 차트 (DB): Y축 필수
hasYAxis);
// customTitle이 변경되었는지 확인
const isTitleChanged = customTitle.trim() !== (element.customTitle || "");
const canSave = isTitleChanged || // 제목만 변경해도 저장 가능
(isSimpleWidget
? // 간단한 위젯: 2단계에서 쿼리 테스트 후 저장 가능
currentStep === 2 && queryResult && queryResult.rows.length > 0
: isMapWidget
? // 지도 위젯: 위도/경도 매핑 필요
currentStep === 2 &&
queryResult &&
queryResult.rows.length > 0 &&
chartConfig.latitudeColumn &&
chartConfig.longitudeColumn
: // 차트: 기존 로직 (2단계에서 차트 설정 필요)
currentStep === 2 &&
queryResult &&
queryResult.rows.length > 0 &&
chartConfig.xAxis &&
(isPieChart || isApiSource
? // 파이/도넛 차트 또는 REST API
chartConfig.aggregation === "count"
? true // count는 Y축 없어도 됨
: hasYAxis // 다른 집계(sum, avg, max, min) 또는 집계 없음 → Y축 필수
: // 일반 차트 (DB): Y축 필수
hasYAxis));
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
@@ -178,20 +185,39 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
}`}
>
{/* 모달 헤더 */}
<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">
{isSimpleWidget
? "데이터 소스를 설정하세요"
: currentStep === 1
? "데이터 소스를 선택하세요"
: "쿼리를 실행하고 차트를 설정하세요"}
<div className="border-b p-6">
<div className="flex items-center justify-between">
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-900">{element.title} </h2>
<p className="mt-1 text-sm text-gray-500">
{isSimpleWidget
? "데이터 소스를 설정하세요"
: currentStep === 1
? "데이터 소스를 선택하세요"
: "쿼리를 실행하고 차트를 설정하세요"}
</p>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
<X className="h-5 w-5" />
</Button>
</div>
{/* 커스텀 제목 입력 */}
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
()
</label>
<input
type="text"
value={customTitle}
onChange={(e) => setCustomTitle(e.target.value)}
placeholder={`예: 정비 일정 목록, 창고 위치 현황 등 (비워두면 자동 생성)`}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
/>
<p className="mt-1 text-xs text-gray-500">
💡 (: "maintenance_schedules 목록")
</p>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
<X className="h-5 w-5" />
</Button>
</div>
{/* 진행 상황 표시 - 간단한 위젯은 표시 안 함 */}

View File

@@ -54,6 +54,7 @@ export interface DashboardElement {
position: Position;
size: Size;
title: string;
customTitle?: string; // 사용자 정의 제목 (옵션)
content: string;
dataSource?: ChartDataSource; // 데이터 소스 설정
chartConfig?: ChartConfig; // 차트 설정