mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 06:35:42 +00:00
243 lines
8.0 KiB
TypeScript
243 lines
8.0 KiB
TypeScript
import { useState, type ReactNode } from "react";
|
|
import { FileUp, Hash } from "lucide-react";
|
|
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
|
|
import { bookings } from "../bookings/bookings.mock";
|
|
import { consignments } from "../consignments/consignments.mock";
|
|
import { customers } from "../customers/customers.mock";
|
|
import type {
|
|
DocumentLinkType,
|
|
DocumentStatus,
|
|
DocumentType,
|
|
} from "./documents.mock";
|
|
|
|
export interface DocumentFormData {
|
|
name?: string;
|
|
type?: DocumentType;
|
|
status?: DocumentStatus;
|
|
linkedType?: DocumentLinkType;
|
|
linkedReference?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface NewDocumentPageProps {
|
|
mode?: "create" | "edit";
|
|
document?: DocumentFormData;
|
|
children?: ReactNode;
|
|
}
|
|
|
|
const selectClass =
|
|
"flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700 shadow-xs outline-none transition hover:border-slate-300 focus:border-[#10B981]/50 focus:ring-2 focus:ring-[#10B981]/20";
|
|
|
|
export default function NewDocumentPage({
|
|
mode = "create",
|
|
document,
|
|
children,
|
|
}: NewDocumentPageProps = {}) {
|
|
const isEdit = mode === "edit";
|
|
const title = isEdit ? "Edit Document" : "Upload Document";
|
|
const description = isEdit
|
|
? "Update document metadata."
|
|
: "Upload a freight document and link it to a booking, consignment, or invoice.";
|
|
const submitLabel = isEdit ? "Save Changes" : "Upload";
|
|
|
|
const [linkedType, setLinkedType] = useState<DocumentLinkType>(
|
|
document?.linkedType ?? "Booking",
|
|
);
|
|
const [fileName, setFileName] = useState<string>("");
|
|
|
|
const referenceOptions = (() => {
|
|
if (linkedType === "Booking") {
|
|
return bookings.map((b) => ({
|
|
value: b.reference,
|
|
label: `${b.reference} — ${b.customer}`,
|
|
}));
|
|
}
|
|
if (linkedType === "Consignment") {
|
|
return consignments.map((c) => ({
|
|
value: c.trackingNumber,
|
|
label: `${c.trackingNumber} — ${c.customer}`,
|
|
}));
|
|
}
|
|
if (linkedType === "Customer") {
|
|
return customers.map((c) => ({
|
|
value: c.company,
|
|
label: c.company,
|
|
}));
|
|
}
|
|
return [] as Array<{ value: string; label: string }>;
|
|
})();
|
|
|
|
return (
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
{children ?? <Button>{isEdit ? "Edit" : "Upload Document"}</Button>}
|
|
</DialogTrigger>
|
|
|
|
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-3xl rounded-3xl">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl font-bold">{title}</DialogTitle>
|
|
<DialogDescription>{description}</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="grid gap-5 py-4 md:grid-cols-2">
|
|
{/* File picker — drop zone */}
|
|
{!isEdit ? (
|
|
<div className="md:col-span-2">
|
|
<Label>File</Label>
|
|
<label
|
|
htmlFor="document-file-input"
|
|
className="mt-1 flex cursor-pointer flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-slate-200 bg-[#10B981]/5 p-8 text-center transition hover:border-[#10B981]/40 hover:bg-[#10B981]/10"
|
|
>
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#10B981] text-white">
|
|
<FileUp className="h-6 w-6" />
|
|
</div>
|
|
<p className="text-sm font-medium text-slate-900">
|
|
{fileName || "Click to choose a file or drag it here"}
|
|
</p>
|
|
<p className="text-xs text-slate-500">
|
|
PDF, DOCX, XLSX, PNG, JPG · max 25 MB
|
|
</p>
|
|
<input
|
|
id="document-file-input"
|
|
type="file"
|
|
className="hidden"
|
|
accept=".pdf,.docx,.xlsx,.png,.jpg,.jpeg"
|
|
onChange={(e) =>
|
|
setFileName(e.target.files?.[0]?.name ?? "")
|
|
}
|
|
/>
|
|
</label>
|
|
</div>
|
|
) : null}
|
|
|
|
{/* Document Name */}
|
|
<div className="space-y-2">
|
|
<Label>Document Name *</Label>
|
|
<div className="relative">
|
|
<Hash className="absolute left-3 top-3 h-4 w-4 text-slate-400" />
|
|
<Input
|
|
defaultValue={document?.name ?? ""}
|
|
placeholder="e.g. bill-of-lading-bk-026003.pdf"
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Document Type */}
|
|
<div className="space-y-2">
|
|
<Label>Document Type *</Label>
|
|
<select
|
|
defaultValue={document?.type ?? "Bill of Lading"}
|
|
className={selectClass}
|
|
>
|
|
<option>Bill of Lading</option>
|
|
<option>Commercial Invoice</option>
|
|
<option>Packing List</option>
|
|
<option>Customs Declaration</option>
|
|
<option>Certificate of Origin</option>
|
|
<option>Insurance Certificate</option>
|
|
<option>Delivery Receipt</option>
|
|
<option>Proof of Delivery</option>
|
|
<option>Contract</option>
|
|
<option>Other</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Linked To */}
|
|
<div className="space-y-2">
|
|
<Label>Linked To</Label>
|
|
<select
|
|
value={linkedType}
|
|
onChange={(e) =>
|
|
setLinkedType(e.target.value as DocumentLinkType)
|
|
}
|
|
className={selectClass}
|
|
>
|
|
<option>Booking</option>
|
|
<option>Consignment</option>
|
|
<option>Shipment</option>
|
|
<option>Customer</option>
|
|
<option>Invoice</option>
|
|
<option>None</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Reference */}
|
|
<div className="space-y-2">
|
|
<Label>Reference</Label>
|
|
{referenceOptions.length > 0 ? (
|
|
<select
|
|
defaultValue={document?.linkedReference ?? ""}
|
|
className={selectClass}
|
|
>
|
|
<option value="" disabled>
|
|
Select {linkedType.toLowerCase()}
|
|
</option>
|
|
{referenceOptions.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<Input
|
|
defaultValue={document?.linkedReference ?? ""}
|
|
placeholder={
|
|
linkedType === "None"
|
|
? "Not linked"
|
|
: `Enter ${linkedType.toLowerCase()} reference`
|
|
}
|
|
disabled={linkedType === "None"}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Status */}
|
|
<div className="space-y-2">
|
|
<Label>Status</Label>
|
|
<select
|
|
defaultValue={document?.status ?? "Draft"}
|
|
className={selectClass}
|
|
>
|
|
<option>Draft</option>
|
|
<option>Pending Review</option>
|
|
<option>Approved</option>
|
|
<option>Rejected</option>
|
|
<option>Expired</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Notes */}
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label>Notes</Label>
|
|
<Textarea
|
|
defaultValue={document?.notes ?? ""}
|
|
placeholder="Any extra context for this document..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3">
|
|
<Button variant="outline">Cancel</Button>
|
|
<Button className="bg-[#10B981] text-white hover:bg-[#10B981]/90">
|
|
{submitLabel}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|