TorbatYar/frontend/modules/delivery/features/driverDetail.tsx
Mortezakoohjani 7978970783 feat(delivery): add Torbat Driver frontend module with real API wiring.
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>
2026-07-27 12:02:01 +03:30

161 lines
6.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;