모든 위젯 복구 (캘린더, 운전자 관리 포함) - main과 동일하게 유지
This commit is contained in:
parent
c6930a4e66
commit
6a3ee2f132
|
|
@ -39,6 +39,10 @@ const RiskAlertWidget = dynamic(() => import("@/components/dashboard/widgets/Ris
|
||||||
|
|
||||||
// 시계 위젯 임포트
|
// 시계 위젯 임포트
|
||||||
import { ClockWidget } from "./widgets/ClockWidget";
|
import { ClockWidget } from "./widgets/ClockWidget";
|
||||||
|
// 달력 위젯 임포트
|
||||||
|
import { CalendarWidget } from "./widgets/CalendarWidget";
|
||||||
|
// 기사 관리 위젯 임포트
|
||||||
|
import { DriverManagementWidget } from "./widgets/DriverManagementWidget";
|
||||||
|
|
||||||
interface CanvasElementProps {
|
interface CanvasElementProps {
|
||||||
element: DashboardElement;
|
element: DashboardElement;
|
||||||
|
|
@ -137,9 +141,13 @@ export function CanvasElement({
|
||||||
const deltaY = e.clientY - dragStart.y;
|
const deltaY = e.clientY - dragStart.y;
|
||||||
|
|
||||||
// 임시 위치 계산 (스냅 안 됨)
|
// 임시 위치 계산 (스냅 안 됨)
|
||||||
const rawX = Math.max(0, dragStart.elementX + deltaX);
|
let rawX = Math.max(0, dragStart.elementX + deltaX);
|
||||||
const rawY = Math.max(0, dragStart.elementY + deltaY);
|
const rawY = Math.max(0, dragStart.elementY + deltaY);
|
||||||
|
|
||||||
|
// X 좌표가 캔버스 너비를 벗어나지 않도록 제한
|
||||||
|
const maxX = GRID_CONFIG.CANVAS_WIDTH - element.size.width;
|
||||||
|
rawX = Math.min(rawX, maxX);
|
||||||
|
|
||||||
setTempPosition({ x: rawX, y: rawY });
|
setTempPosition({ x: rawX, y: rawY });
|
||||||
} else if (isResizing) {
|
} else if (isResizing) {
|
||||||
const deltaX = e.clientX - resizeStart.x;
|
const deltaX = e.clientX - resizeStart.x;
|
||||||
|
|
@ -150,46 +158,58 @@ export function CanvasElement({
|
||||||
let newX = resizeStart.elementX;
|
let newX = resizeStart.elementX;
|
||||||
let newY = resizeStart.elementY;
|
let newY = resizeStart.elementY;
|
||||||
|
|
||||||
const minSize = GRID_CONFIG.CELL_SIZE * 2; // 최소 2셀
|
// 최소 크기 설정: 달력은 2x3, 나머지는 2x2
|
||||||
|
const minWidthCells = 2;
|
||||||
|
const minHeightCells = element.type === "widget" && element.subtype === "calendar" ? 3 : 2;
|
||||||
|
const minWidth = GRID_CONFIG.CELL_SIZE * minWidthCells;
|
||||||
|
const minHeight = GRID_CONFIG.CELL_SIZE * minHeightCells;
|
||||||
|
|
||||||
switch (resizeStart.handle) {
|
switch (resizeStart.handle) {
|
||||||
case "se": // 오른쪽 아래
|
case "se": // 오른쪽 아래
|
||||||
newWidth = Math.max(minSize, resizeStart.width + deltaX);
|
newWidth = Math.max(minWidth, resizeStart.width + deltaX);
|
||||||
newHeight = Math.max(minSize, resizeStart.height + deltaY);
|
newHeight = Math.max(minHeight, resizeStart.height + deltaY);
|
||||||
break;
|
break;
|
||||||
case "sw": // 왼쪽 아래
|
case "sw": // 왼쪽 아래
|
||||||
newWidth = Math.max(minSize, resizeStart.width - deltaX);
|
newWidth = Math.max(minWidth, resizeStart.width - deltaX);
|
||||||
newHeight = Math.max(minSize, resizeStart.height + deltaY);
|
newHeight = Math.max(minHeight, resizeStart.height + deltaY);
|
||||||
newX = resizeStart.elementX + deltaX;
|
newX = resizeStart.elementX + deltaX;
|
||||||
break;
|
break;
|
||||||
case "ne": // 오른쪽 위
|
case "ne": // 오른쪽 위
|
||||||
newWidth = Math.max(minSize, resizeStart.width + deltaX);
|
newWidth = Math.max(minWidth, resizeStart.width + deltaX);
|
||||||
newHeight = Math.max(minSize, resizeStart.height - deltaY);
|
newHeight = Math.max(minHeight, resizeStart.height - deltaY);
|
||||||
newY = resizeStart.elementY + deltaY;
|
newY = resizeStart.elementY + deltaY;
|
||||||
break;
|
break;
|
||||||
case "nw": // 왼쪽 위
|
case "nw": // 왼쪽 위
|
||||||
newWidth = Math.max(minSize, resizeStart.width - deltaX);
|
newWidth = Math.max(minWidth, resizeStart.width - deltaX);
|
||||||
newHeight = Math.max(minSize, resizeStart.height - deltaY);
|
newHeight = Math.max(minHeight, resizeStart.height - deltaY);
|
||||||
newX = resizeStart.elementX + deltaX;
|
newX = resizeStart.elementX + deltaX;
|
||||||
newY = resizeStart.elementY + deltaY;
|
newY = resizeStart.elementY + deltaY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 가로 너비가 캔버스를 벗어나지 않도록 제한
|
||||||
|
const maxWidth = GRID_CONFIG.CANVAS_WIDTH - newX;
|
||||||
|
newWidth = Math.min(newWidth, maxWidth);
|
||||||
|
|
||||||
// 임시 크기/위치 저장 (스냅 안 됨)
|
// 임시 크기/위치 저장 (스냅 안 됨)
|
||||||
setTempPosition({ x: Math.max(0, newX), y: Math.max(0, newY) });
|
setTempPosition({ x: Math.max(0, newX), y: Math.max(0, newY) });
|
||||||
setTempSize({ width: newWidth, height: newHeight });
|
setTempSize({ width: newWidth, height: newHeight });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isDragging, isResizing, dragStart, resizeStart],
|
[isDragging, isResizing, dragStart, resizeStart, element.size.width, element.type, element.subtype],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 마우스 업 처리 (그리드 스냅 적용)
|
// 마우스 업 처리 (그리드 스냅 적용)
|
||||||
const handleMouseUp = useCallback(() => {
|
const handleMouseUp = useCallback(() => {
|
||||||
if (isDragging && tempPosition) {
|
if (isDragging && tempPosition) {
|
||||||
// 드래그 종료 시 그리드에 스냅 (동적 셀 크기 사용)
|
// 드래그 종료 시 그리드에 스냅 (동적 셀 크기 사용)
|
||||||
const snappedX = snapToGrid(tempPosition.x, cellSize);
|
let snappedX = snapToGrid(tempPosition.x, cellSize);
|
||||||
const snappedY = snapToGrid(tempPosition.y, cellSize);
|
const snappedY = snapToGrid(tempPosition.y, cellSize);
|
||||||
|
|
||||||
|
// X 좌표가 캔버스 너비를 벗어나지 않도록 최종 제한
|
||||||
|
const maxX = GRID_CONFIG.CANVAS_WIDTH - element.size.width;
|
||||||
|
snappedX = Math.min(snappedX, maxX);
|
||||||
|
|
||||||
onUpdate(element.id, {
|
onUpdate(element.id, {
|
||||||
position: { x: snappedX, y: snappedY },
|
position: { x: snappedX, y: snappedY },
|
||||||
});
|
});
|
||||||
|
|
@ -201,9 +221,13 @@ export function CanvasElement({
|
||||||
// 리사이즈 종료 시 그리드에 스냅 (동적 셀 크기 사용)
|
// 리사이즈 종료 시 그리드에 스냅 (동적 셀 크기 사용)
|
||||||
const snappedX = snapToGrid(tempPosition.x, cellSize);
|
const snappedX = snapToGrid(tempPosition.x, cellSize);
|
||||||
const snappedY = snapToGrid(tempPosition.y, cellSize);
|
const snappedY = snapToGrid(tempPosition.y, cellSize);
|
||||||
const snappedWidth = snapSizeToGrid(tempSize.width, 2, cellSize);
|
let snappedWidth = snapSizeToGrid(tempSize.width, 2, cellSize);
|
||||||
const snappedHeight = snapSizeToGrid(tempSize.height, 2, cellSize);
|
const snappedHeight = snapSizeToGrid(tempSize.height, 2, cellSize);
|
||||||
|
|
||||||
|
// 가로 너비가 캔버스를 벗어나지 않도록 최종 제한
|
||||||
|
const maxWidth = GRID_CONFIG.CANVAS_WIDTH - snappedX;
|
||||||
|
snappedWidth = Math.min(snappedWidth, maxWidth);
|
||||||
|
|
||||||
onUpdate(element.id, {
|
onUpdate(element.id, {
|
||||||
position: { x: snappedX, y: snappedY },
|
position: { x: snappedX, y: snappedY },
|
||||||
size: { width: snappedWidth, height: snappedHeight },
|
size: { width: snappedWidth, height: snappedHeight },
|
||||||
|
|
@ -215,7 +239,7 @@ export function CanvasElement({
|
||||||
|
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
setIsResizing(false);
|
setIsResizing(false);
|
||||||
}, [isDragging, isResizing, tempPosition, tempSize, element.id, onUpdate, cellSize]);
|
}, [isDragging, isResizing, tempPosition, tempSize, element.id, element.size.width, onUpdate, cellSize]);
|
||||||
|
|
||||||
// 전역 마우스 이벤트 등록
|
// 전역 마우스 이벤트 등록
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -253,12 +277,11 @@ export function CanvasElement({
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('❌ 데이터 로딩 오류:', error);
|
|
||||||
setChartData(null);
|
setChartData(null);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingData(false);
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
}, [element.dataSource?.query, element.type, element.subtype]);
|
}, [element.dataSource?.query, element.type]);
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 및 쿼리 변경 시 데이터 로딩
|
// 컴포넌트 마운트 시 및 쿼리 변경 시 데이터 로딩
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -301,6 +324,10 @@ export function CanvasElement({
|
||||||
return "bg-gradient-to-br from-cyan-400 to-indigo-800";
|
return "bg-gradient-to-br from-cyan-400 to-indigo-800";
|
||||||
case "clock":
|
case "clock":
|
||||||
return "bg-gradient-to-br from-teal-400 to-cyan-600";
|
return "bg-gradient-to-br from-teal-400 to-cyan-600";
|
||||||
|
case "calendar":
|
||||||
|
return "bg-gradient-to-br from-indigo-400 to-purple-600";
|
||||||
|
case "driver-management":
|
||||||
|
return "bg-gradient-to-br from-blue-400 to-indigo-600";
|
||||||
default:
|
default:
|
||||||
return "bg-gray-200";
|
return "bg-gray-200";
|
||||||
}
|
}
|
||||||
|
|
@ -330,16 +357,20 @@ export function CanvasElement({
|
||||||
<div className="flex cursor-move items-center justify-between border-b border-gray-200 bg-gray-50 p-3">
|
<div className="flex cursor-move items-center justify-between border-b border-gray-200 bg-gray-50 p-3">
|
||||||
<span className="text-sm font-bold text-gray-800">{element.title}</span>
|
<span className="text-sm font-bold text-gray-800">{element.title}</span>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{/* 설정 버튼 (시계 위젯은 자체 설정 UI 사용) */}
|
{/* 설정 버튼 (시계, 달력, 기사관리 위젯은 자체 설정 UI 사용) */}
|
||||||
{onConfigure && !(element.type === "widget" && element.subtype === "clock") && (
|
{onConfigure &&
|
||||||
<button
|
!(
|
||||||
className="hover:bg-accent0 flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors duration-200 hover:text-white"
|
element.type === "widget" &&
|
||||||
onClick={() => onConfigure(element)}
|
(element.subtype === "clock" || element.subtype === "calendar" || element.subtype === "driver-management")
|
||||||
title="설정"
|
) && (
|
||||||
>
|
<button
|
||||||
⚙️
|
className="hover:bg-accent0 flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors duration-200 hover:text-white"
|
||||||
</button>
|
onClick={() => onConfigure(element)}
|
||||||
)}
|
title="설정"
|
||||||
|
>
|
||||||
|
⚙️
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{/* 삭제 버튼 */}
|
{/* 삭제 버튼 */}
|
||||||
<button
|
<button
|
||||||
className="element-close hover:bg-destructive/100 flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors duration-200 hover:text-white"
|
className="element-close hover:bg-destructive/100 flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors duration-200 hover:text-white"
|
||||||
|
|
@ -366,7 +397,7 @@ export function CanvasElement({
|
||||||
) : (
|
) : (
|
||||||
<ChartRenderer
|
<ChartRenderer
|
||||||
element={element}
|
element={element}
|
||||||
data={chartData}
|
data={chartData || undefined}
|
||||||
width={element.size.width}
|
width={element.size.width}
|
||||||
height={element.size.height - 45}
|
height={element.size.height - 45}
|
||||||
/>
|
/>
|
||||||
|
|
@ -375,16 +406,12 @@ export function CanvasElement({
|
||||||
) : element.type === "widget" && element.subtype === "weather" ? (
|
) : element.type === "widget" && element.subtype === "weather" ? (
|
||||||
// 날씨 위젯 렌더링
|
// 날씨 위젯 렌더링
|
||||||
<div className="widget-interactive-area h-full w-full">
|
<div className="widget-interactive-area h-full w-full">
|
||||||
<WeatherWidget city={element.config?.city || "서울"} refreshInterval={600000} />
|
<WeatherWidget city="서울" refreshInterval={600000} />
|
||||||
</div>
|
</div>
|
||||||
) : element.type === "widget" && element.subtype === "exchange" ? (
|
) : element.type === "widget" && element.subtype === "exchange" ? (
|
||||||
// 환율 위젯 렌더링
|
// 환율 위젯 렌더링
|
||||||
<div className="widget-interactive-area h-full w-full">
|
<div className="widget-interactive-area h-full w-full">
|
||||||
<ExchangeWidget
|
<ExchangeWidget baseCurrency="KRW" targetCurrency="USD" refreshInterval={600000} />
|
||||||
baseCurrency={element.config?.baseCurrency || "KRW"}
|
|
||||||
targetCurrency={element.config?.targetCurrency || "USD"}
|
|
||||||
refreshInterval={600000}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : element.type === "widget" && element.subtype === "clock" ? (
|
) : element.type === "widget" && element.subtype === "clock" ? (
|
||||||
// 시계 위젯 렌더링
|
// 시계 위젯 렌더링
|
||||||
|
|
@ -416,6 +443,26 @@ export function CanvasElement({
|
||||||
<div className="widget-interactive-area h-full w-full">
|
<div className="widget-interactive-area h-full w-full">
|
||||||
<RiskAlertWidget />
|
<RiskAlertWidget />
|
||||||
</div>
|
</div>
|
||||||
|
) : element.type === "widget" && element.subtype === "calendar" ? (
|
||||||
|
// 달력 위젯 렌더링
|
||||||
|
<div className="h-full w-full">
|
||||||
|
<CalendarWidget
|
||||||
|
element={element}
|
||||||
|
onConfigUpdate={(newConfig) => {
|
||||||
|
onUpdate(element.id, { calendarConfig: newConfig });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : element.type === "widget" && element.subtype === "driver-management" ? (
|
||||||
|
// 기사 관리 위젯 렌더링
|
||||||
|
<div className="h-full w-full">
|
||||||
|
<DriverManagementWidget
|
||||||
|
element={element}
|
||||||
|
onConfigUpdate={(newConfig) => {
|
||||||
|
onUpdate(element.id, { driverManagementConfig: newConfig });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// 기타 위젯 렌더링
|
// 기타 위젯 렌더링
|
||||||
<div
|
<div
|
||||||
|
|
@ -471,68 +518,3 @@ function ResizeHandle({ position, onMouseDown }: ResizeHandleProps) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 샘플 데이터 생성 함수 (실제 API 호출 대신 사용)
|
|
||||||
*/
|
|
||||||
function generateSampleData(query: string, chartType: string): QueryResult {
|
|
||||||
// 쿼리에서 키워드 추출하여 적절한 샘플 데이터 생성
|
|
||||||
const isMonthly = query.toLowerCase().includes("month");
|
|
||||||
const isSales = query.toLowerCase().includes("sales") || query.toLowerCase().includes("매출");
|
|
||||||
const isUsers = query.toLowerCase().includes("users") || query.toLowerCase().includes("사용자");
|
|
||||||
const isProducts = query.toLowerCase().includes("product") || query.toLowerCase().includes("상품");
|
|
||||||
|
|
||||||
let columns: string[];
|
|
||||||
let rows: Record<string, any>[];
|
|
||||||
|
|
||||||
if (isMonthly && isSales) {
|
|
||||||
// 월별 매출 데이터
|
|
||||||
columns = ["month", "sales", "order_count"];
|
|
||||||
rows = [
|
|
||||||
{ month: "2024-01", sales: 1200000, order_count: 45 },
|
|
||||||
{ month: "2024-02", sales: 1350000, order_count: 52 },
|
|
||||||
{ month: "2024-03", sales: 1180000, order_count: 41 },
|
|
||||||
{ month: "2024-04", sales: 1420000, order_count: 58 },
|
|
||||||
{ month: "2024-05", sales: 1680000, order_count: 67 },
|
|
||||||
{ month: "2024-06", sales: 1540000, order_count: 61 },
|
|
||||||
];
|
|
||||||
} else if (isUsers) {
|
|
||||||
// 사용자 가입 추이
|
|
||||||
columns = ["week", "new_users"];
|
|
||||||
rows = [
|
|
||||||
{ week: "2024-W10", new_users: 23 },
|
|
||||||
{ week: "2024-W11", new_users: 31 },
|
|
||||||
{ week: "2024-W12", new_users: 28 },
|
|
||||||
{ week: "2024-W13", new_users: 35 },
|
|
||||||
{ week: "2024-W14", new_users: 42 },
|
|
||||||
{ week: "2024-W15", new_users: 38 },
|
|
||||||
];
|
|
||||||
} else if (isProducts) {
|
|
||||||
// 상품별 판매량
|
|
||||||
columns = ["product_name", "total_sold", "revenue"];
|
|
||||||
rows = [
|
|
||||||
{ product_name: "스마트폰", total_sold: 156, revenue: 234000000 },
|
|
||||||
{ product_name: "노트북", total_sold: 89, revenue: 178000000 },
|
|
||||||
{ product_name: "태블릿", total_sold: 134, revenue: 67000000 },
|
|
||||||
{ product_name: "이어폰", total_sold: 267, revenue: 26700000 },
|
|
||||||
{ product_name: "스마트워치", total_sold: 98, revenue: 49000000 },
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
// 기본 샘플 데이터
|
|
||||||
columns = ["category", "value", "count"];
|
|
||||||
rows = [
|
|
||||||
{ category: "A", value: 100, count: 10 },
|
|
||||||
{ category: "B", value: 150, count: 15 },
|
|
||||||
{ category: "C", value: 120, count: 12 },
|
|
||||||
{ category: "D", value: 180, count: 18 },
|
|
||||||
{ category: "E", value: 90, count: 9 },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
rows,
|
|
||||||
totalRows: rows.length,
|
|
||||||
executionTime: Math.floor(Math.random() * 100) + 50, // 50-150ms
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -133,15 +133,31 @@ export function DashboardSidebar() {
|
||||||
type="widget"
|
type="widget"
|
||||||
subtype="delivery-status"
|
subtype="delivery-status"
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
className="border-l-4 border-blue-600"
|
className="border-l-4 border-amber-500"
|
||||||
/>
|
/>
|
||||||
<DraggableItem
|
<DraggableItem
|
||||||
icon="🚨"
|
icon="⚠️"
|
||||||
title="리스크 / 알림"
|
title="리스크/알림 위젯"
|
||||||
type="widget"
|
type="widget"
|
||||||
subtype="risk-alert"
|
subtype="risk-alert"
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
className="border-l-4 border-red-600"
|
className="border-l-4 border-rose-500"
|
||||||
|
/>
|
||||||
|
<DraggableItem
|
||||||
|
icon="📅"
|
||||||
|
title="달력 위젯"
|
||||||
|
type="widget"
|
||||||
|
subtype="calendar"
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
className="border-l-4 border-indigo-500"
|
||||||
|
/>
|
||||||
|
<DraggableItem
|
||||||
|
icon="🚗"
|
||||||
|
title="기사 관리 위젯"
|
||||||
|
type="widget"
|
||||||
|
subtype="driver-management"
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
className="border-l-4 border-blue-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ export type ElementSubtype =
|
||||||
| "exchange"
|
| "exchange"
|
||||||
| "weather"
|
| "weather"
|
||||||
| "clock"
|
| "clock"
|
||||||
|
| "calendar"
|
||||||
| "calculator"
|
| "calculator"
|
||||||
| "vehicle-map"
|
| "vehicle-map"
|
||||||
| "delivery-status"
|
| "delivery-status"
|
||||||
| "risk-alert"; // 위젯 타입
|
| "risk-alert"
|
||||||
|
| "driver-management"; // 위젯 타입
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
x: number;
|
x: number;
|
||||||
|
|
@ -41,6 +43,8 @@ export interface DashboardElement {
|
||||||
dataSource?: ChartDataSource; // 데이터 소스 설정
|
dataSource?: ChartDataSource; // 데이터 소스 설정
|
||||||
chartConfig?: ChartConfig; // 차트 설정
|
chartConfig?: ChartConfig; // 차트 설정
|
||||||
clockConfig?: ClockConfig; // 시계 설정
|
clockConfig?: ClockConfig; // 시계 설정
|
||||||
|
calendarConfig?: CalendarConfig; // 달력 설정
|
||||||
|
driverManagementConfig?: DriverManagementConfig; // 기사 관리 설정
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragData {
|
export interface DragData {
|
||||||
|
|
@ -90,3 +94,42 @@ export interface ClockConfig {
|
||||||
theme: "light" | "dark" | "custom"; // 테마
|
theme: "light" | "dark" | "custom"; // 테마
|
||||||
customColor?: string; // 사용자 지정 색상 (custom 테마일 때)
|
customColor?: string; // 사용자 지정 색상 (custom 테마일 때)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 달력 위젯 설정
|
||||||
|
export interface CalendarConfig {
|
||||||
|
view: "month" | "week" | "day"; // 뷰 타입
|
||||||
|
startWeekOn: "monday" | "sunday"; // 주 시작 요일
|
||||||
|
highlightWeekends: boolean; // 주말 강조
|
||||||
|
highlightToday: boolean; // 오늘 강조
|
||||||
|
showHolidays: boolean; // 공휴일 표시
|
||||||
|
theme: "light" | "dark" | "custom"; // 테마
|
||||||
|
customColor?: string; // 사용자 지정 색상
|
||||||
|
showWeekNumbers?: boolean; // 주차 표시 (선택)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기사 관리 위젯 설정
|
||||||
|
export interface DriverManagementConfig {
|
||||||
|
viewType: "list"; // 뷰 타입 (현재는 리스트만)
|
||||||
|
autoRefreshInterval: number; // 자동 새로고침 간격 (초)
|
||||||
|
visibleColumns: string[]; // 표시할 컬럼 목록
|
||||||
|
theme: "light" | "dark" | "custom"; // 테마
|
||||||
|
customColor?: string; // 사용자 지정 색상
|
||||||
|
statusFilter: "all" | "driving" | "standby" | "resting" | "maintenance"; // 상태 필터
|
||||||
|
sortBy: "name" | "vehicleNumber" | "status" | "departureTime"; // 정렬 기준
|
||||||
|
sortOrder: "asc" | "desc"; // 정렬 순서
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기사 정보
|
||||||
|
export interface DriverInfo {
|
||||||
|
id: string; // 기사 고유 ID
|
||||||
|
name: string; // 기사 이름
|
||||||
|
vehicleNumber: string; // 차량 번호
|
||||||
|
vehicleType: string; // 차량 유형
|
||||||
|
phone: string; // 연락처
|
||||||
|
status: "standby" | "driving" | "resting" | "maintenance"; // 운행 상태
|
||||||
|
departure?: string; // 출발지
|
||||||
|
destination?: string; // 목적지
|
||||||
|
departureTime?: string; // 출발 시간
|
||||||
|
estimatedArrival?: string; // 예상 도착 시간
|
||||||
|
progress?: number; // 운행 진행률 (0-100)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue