ERP-node/frontend/components/dashboard/widgets/MaintenanceWidget.tsx

245 lines
9.2 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { Calendar, Wrench, Truck, Check, Clock, AlertTriangle } from "lucide-react";
interface MaintenanceSchedule {
id: string;
vehicleNumber: string;
vehicleType: string;
maintenanceType: "정기점검" | "수리" | "타이어교체" | "오일교환" | "기타";
scheduledDate: string;
status: "scheduled" | "in_progress" | "completed" | "overdue";
notes?: string;
estimatedCost?: number;
}
// 목 데이터
const mockSchedules: MaintenanceSchedule[] = [
{
id: "1",
vehicleNumber: "서울12가3456",
vehicleType: "1톤 트럭",
maintenanceType: "정기점검",
scheduledDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(),
status: "scheduled",
notes: "6개월 정기점검",
estimatedCost: 300000,
},
{
id: "2",
vehicleNumber: "경기34나5678",
vehicleType: "2.5톤 트럭",
maintenanceType: "오일교환",
scheduledDate: new Date(Date.now() + 1 * 24 * 60 * 60 * 1000).toISOString(),
status: "scheduled",
estimatedCost: 150000,
},
{
id: "3",
vehicleNumber: "인천56다7890",
vehicleType: "라보",
maintenanceType: "타이어교체",
scheduledDate: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
status: "overdue",
notes: "긴급",
estimatedCost: 400000,
},
{
id: "4",
vehicleNumber: "부산78라1234",
vehicleType: "1톤 트럭",
maintenanceType: "수리",
scheduledDate: new Date().toISOString(),
status: "in_progress",
notes: "엔진 점검 중",
estimatedCost: 800000,
},
];
export default function MaintenanceWidget() {
const [schedules] = useState<MaintenanceSchedule[]>(mockSchedules);
const [filter, setFilter] = useState<"all" | MaintenanceSchedule["status"]>("all");
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
const filteredSchedules = schedules.filter(
(s) => filter === "all" || s.status === filter
);
const getStatusBadge = (status: MaintenanceSchedule["status"]) => {
switch (status) {
case "scheduled":
return <span className="rounded bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700"></span>;
case "in_progress":
return <span className="rounded bg-amber-100 px-2 py-1 text-xs font-medium text-amber-700"></span>;
case "completed":
return <span className="rounded bg-green-100 px-2 py-1 text-xs font-medium text-green-700"></span>;
case "overdue":
return <span className="rounded bg-red-100 px-2 py-1 text-xs font-medium text-red-700"></span>;
}
};
const getMaintenanceIcon = (type: MaintenanceSchedule["maintenanceType"]) => {
switch (type) {
case "정기점검":
return "🔍";
case "수리":
return "🔧";
case "타이어교체":
return "⚙️";
case "오일교환":
return "🛢️";
default:
return "🔧";
}
};
const getDaysUntil = (date: string) => {
const now = new Date();
const scheduled = new Date(date);
const diff = scheduled.getTime() - now.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days < 0) return `${Math.abs(days)}일 지연`;
if (days === 0) return "오늘";
if (days === 1) return "내일";
return `${days}일 후`;
};
const stats = {
total: schedules.length,
scheduled: schedules.filter((s) => s.status === "scheduled").length,
inProgress: schedules.filter((s) => s.status === "in_progress").length,
overdue: schedules.filter((s) => s.status === "overdue").length,
};
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-teal-50">
{/* 헤더 */}
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-lg font-bold text-gray-800">🔧 </h3>
<button className="rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90">
+
</button>
</div>
{/* 통계 */}
<div className="mb-3 grid grid-cols-4 gap-2 text-xs">
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
<div className="font-bold text-blue-700">{stats.scheduled}</div>
<div className="text-blue-600"></div>
</div>
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
<div className="font-bold text-amber-700">{stats.inProgress}</div>
<div className="text-amber-600"></div>
</div>
<div className="rounded bg-red-50 px-2 py-1.5 text-center">
<div className="font-bold text-red-700">{stats.overdue}</div>
<div className="text-red-600"></div>
</div>
<div className="rounded bg-gray-50 px-2 py-1.5 text-center">
<div className="font-bold text-gray-700">{stats.total}</div>
<div className="text-gray-600"></div>
</div>
</div>
{/* 필터 */}
<div className="flex gap-2">
{(["all", "scheduled", "in_progress", "overdue"] as const).map((f) => (
<button
key={f}
onClick={() => setFilter(f)}
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
}`}
>
{f === "all" ? "전체" : f === "scheduled" ? "예정" : f === "in_progress" ? "진행중" : "지연"}
</button>
))}
</div>
</div>
{/* 일정 리스트 */}
<div className="flex-1 overflow-y-auto p-4">
{filteredSchedules.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="text-center">
<div className="mb-2 text-4xl">📅</div>
<div> </div>
</div>
</div>
) : (
<div className="space-y-3">
{filteredSchedules.map((schedule) => (
<div
key={schedule.id}
className={`group rounded-lg border-2 bg-white p-4 shadow-sm transition-all hover:shadow-md ${
schedule.status === "overdue" ? "border-red-300" : "border-gray-200"
}`}
>
<div className="mb-2 flex items-start justify-between">
<div className="flex items-center gap-2">
<span className="text-2xl">{getMaintenanceIcon(schedule.maintenanceType)}</span>
<div>
<div className="font-bold text-gray-800">{schedule.vehicleNumber}</div>
<div className="text-xs text-gray-600">{schedule.vehicleType}</div>
</div>
</div>
{getStatusBadge(schedule.status)}
</div>
<div className="mb-3 rounded bg-gray-50 p-2">
<div className="text-sm font-medium text-gray-700">{schedule.maintenanceType}</div>
{schedule.notes && <div className="mt-1 text-xs text-gray-600">{schedule.notes}</div>}
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex items-center gap-1 text-gray-600">
<Calendar className="h-3 w-3" />
{new Date(schedule.scheduledDate).toLocaleDateString()}
</div>
<div
className={`flex items-center gap-1 font-medium ${
schedule.status === "overdue" ? "text-red-600" : "text-blue-600"
}`}
>
<Clock className="h-3 w-3" />
{getDaysUntil(schedule.scheduledDate)}
</div>
{schedule.estimatedCost && (
<div className="col-span-2 font-bold text-primary">
: {schedule.estimatedCost.toLocaleString()}
</div>
)}
</div>
{/* 액션 버튼 */}
{schedule.status === "scheduled" && (
<div className="mt-3 flex gap-2">
<button className="flex-1 rounded bg-blue-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-600">
</button>
<button className="flex-1 rounded bg-gray-200 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-300">
</button>
</div>
)}
{schedule.status === "in_progress" && (
<div className="mt-3">
<button className="w-full rounded bg-green-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-green-600">
<Check className="mr-1 inline h-3 w-3" />
</button>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
}