범용적으로 만든것들이랑 ui 수정

This commit is contained in:
leeheejin
2025-10-15 16:16:27 +09:00
parent 03635ff82e
commit 39ddf59275
12 changed files with 2074 additions and 152 deletions

View File

@@ -37,11 +37,42 @@ const VehicleMapOnlyWidget = dynamic(() => import("@/components/dashboard/widget
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
});
const DeliveryStatusWidget = dynamic(() => import("@/components/dashboard/widgets/DeliveryStatusWidget"), {
// 범용 지도 위젯 (차량, 창고, 고객 등 모든 위치 위젯 통합)
const MapSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/MapSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
});
// 범용 상태 요약 위젯 (차량, 배송 등 모든 상태 위젯 통합)
const StatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/StatusSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
});
// 범용 목록 위젯 (차량, 기사, 제품 등 모든 목록 위젯 통합) - 다른 분 작업 중, 임시 주석
/* const ListSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/ListSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
}); */
// 개별 위젯들 (주석 처리 - StatusSummaryWidget으로 통합됨)
// const DeliveryStatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/DeliveryStatusSummaryWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// });
// const DeliveryTodayStatsWidget = dynamic(() => import("@/components/dashboard/widgets/DeliveryTodayStatsWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// });
// const CargoListWidget = dynamic(() => import("@/components/dashboard/widgets/CargoListWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// });
// const CustomerIssuesWidget = dynamic(() => import("@/components/dashboard/widgets/CustomerIssuesWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// });
const RiskAlertWidget = dynamic(() => import("@/components/dashboard/widgets/RiskAlertWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
@@ -496,15 +527,86 @@ export function CanvasElement({
<div className="widget-interactive-area h-full w-full">
<VehicleListWidget element={element} />
</div>
) : element.type === "widget" && element.subtype === "map-summary" ? (
// 커스텀 지도 카드 - 범용 위젯
<div className="widget-interactive-area h-full w-full">
<MapSummaryWidget element={element} />
</div>
) : element.type === "widget" && element.subtype === "vehicle-map" ? (
// 차량 위치 지도 위젯 렌더링
// 차량 위치 지도 위젯 렌더링 (구버전 - 호환용)
<div className="widget-interactive-area h-full w-full">
<VehicleMapOnlyWidget element={element} />
</div>
) : element.type === "widget" && element.subtype === "delivery-status" ? (
// 배송/화물 현황 위젯 렌더링
) : element.type === "widget" && element.subtype === "status-summary" ? (
// 커스텀 상태 카드 - 범용 위젯
<div className="widget-interactive-area h-full w-full">
<DeliveryStatusWidget element={element} />
<StatusSummaryWidget
element={element}
title="상태 요약"
icon="📊"
bgGradient="from-slate-50 to-blue-50"
/>
</div>
) : /* element.type === "widget" && element.subtype === "list-summary" ? (
// 커스텀 목록 카드 - 범용 위젯 (다른 분 작업 중 - 임시 주석)
<div className="widget-interactive-area h-full w-full">
<ListSummaryWidget element={element} />
</div>
) : */ element.type === "widget" && element.subtype === "delivery-status" ? (
// 배송/화물 현황 위젯 - 범용 위젯 사용 (구버전 호환)
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget
element={element}
title="배송/화물 현황"
icon="📦"
bgGradient="from-slate-50 to-blue-50"
/>
</div>
) : element.type === "widget" && element.subtype === "delivery-status-summary" ? (
// 배송 상태 요약 - 범용 위젯 사용
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget
element={element}
title="배송 상태 요약"
icon="📊"
bgGradient="from-slate-50 to-blue-50"
statusConfig={{
"배송중": { label: "배송중", color: "blue" },
"완료": { label: "완료", color: "green" },
"지연": { label: "지연", color: "red" },
"픽업 대기": { label: "픽업 대기", color: "yellow" }
}}
/>
</div>
) : element.type === "widget" && element.subtype === "delivery-today-stats" ? (
// 오늘 처리 현황 - 범용 위젯 사용
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget
element={element}
title="오늘 처리 현황"
icon="📈"
bgGradient="from-slate-50 to-green-50"
/>
</div>
) : element.type === "widget" && element.subtype === "cargo-list" ? (
// 화물 목록 - 범용 위젯 사용
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget
element={element}
title="화물 목록"
icon="📦"
bgGradient="from-slate-50 to-orange-50"
/>
</div>
) : element.type === "widget" && element.subtype === "customer-issues" ? (
// 고객 클레임/이슈 - 범용 위젯 사용
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget
element={element}
title="고객 클레임/이슈"
icon="⚠️"
bgGradient="from-slate-50 to-red-50"
/>
</div>
) : element.type === "widget" && element.subtype === "risk-alert" ? (
// 리스크/알림 위젯 렌더링

View File

@@ -1,14 +1,26 @@
"use client";
import React from "react";
import React, { useState } from "react";
import { DragData, ElementType, ElementSubtype } from "./types";
import { ChevronDown, ChevronRight } from "lucide-react";
/**
* 대시보드 사이드바 컴포넌트
* - 드래그 가능한 차트/위젯 목록
* - 카테고리별 구분
* - 아코디언 방식으로 카테고리별 구분
*/
export function DashboardSidebar() {
const [expandedSections, setExpandedSections] = useState({
charts: true,
widgets: true,
operations: true,
});
// 섹션 토글
const toggleSection = (section: keyof typeof expandedSections) => {
setExpandedSections((prev) => ({ ...prev, [section]: !prev[section] }));
};
// 드래그 시작 처리
const handleDragStart = (e: React.DragEvent, type: ElementType, subtype: ElementSubtype) => {
const dragData: DragData = { type, subtype };
@@ -17,27 +29,36 @@ export function DashboardSidebar() {
};
return (
<div className="w-[370px] overflow-y-auto border-l border-gray-200 bg-white p-6">
<div className="w-[370px] overflow-y-auto border-l border-border bg-background p-5">
{/* 차트 섹션 */}
<div className="mb-8">
<h3 className="mb-4 border-b-2 border-green-500 pb-3 text-lg font-semibold text-gray-800">📊 </h3>
<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>
<div className="space-y-3">
<DraggableItem
icon="📊"
title="바 차트"
type="chart"
subtype="bar"
onDragStart={handleDragStart}
className="border-primary border-l-4"
/>
{expandedSections.charts && (
<div className="space-y-2">
<DraggableItem
icon="📊"
title="바 차트"
type="chart"
subtype="bar"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="📊"
title="수평 바 차트"
type="chart"
subtype="horizontal-bar"
onDragStart={handleDragStart}
className="border-l-4 border-blue-500"
/>
<DraggableItem
icon="📚"
@@ -45,7 +66,6 @@ export function DashboardSidebar() {
type="chart"
subtype="stacked-bar"
onDragStart={handleDragStart}
className="border-l-4 border-blue-600"
/>
<DraggableItem
icon="📈"
@@ -53,7 +73,6 @@ export function DashboardSidebar() {
type="chart"
subtype="line"
onDragStart={handleDragStart}
className="border-l-4 border-green-500"
/>
<DraggableItem
icon="📉"
@@ -61,7 +80,6 @@ export function DashboardSidebar() {
type="chart"
subtype="area"
onDragStart={handleDragStart}
className="border-l-4 border-green-600"
/>
<DraggableItem
icon="🥧"
@@ -69,7 +87,6 @@ export function DashboardSidebar() {
type="chart"
subtype="pie"
onDragStart={handleDragStart}
className="border-l-4 border-purple-500"
/>
<DraggableItem
icon="🍩"
@@ -77,39 +94,47 @@ export function DashboardSidebar() {
type="chart"
subtype="donut"
onDragStart={handleDragStart}
className="border-l-4 border-purple-600"
/>
<DraggableItem
icon="📊📈"
icon="📊"
title="콤보 차트"
type="chart"
subtype="combo"
onDragStart={handleDragStart}
className="border-l-4 border-indigo-500"
/>
</div>
</div>
)}
</div>
{/* 위젯 섹션 */}
<div className="mb-8">
<h3 className="mb-4 border-b-2 border-green-500 pb-3 text-lg font-semibold text-gray-800">🔧 </h3>
<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>
<div className="space-y-3">
<DraggableItem
icon="💱"
title="환율 위젯"
type="widget"
subtype="exchange"
onDragStart={handleDragStart}
className="border-l-4 border-orange-500"
/>
{expandedSections.widgets && (
<div className="space-y-2">
<DraggableItem
icon="💱"
title="환율 위젯"
type="widget"
subtype="exchange"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="☁️"
title="날씨 위젯"
type="widget"
subtype="weather"
onDragStart={handleDragStart}
className="border-l-4 border-cyan-500"
/>
<DraggableItem
icon="🧮"
@@ -117,7 +142,6 @@ export function DashboardSidebar() {
type="widget"
subtype="calculator"
onDragStart={handleDragStart}
className="border-l-4 border-green-500"
/>
<DraggableItem
icon="⏰"
@@ -125,47 +149,28 @@ export function DashboardSidebar() {
type="widget"
subtype="clock"
onDragStart={handleDragStart}
className="border-l-4 border-teal-500"
/>
<DraggableItem
icon="📊"
title="차량 상태 현황"
icon="📍"
title="커스텀 지도 카드"
type="widget"
subtype="vehicle-status"
subtype="map-summary"
onDragStart={handleDragStart}
className="border-l-4 border-green-500"
/>
<DraggableItem
{/* 주석: 다른 분이 범용 리스트 작업 중 - 충돌 방지를 위해 임시 주석처리 */}
{/* <DraggableItem
icon="📋"
title="차량 목록"
title="커스텀 목록 카드"
type="widget"
subtype="vehicle-list"
subtype="list-summary"
onDragStart={handleDragStart}
className="border-l-4 border-blue-500"
/>
<DraggableItem
icon="🗺️"
title="차량 위치 지도"
type="widget"
subtype="vehicle-map"
onDragStart={handleDragStart}
className="border-l-4 border-red-500"
/>
<DraggableItem
icon="📦"
title="배송/화물 현황"
type="widget"
subtype="delivery-status"
onDragStart={handleDragStart}
className="border-l-4 border-amber-500"
/>
/> */}
<DraggableItem
icon="⚠️"
title="리스크/알림 위젯"
type="widget"
subtype="risk-alert"
onDragStart={handleDragStart}
className="border-l-4 border-rose-500"
/>
<DraggableItem
icon="📅"
@@ -173,65 +178,71 @@ export function DashboardSidebar() {
type="widget"
subtype="calendar"
onDragStart={handleDragStart}
className="border-l-4 border-indigo-500"
/>
<DraggableItem
icon="🚗"
title="기사 관리 위젯"
icon="📊"
title="커스텀 상태 카드"
type="widget"
subtype="driver-management"
subtype="status-summary"
onDragStart={handleDragStart}
className="border-l-4 border-blue-500"
/>
</div>
</div>
)}
</div>
{/* 운영/작업 지원 섹션 */}
<div className="mb-8">
<h3 className="mb-4 border-b-2 border-green-500 pb-3 text-lg font-semibold text-gray-800">📋 / </h3>
<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>
<div className="space-y-3">
<DraggableItem
icon="✅"
title="To-Do / 긴급 지시"
type="widget"
subtype="todo"
onDragStart={handleDragStart}
className="border-l-4 border-blue-600"
/>
<DraggableItem
icon="🔔"
title="예약 요청 알림"
type="widget"
subtype="booking-alert"
onDragStart={handleDragStart}
className="border-l-4 border-rose-600"
/>
<DraggableItem
icon="🔧"
title="정비 일정 관리"
type="widget"
subtype="maintenance"
onDragStart={handleDragStart}
className="border-l-4 border-teal-600"
/>
<DraggableItem
icon="📂"
title="문서 다운로드"
type="widget"
subtype="document"
onDragStart={handleDragStart}
className="border-l-4 border-purple-600"
/>
<DraggableItem
icon="📋"
title="리스트 위젯"
type="widget"
subtype="list"
onDragStart={handleDragStart}
className="border-l-4 border-blue-600"
/>
</div>
{expandedSections.operations && (
<div className="space-y-2">
<DraggableItem
icon="✅"
title="To-Do / 긴급 지시"
type="widget"
subtype="todo"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="🔔"
title="예약 요청 알림"
type="widget"
subtype="booking-alert"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="🔧"
title="정비 일정 관리"
type="widget"
subtype="maintenance"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="📂"
title="문서 다운로드"
type="widget"
subtype="document"
onDragStart={handleDragStart}
/>
<DraggableItem
icon="📋"
title="리스트 위젯"
type="widget"
subtype="list"
onDragStart={handleDragStart}
/>
</div>
)}
</div>
</div>
);
@@ -253,10 +264,9 @@ function DraggableItem({ icon, title, type, subtype, className = "", onDragStart
return (
<div
draggable
className={`cursor-move rounded-lg border-2 border-gray-200 bg-white p-4 text-center text-sm font-medium transition-all duration-200 hover:translate-x-1 hover:border-green-500 hover:bg-gray-50 ${className} `}
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"
onDragStart={(e) => onDragStart(e, type, subtype)}
>
<span className="mr-2 text-lg">{icon}</span>
{title}
</div>
);

View File

@@ -37,11 +37,17 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
const isSimpleWidget =
element.subtype === "vehicle-status" ||
element.subtype === "vehicle-list" ||
element.subtype === "status-summary" || // 커스텀 상태 카드
// element.subtype === "list-summary" || // 커스텀 목록 카드 (다른 분 작업 중 - 임시 주석)
element.subtype === "delivery-status" ||
element.subtype === "delivery-status-summary" ||
element.subtype === "delivery-today-stats" ||
element.subtype === "cargo-list" ||
element.subtype === "customer-issues" ||
element.subtype === "driver-management";
// 지도 위젯 (위도/경도 매핑 필요)
const isMapWidget = element.subtype === "vehicle-map";
const isMapWidget = element.subtype === "vehicle-map" || element.subtype === "map-summary";
// 주석
// 모달이 열릴 때 초기화

View File

@@ -19,11 +19,18 @@ export type ElementSubtype =
| "calendar"
| "calculator"
| "vehicle-status"
| "vehicle-list"
| "vehicle-map"
| "vehicle-list" // (구버전 - 호환용)
| "vehicle-map" // (구버전 - 호환용)
| "map-summary" // 범용 지도 카드 (통합)
| "delivery-status"
| "status-summary" // 범용 상태 카드 (통합)
// | "list-summary" // 범용 목록 카드 (다른 분 작업 중 - 임시 주석)
| "delivery-status-summary" // (구버전 - 호환용)
| "delivery-today-stats" // (구버전 - 호환용)
| "cargo-list" // (구버전 - 호환용)
| "customer-issues" // (구버전 - 호환용)
| "risk-alert"
| "driver-management"
| "driver-management" // (구버전 - 호환용)
| "todo"
| "booking-alert"
| "maintenance"
@@ -112,6 +119,9 @@ export interface ChartConfig {
// 애니메이션
enableAnimation?: boolean; // 애니메이션 활성화
// 상태 필터링 (커스텀 상태 카드용)
statusFilter?: string[]; // 표시할 상태 목록 (예: ["driving", "parked"])
animationDuration?: number; // 애니메이션 시간 (ms)
// 툴팁