2025-10-13 17:21:24 +09:00
|
|
|
"use client";
|
2025-09-30 13:23:22 +09:00
|
|
|
|
2025-10-15 16:16:27 +09:00
|
|
|
import React, { useState } from "react";
|
2025-10-13 17:21:24 +09:00
|
|
|
import { DragData, ElementType, ElementSubtype } from "./types";
|
2025-10-15 16:16:27 +09:00
|
|
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
2025-09-30 13:23:22 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 대시보드 사이드바 컴포넌트
|
|
|
|
|
* - 드래그 가능한 차트/위젯 목록
|
2025-10-15 16:16:27 +09:00
|
|
|
* - 아코디언 방식으로 카테고리별 구분
|
2025-09-30 13:23:22 +09:00
|
|
|
*/
|
|
|
|
|
export function DashboardSidebar() {
|
2025-10-15 16:16:27 +09:00
|
|
|
const [expandedSections, setExpandedSections] = useState({
|
|
|
|
|
charts: true,
|
|
|
|
|
widgets: true,
|
|
|
|
|
operations: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 섹션 토글
|
|
|
|
|
const toggleSection = (section: keyof typeof expandedSections) => {
|
|
|
|
|
setExpandedSections((prev) => ({ ...prev, [section]: !prev[section] }));
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-30 13:23:22 +09:00
|
|
|
// 드래그 시작 처리
|
|
|
|
|
const handleDragStart = (e: React.DragEvent, type: ElementType, subtype: ElementSubtype) => {
|
|
|
|
|
const dragData: DragData = { type, subtype };
|
2025-10-13 17:21:24 +09:00
|
|
|
e.dataTransfer.setData("application/json", JSON.stringify(dragData));
|
|
|
|
|
e.dataTransfer.effectAllowed = "copy";
|
2025-09-30 13:23:22 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2025-10-15 16:16:27 +09:00
|
|
|
<div className="w-[370px] overflow-y-auto border-l border-border bg-background p-5">
|
2025-09-30 13:23:22 +09:00
|
|
|
{/* 차트 섹션 */}
|
2025-10-15 16:16:27 +09:00
|
|
|
<div className="mb-5">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => toggleSection("charts")}
|
|
|
|
|
className="mb-3 flex w-full items-center justify-between px-1 py-2.5 text-xl font-bold text-foreground transition-colors hover:text-primary"
|
|
|
|
|
>
|
|
|
|
|
<span>차트 종류</span>
|
|
|
|
|
{expandedSections.charts ? (
|
|
|
|
|
<ChevronDown className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronRight className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
2025-10-13 17:21:24 +09:00
|
|
|
|
2025-10-15 16:16:27 +09:00
|
|
|
{expandedSections.charts && (
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="바 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="bar"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-14 16:49:57 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="수평 바 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="horizontal-bar"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-09-30 13:23:22 +09:00
|
|
|
<DraggableItem
|
2025-10-01 12:06:24 +09:00
|
|
|
title="누적 바 차트"
|
2025-09-30 13:23:22 +09:00
|
|
|
type="chart"
|
2025-10-01 12:06:24 +09:00
|
|
|
subtype="stacked-bar"
|
2025-09-30 13:23:22 +09:00
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="꺾은선 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="line"
|
|
|
|
|
onDragStart={handleDragStart}
|
2025-10-01 12:06:24 +09:00
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="영역 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="area"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="원형 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="pie"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="도넛 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="donut"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="콤보 차트"
|
|
|
|
|
type="chart"
|
|
|
|
|
subtype="combo"
|
|
|
|
|
onDragStart={handleDragStart}
|
2025-09-30 13:23:22 +09:00
|
|
|
/>
|
2025-10-15 16:16:27 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-30 13:23:22 +09:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 위젯 섹션 */}
|
2025-10-15 16:16:27 +09:00
|
|
|
<div className="mb-5">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => toggleSection("widgets")}
|
|
|
|
|
className="mb-3 flex w-full items-center justify-between px-1 py-2.5 text-xl font-bold text-foreground transition-colors hover:text-primary"
|
|
|
|
|
>
|
|
|
|
|
<span>위젯 종류</span>
|
|
|
|
|
{expandedSections.widgets ? (
|
|
|
|
|
<ChevronDown className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronRight className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
2025-10-13 17:21:24 +09:00
|
|
|
|
2025-10-15 16:16:27 +09:00
|
|
|
{expandedSections.widgets && (
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="환율 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="exchange"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-09-30 13:23:22 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="날씨 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="weather"
|
|
|
|
|
onDragStart={handleDragStart}
|
2025-10-14 10:34:18 +09:00
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="계산기 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="calculator"
|
|
|
|
|
onDragStart={handleDragStart}
|
2025-09-30 13:23:22 +09:00
|
|
|
/>
|
2025-10-14 09:41:33 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="시계 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="clock"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-14 11:55:31 +09:00
|
|
|
<DraggableItem
|
2025-10-15 16:16:27 +09:00
|
|
|
title="커스텀 지도 카드"
|
2025-10-15 10:29:15 +09:00
|
|
|
type="widget"
|
2025-10-15 16:16:27 +09:00
|
|
|
subtype="map-summary"
|
2025-10-15 10:29:15 +09:00
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-17 10:38:22 +09:00
|
|
|
{/* <DraggableItem
|
2025-10-15 16:16:27 +09:00
|
|
|
title="커스텀 목록 카드"
|
2025-10-14 16:36:00 +09:00
|
|
|
type="widget"
|
2025-10-15 16:16:27 +09:00
|
|
|
subtype="list-summary"
|
2025-10-14 16:36:00 +09:00
|
|
|
onDragStart={handleDragStart}
|
2025-10-17 10:38:22 +09:00
|
|
|
/> */}
|
2025-10-14 16:36:00 +09:00
|
|
|
<DraggableItem
|
2025-10-14 16:42:23 +09:00
|
|
|
title="리스크/알림 위젯"
|
2025-10-14 16:36:00 +09:00
|
|
|
type="widget"
|
|
|
|
|
subtype="risk-alert"
|
|
|
|
|
onDragStart={handleDragStart}
|
2025-10-14 16:42:23 +09:00
|
|
|
/>
|
2025-10-17 09:49:02 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="To-Do / 긴급 지시"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="todo"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-14 10:48:17 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="달력 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="calendar"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-14 11:26:53 +09:00
|
|
|
<DraggableItem
|
2025-10-15 16:16:27 +09:00
|
|
|
title="커스텀 상태 카드"
|
2025-10-14 11:26:53 +09:00
|
|
|
type="widget"
|
2025-10-15 16:16:27 +09:00
|
|
|
subtype="status-summary"
|
2025-10-14 11:26:53 +09:00
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-15 16:16:27 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-30 13:23:22 +09:00
|
|
|
</div>
|
2025-10-14 17:21:28 +09:00
|
|
|
|
|
|
|
|
{/* 운영/작업 지원 섹션 */}
|
2025-10-15 16:16:27 +09:00
|
|
|
<div className="mb-5">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => toggleSection("operations")}
|
|
|
|
|
className="mb-3 flex w-full items-center justify-between px-1 py-2.5 text-xl font-bold text-foreground transition-colors hover:text-primary"
|
|
|
|
|
>
|
|
|
|
|
<span>운영/작업 지원</span>
|
|
|
|
|
{expandedSections.operations ? (
|
|
|
|
|
<ChevronDown className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronRight className="h-5 w-5 text-muted-foreground transition-transform" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
2025-10-14 17:21:28 +09:00
|
|
|
|
2025-10-15 16:16:27 +09:00
|
|
|
{expandedSections.operations && (
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="To-Do / 긴급 지시"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="todo"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="예약 요청 알림"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="booking-alert"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
2025-10-15 18:25:16 +09:00
|
|
|
{/* 정비 일정 관리 위젯 제거 - 커스텀 목록 카드로 대체 가능 */}
|
2025-10-15 16:16:27 +09:00
|
|
|
<DraggableItem
|
|
|
|
|
title="문서 다운로드"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="document"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
<DraggableItem
|
|
|
|
|
title="리스트 위젯"
|
|
|
|
|
type="widget"
|
|
|
|
|
subtype="list"
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-10-14 17:21:28 +09:00
|
|
|
</div>
|
2025-09-30 13:23:22 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface DraggableItemProps {
|
2025-10-17 14:52:08 +09:00
|
|
|
icon?: string;
|
2025-09-30 13:23:22 +09:00
|
|
|
title: string;
|
|
|
|
|
type: ElementType;
|
|
|
|
|
subtype: ElementSubtype;
|
|
|
|
|
className?: string;
|
|
|
|
|
onDragStart: (e: React.DragEvent, type: ElementType, subtype: ElementSubtype) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 드래그 가능한 아이템 컴포넌트
|
|
|
|
|
*/
|
2025-10-17 14:52:08 +09:00
|
|
|
function DraggableItem({ title, type, subtype, className = "", onDragStart }: DraggableItemProps) {
|
2025-09-30 13:23:22 +09:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
draggable
|
2025-10-15 16:16:27 +09:00
|
|
|
className="cursor-move rounded-md border border-border bg-card px-4 py-2.5 text-sm font-medium text-card-foreground transition-all duration-150 hover:border-primary hover:bg-accent"
|
2025-09-30 13:23:22 +09:00
|
|
|
onDragStart={(e) => onDragStart(e, type, subtype)}
|
|
|
|
|
>
|
|
|
|
|
{title}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|