Files
LogisicsApp_Client/src/Components/Trip/TripDetailModal.tsx

152 lines
6.2 KiB
TypeScript
Raw 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 { useEffect, useState } from "react";
import { Alert, Button, Modal, Spinner } from "../UI";
import { getStoredToken } from "@/src/lib/auth";
import { tripService } from "@/src/services/trip.service";
import { TRIP_STATUS_MAP } from "@/src/types/trip";
import type { Trip } from "@/src/types/trip";
interface TripDetailModalProps {
tripId: string;
onClose: () => void;
}
// ── small helper components ───────────────────────────────────────────────────
// (No shared-UI equivalents — purpose-built layouts, not generic controls.)
function DetailRow({ label, value }: { label: string; value?: string | null }) {
return (
<div style={{
display: "flex", flexDirection: "column", gap: 4,
padding: "0.75rem 0",
borderBottom: "1px solid var(--color-border)",
}}>
<span style={{ fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.15em", color: "var(--color-text-muted)" }}>
{label}
</span>
<span style={{ fontSize: 13, fontWeight: 500, color: value ? "var(--color-text-primary)" : "var(--color-text-hint)" }}>
{value || "—"}
</span>
</div>
);
}
function StatusBadge({ status }: { status: Trip["status"] }) {
const s = TRIP_STATUS_MAP[status];
return (
<span style={{
display: "inline-flex", alignItems: "center", gap: 6,
borderRadius: "var(--radius-full)",
border: `1px solid ${s.border}`,
background: s.bg,
padding: "0.25rem 0.75rem",
fontSize: 12, fontWeight: 600,
color: s.color,
}}>
<span style={{ width: 6, height: 6, borderRadius: "50%", background: s.dot }} />
{s.label}
</span>
);
}
// ── main component ────────────────────────────────────────────────────────────
export function TripDetailModal({ tripId, onClose }: TripDetailModalProps) {
const [trip, setTrip] = useState<Trip | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// fetch trip details on mount
useEffect(() => {
let cancelled = false;
(async () => {
try {
const token = getStoredToken();
const data = await tripService.getById(tripId, token);
if (!cancelled) setTrip(data);
} catch {
if (!cancelled) setError("تعذّر تحميل بيانات الرحلة. يرجى المحاولة لاحقاً.");
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => { cancelled = true; };
}, [tripId]);
// ── helpers ───────────────────────────────────────────────────────────────
const fmt = (iso?: string | null) =>
iso ? new Date(iso).toLocaleDateString("ar-SA", { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit" }) : null;
const cash = (v?: number | string | null) =>
v != null ? `${Number(v).toLocaleString("ar-SA")} ر.س` : null;
// ── render ────────────────────────────────────────────────────────────────
return (
<Modal
open
title={trip?.title ?? "عرض الرحلة"}
subtitle="تفاصيل الرحلة"
onClose={onClose}
size="md"
footer={
<Button type="button" variant="secondary" onClick={onClose}>
إغلاق
</Button>
}
>
{/* loading */}
{loading && (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, padding: "3rem 0", color: "var(--color-text-muted)" }}>
<Spinner size="sm" className="text-blue-600" />
<span style={{ fontSize: 13 }}>جارٍ التحميل</span>
</div>
)}
{/* error */}
{!loading && error && <Alert type="error" message={error} />}
{/* content */}
{!loading && trip && (
<div style={{ display: "flex", flexDirection: "column", gap: 0 }} dir="rtl">
{/* title + trip number + status row */}
<div style={{
display: "flex", flexDirection: "column", gap: 8,
padding: "0 0 1.25rem",
borderBottom: "1px solid var(--color-border)",
marginBottom: "0.25rem",
}}>
<p style={{ fontSize: 16, fontWeight: 700, color: "var(--color-text-primary)", margin: 0 }}>{trip.title}</p>
<p style={{ margin: 0, fontFamily: "var(--font-mono)", fontSize: 12, color: "#2563EB", fontWeight: 600 }}>
{trip.tripNumber}
</p>
<StatusBadge status={trip.status} />
</div>
{/* driver / car / branch */}
<DetailRow label="السائق" value={trip.driver?.name} />
<DetailRow label="السيارة" value={trip.car ? `${trip.car.manufacturer} ${trip.car.model}${trip.car.plateNumber}` : null} />
<DetailRow label="الفرع" value={trip.branch?.name} />
{/* timing */}
<DetailRow label="وقت البدء" value={fmt(trip.startTime)} />
<DetailRow label="وقت الانتهاء" value={fmt(trip.endTime)} />
{/* counts + cash */}
<DetailRow label="المجمّع" value={String(trip.collectedCount)} />
<DetailRow label="المُسلَّم" value={String(trip.deliveredCount)} />
<DetailRow label="المُرتجع" value={String(trip.returnedCount)} />
<DetailRow label="النقد المحصّل" value={cash(trip.totalCashCollected)} />
{/* notes */}
<DetailRow label="ملاحظات" value={trip.notes} />
<DetailRow label="سبب الإنهاء" value={trip.endReason} />
{/* timestamps */}
<DetailRow label="تاريخ الإنشاء" value={fmt(trip.createdAt)} />
<DetailRow label="آخر تحديث" value={fmt(trip.updatedAt)} />
</div>
)}
</Modal>
);
}