Ship the delivery platform UI under modules/delivery with thin app routes, BFF proxy, CRUD for phase 10.0-10.1 APIs, and capability-gated future screens. Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
6.6 KiB
TypeScript
161 lines
6.6 KiB
TypeScript
"use client";
|
||
|
||
import { useParams } from "next/navigation";
|
||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||
import { toast } from "sonner";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { deliveryApi } from "@/modules/delivery/services/delivery-api";
|
||
import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
|
||
import { PageHeader, Card, CardContent, Button, Badge, Tabs, ConfirmDialog } from "@/components/ds";
|
||
import { DeliveryDetailBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
|
||
import { DeliveryStatusChip } from "@/modules/delivery/design-system";
|
||
import { useDeliveryPermissions } from "@/modules/delivery/hooks/useDeliveryPermissions";
|
||
import { useState } from "react";
|
||
|
||
export const DriverDetailPage = function DriverDetail() {
|
||
const params = useParams();
|
||
const driverId = String(params.id);
|
||
const { tenantId } = useTenantId();
|
||
const qc = useQueryClient();
|
||
const perms = useDeliveryPermissions();
|
||
const [tab, setTab] = useState("profile");
|
||
const [lifecycleAction, setLifecycleAction] = useState<string | null>(null);
|
||
|
||
const driverQ = useQuery({
|
||
queryKey: ["delivery", tenantId, "driver", driverId],
|
||
queryFn: () => deliveryApi.drivers.get(tenantId!, driverId),
|
||
enabled: !!tenantId && !!driverId,
|
||
});
|
||
|
||
const lifecycleQ = useQuery({
|
||
queryKey: ["delivery", tenantId, "driver-lifecycle", driverId],
|
||
queryFn: () => deliveryApi.drivers.lifecycle(tenantId!, driverId),
|
||
enabled: !!tenantId && !!driverId && tab === "lifecycle",
|
||
});
|
||
|
||
const credsQ = useQuery({
|
||
queryKey: ["delivery", tenantId, "driver-creds", driverId],
|
||
queryFn: () => deliveryApi.drivers.credentials.list(tenantId!, driverId),
|
||
enabled: !!tenantId && !!driverId && tab === "credentials",
|
||
});
|
||
|
||
const docsQ = useQuery({
|
||
queryKey: ["delivery", tenantId, "driver-docs", driverId],
|
||
queryFn: () => deliveryApi.drivers.documents.list(tenantId!, driverId),
|
||
enabled: !!tenantId && !!driverId && tab === "documents",
|
||
});
|
||
|
||
const lifecycleM = useMutation({
|
||
mutationFn: (action: string) => {
|
||
const body = { version: driverQ.data!.version as number };
|
||
const drivers = deliveryApi.drivers;
|
||
switch (action) {
|
||
case "activate":
|
||
return drivers.activate(tenantId!, driverId, body);
|
||
case "suspend":
|
||
return drivers.suspend(tenantId!, driverId, body);
|
||
case "resume":
|
||
return drivers.resume(tenantId!, driverId, body);
|
||
case "deactivate":
|
||
return drivers.deactivate(tenantId!, driverId, body);
|
||
case "block":
|
||
return drivers.block(tenantId!, driverId, body);
|
||
case "unblock":
|
||
return drivers.unblock(tenantId!, driverId, body);
|
||
case "archive":
|
||
return drivers.archive(tenantId!, driverId, body);
|
||
default:
|
||
throw new Error("عملیات نامعتبر");
|
||
}
|
||
},
|
||
onSuccess: () => {
|
||
toast.success("وضعیت بهروز شد");
|
||
setLifecycleAction(null);
|
||
qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver", driverId] });
|
||
qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver-lifecycle", driverId] });
|
||
},
|
||
onError: (e: Error) => toast.error(e.message),
|
||
});
|
||
|
||
if (!tenantId || driverQ.isLoading) return <DeliveryPageLoader />;
|
||
if (driverQ.error) return <DeliveryPageError error={driverQ.error} onRetry={() => driverQ.refetch()} />;
|
||
|
||
const d = driverQ.data!;
|
||
const lifecycleActions = ["activate", "suspend", "resume", "deactivate", "block", "unblock", "archive"];
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<DeliveryDetailBreadcrumbs section="رانندگان" sectionHref="/delivery/drivers" current={String(d.display_name)} />
|
||
<PageHeader
|
||
title={String(d.display_name)}
|
||
description={`کد: ${d.code} — ${d.mobile ?? ""}`}
|
||
actions={<DeliveryStatusChip status={String(d.status)} />}
|
||
/>
|
||
<Tabs
|
||
items={[
|
||
{ id: "profile", label: "پروفایل" },
|
||
{ id: "lifecycle", label: "چرخه حیات" },
|
||
{ id: "credentials", label: "مدارک" },
|
||
{ id: "documents", label: "اسناد" },
|
||
]}
|
||
value={tab}
|
||
onChange={setTab}
|
||
/>
|
||
{tab === "profile" ? (
|
||
<Card><CardContent className="grid gap-2 p-4 text-sm sm:grid-cols-2">
|
||
<p><span className="text-[var(--muted)]">ایمیل:</span> {String(d.email ?? "—")}</p>
|
||
<p><span className="text-[var(--muted)]">نسخه:</span> {String(d.version)}</p>
|
||
<p><span className="text-[var(--muted)]">ایجاد:</span> {new Date(String(d.created_at)).toLocaleString("fa-IR")}</p>
|
||
{d.notes ? <p className="sm:col-span-2">{String(d.notes)}</p> : null}
|
||
</CardContent></Card>
|
||
) : null}
|
||
{tab === "lifecycle" ? (
|
||
<div className="space-y-4">
|
||
<div className="flex flex-wrap gap-2">
|
||
{lifecycleActions.map((a) => (
|
||
<Button key={a} variant="outline" size="sm" onClick={() => setLifecycleAction(a)} disabled={!perms.can("delivery.drivers." + a)}>
|
||
{a}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
{(lifecycleQ.data ?? []).map((ev) => (
|
||
<Card key={String(ev.id)}><CardContent className="p-3 text-sm">
|
||
{String(ev.action)}: {String(ev.from_status)} → {String(ev.to_status)}
|
||
<span className="mr-2 text-[var(--muted)]">{new Date(String(ev.created_at)).toLocaleString("fa-IR")}</span>
|
||
</CardContent></Card>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
{tab === "credentials" ? (
|
||
<div className="space-y-2">
|
||
{(credsQ.data ?? []).map((c) => (
|
||
<Card key={String(c.id)}><CardContent className="p-3 text-sm">
|
||
{String(c.kind)} — {String(c.number)}
|
||
{c.is_verified ? <Badge tone="success" className="mr-2">تأیید شده</Badge> : null}
|
||
</CardContent></Card>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
{tab === "documents" ? (
|
||
<div className="space-y-2">
|
||
{(docsQ.data ?? []).map((doc) => (
|
||
<Card key={String(doc.id)}><CardContent className="p-3 text-sm">
|
||
{String(doc.title)} — {String(doc.kind)}
|
||
</CardContent></Card>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
<ConfirmDialog
|
||
open={!!lifecycleAction}
|
||
onClose={() => setLifecycleAction(null)}
|
||
title="تغییر وضعیت"
|
||
description={`آیا از اجرای ${lifecycleAction} اطمینان دارید؟`}
|
||
onConfirm={() => lifecycleAction && lifecycleM.mutate(lifecycleAction)}
|
||
loading={lifecycleM.isPending}
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default DriverDetailPage;
|