import { useMemo, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
ActionIcon,
Badge,
Button,
Card,
Center,
Container,
Group,
Loader,
SimpleGrid,
Stack,
Table,
Tabs,
Text,
Timeline,
Title,
} from "@mantine/core";
import { SmartFileInput } from "@edr/ui-common";
import type { IFileUploadSetting } from "@edr/types/freight";
import {
ArrowLeft,
Download,
Eye,
FileText,
History,
Route,
ShieldCheck,
Trash2,
Upload,
Truck,
User,
} from "lucide-react";
import { driversService } from "@/services/drivers.service";
import { vehiclesService } from "@/services/vehicles.service";
import { fleetHistoryService, type FleetHistoryEvent } from "@/services/fleet-history.service";
import { fileUploadSettingsService } from "@/services/fileUploadSettings.service";
import {
downloadBookingFile,
fetchViewableFile,
} from "@/services/files.service";
import { useToast } from "@/hooks/use-toast";
const fmtDate = (iso?: string | null) => {
if (!iso) return "—";
const d = new Date(iso);
return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString();
};
const fmtDateTime = (iso?: string | null) => {
if (!iso) return "—";
const d = new Date(iso);
return Number.isNaN(d.getTime()) ? "—" : d.toLocaleString();
};
const meta = (e: FleetHistoryEvent, k: string) => {
const v = e.metadata?.[k];
return typeof v === "string" && v ? v : null;
};
const InfoRow = ({ label, value }: { label: string; value: React.ReactNode }) => (
{label}
{value}
);
const Loading = () => (
);
const fmtSize = (bytes: number) => {
if (!bytes) return "—";
const kb = bytes / 1024;
return kb < 1024 ? `${kb.toFixed(0)} KB` : `${(kb / 1024).toFixed(1)} MB`;
};
/** Upload-area setting code configured on the File Settings page. */
const DRIVER_DOCS_CODE = "driver_docs";
// Field key the FALLBACK setting is keyed on (used only when the "driver_docs"
// upload area hasn't been configured in File Settings yet).
const DRIVER_DOCS_KEY = "driver_docs";
/** Fallback single-field setting so the dropzone still works before an admin
* configures the "driver_docs" area in File Settings. */
const DRIVER_DOCS_FALLBACK: IFileUploadSetting = {
id: "driver-docs-setting",
createdAt: "",
updatedAt: "",
deletedAt: null,
code: "driver_docs",
label: "Driver documents",
description: null,
entity: "other",
fields: [
{
id: "driver-docs-field",
createdAt: "",
updatedAt: "",
deletedAt: null,
settingId: "driver-docs-setting",
fileKey: DRIVER_DOCS_KEY,
fileLabel: "Upload driver document(s)",
helpText: "License, national ID, contracts, training certificates, etc.",
isRequired: false,
isMultiple: true,
maxFiles: 20,
allowedExtensions: ["pdf", "png", "jpg", "jpeg", "doc", "docx"],
maxSizeMb: 10,
order: 1,
},
],
};
/** Driver documents upload + view area (files stored under code "driver_docs"). */
const DriverDocuments = ({ driverId }: { driverId: string }) => {
const { toast } = useToast();
const qc = useQueryClient();
// Files selected per configured field key (SmartFileInput is multi-field).
const [selectedMap, setSelectedMap] = useState>({});
const selectedFiles = Object.values(selectedMap).flatMap((v) =>
Array.isArray(v) ? v : v ? [v] : [],
);
// Upload-area configuration from the File Settings page (code "driver_docs").
// Falls back to a default field until an admin configures it there.
const { data: setting } = useQuery({
queryKey: ["file-upload-setting", DRIVER_DOCS_CODE],
queryFn: () => fileUploadSettingsService.getByCode(DRIVER_DOCS_CODE),
retry: false,
});
const activeSetting = setting ?? DRIVER_DOCS_FALLBACK;
const { data: docs = [], isLoading } = useQuery({
queryKey: ["driver", driverId, "documents"],
queryFn: () => driversService.listDocuments(driverId).then((r) => r.data ?? []),
enabled: Boolean(driverId),
});
const uploadMutation = useMutation({
mutationFn: (files: File[]) => driversService.uploadDocuments(driverId, files),
onSuccess: () => {
toast({ title: "Documents uploaded" });
void qc.invalidateQueries({ queryKey: ["driver", driverId, "documents"] });
},
onError: (err: unknown) => {
const description =
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
"Upload failed";
toast({ title: "Upload failed", description, variant: "destructive" });
},
});
const removeMutation = useMutation({
mutationFn: (fileId: string) => driversService.removeDocument(driverId, fileId),
onSuccess: () => {
toast({ title: "Document deleted" });
void qc.invalidateQueries({ queryKey: ["driver", driverId, "documents"] });
},
onError: () => toast({ title: "Delete failed", variant: "destructive" }),
});
return (
{activeSetting.label ?? "Upload documents"}
}
loading={uploadMutation.isPending}
disabled={selectedFiles.length === 0}
onClick={() =>
uploadMutation.mutate(selectedFiles, { onSuccess: () => setSelectedMap({}) })
}
>
Upload {selectedFiles.length > 0 ? `(${selectedFiles.length})` : ""}
Uploaded documents ({docs.length})
{isLoading ? (
) : docs.length === 0 ? (
No documents uploaded yet.
) : (
Name
Size
Uploaded
Actions
{docs.map((doc) => (
{doc.name}
{fmtSize(doc.size)}
{fmtDate(doc.createdAt)}
void fetchViewableFile(doc.id, doc.name).then((f) =>
window.open(f.url, "_blank"),
)
}
>
void downloadBookingFile(doc.id, doc.name)}
>
removeMutation.mutate(doc.id)}
>
))}
)}
);
};
const DriverDetailPage = () => {
const { id = "" } = useParams<{ id: string }>();
const navigate = useNavigate();
const { data: driver, isLoading } = useQuery({
queryKey: ["driver", id],
queryFn: () => driversService.getById(id).then((r) => r.data),
enabled: Boolean(id),
});
const name = driver ? `${driver.firstName ?? ""} ${driver.lastName ?? ""}`.trim() : "";
const licenseExpired =
driver?.licenseExpiryDate && new Date(driver.licenseExpiryDate) < new Date();
return (
navigate("/dashboard/drivers")} aria-label="Back">
{name || "Driver"}
{driver && (
{driver.status}
{driver.faydaVerified && (
}>
Fayda verified
)}
{licenseExpired && (
License expired
)}
{driver.licenseNumber}
)}
{isLoading ? (
) : !driver ? (
Driver not found.
) : (
}>Overview
}>Vehicles
}>History
}>Trips
}>Documents
{fmtDate(driver.licenseExpiryDate)}
}
/>
)}
);
};
const useDriverHistory = (driverId: string) =>
useQuery({
queryKey: ["driver-history", driverId],
queryFn: () => fleetHistoryService.driver(driverId),
});
/** id → "code · plate" map so events without a stored plate still show a name. */
const useVehicleMap = () => {
const { data } = useQuery({
queryKey: ["vehicles-all"],
queryFn: () => vehiclesService.getAll({}).then((r) => r.data),
});
return useMemo(() => {
const m = new Map();
for (const v of data ?? []) {
m.set(v.id, [v.code, v.plateNumber].filter(Boolean).join(" · ") || v.id);
}
return m;
}, [data]);
};
const VehiclesTab = ({ driverId }: { driverId: string }) => {
const { data = [], isLoading } = useDriverHistory(driverId);
const vmap = useVehicleMap();
const rows = data
.filter((e) => e.eventType === "DRIVER_ASSIGNED")
.map((e) => ({
id: e.id,
plate: meta(e, "vehiclePlate") || (e.vehicleId ? vmap.get(e.vehicleId) : null) || "Vehicle",
at: e.createdAt,
}));
if (isLoading) return ;
return (
Vehicles driven ({rows.length})
{rows.length === 0 ? (
No vehicle assignments recorded.
) : (
VehicleAssigned
{rows.map((r) => (
{r.plate}
{fmtDateTime(r.at)}
))}
)}
);
};
const HistoryTab = ({ driverId }: { driverId: string }) => {
const { data = [], isLoading } = useDriverHistory(driverId);
if (isLoading) return ;
if (!data.length) return No activity recorded yet.;
return (
{data.map((e) => (
{e.eventType.replaceAll("_", " ")}}>
{(meta(e, "vehiclePlate") || meta(e, "bookingRef") || e.label) && (
{[meta(e, "vehiclePlate"), meta(e, "bookingRef") && `Booking ${meta(e, "bookingRef")}`, e.label]
.filter(Boolean)
.join(" · ")}
)}
{fmtDateTime(e.createdAt)}
))}
);
};
const TripsTab = ({ driverId }: { driverId: string }) => {
const { data = [], isLoading } = useDriverHistory(driverId);
const vmap = useVehicleMap();
const trips = useMemo(
() =>
data
.filter((e) => e.eventType === "MILE_VEHICLE_ASSIGNED")
.map((e) => ({
id: e.id,
mile: meta(e, "mile") === "LAST" ? "Last-mile" : "First-mile",
booking: meta(e, "bookingRef") ?? "—",
vehicle: meta(e, "vehiclePlate") || (e.vehicleId ? vmap.get(e.vehicleId) : null) || "—",
status: e.label ?? "—",
at: e.createdAt,
})),
[data, vmap],
);
if (isLoading) return ;
return (
Trips assigned ({trips.length})
{trips.length === 0 ? (
No trips recorded.
) : (
MileBooking
VehicleStatusWhen
{trips.map((t) => (
{t.mile}
{t.booking}
{t.vehicle}
{t.status}
{fmtDateTime(t.at)}
))}
)}
);
};
export default DriverDetailPage;