mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 01:55:41 +00:00
203 lines
6.1 KiB
TypeScript
203 lines
6.1 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { Calendar, DollarSign, 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 { customers } from "../customers/customers.mock";
|
|
import { bookings } from "../bookings/bookings.mock";
|
|
import type { Currency, InvoiceStatus } from "./invoices.mock";
|
|
|
|
export interface InvoiceFormData {
|
|
number?: string;
|
|
customerId?: number;
|
|
bookingReference?: string;
|
|
amount?: number;
|
|
currency?: Currency;
|
|
status?: InvoiceStatus;
|
|
issueDate?: string;
|
|
dueDate?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface NewInvoicePageProps {
|
|
mode?: "create" | "edit";
|
|
invoice?: InvoiceFormData;
|
|
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 NewInvoicePage({
|
|
mode = "create",
|
|
invoice,
|
|
children,
|
|
}: NewInvoicePageProps = {}) {
|
|
const isEdit = mode === "edit";
|
|
const title = isEdit ? "Edit Invoice" : "New Invoice";
|
|
const description = isEdit
|
|
? "Update invoice details."
|
|
: "Create a new invoice for a customer booking.";
|
|
const submitLabel = isEdit ? "Save Changes" : "Create Invoice";
|
|
|
|
return (
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
{children ?? <Button>{isEdit ? "Edit" : "New Invoice"}</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">
|
|
{/* Invoice Number */}
|
|
<div className="space-y-2">
|
|
<Label>Invoice Number *</Label>
|
|
<div className="relative">
|
|
<Hash className="absolute left-3 top-3 h-4 w-4 text-slate-400" />
|
|
<Input
|
|
defaultValue={invoice?.number ?? ""}
|
|
placeholder="e.g. INV-2026-0001"
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status */}
|
|
<div className="space-y-2">
|
|
<Label>Status</Label>
|
|
<select
|
|
defaultValue={invoice?.status ?? "Draft"}
|
|
className={selectClass}
|
|
>
|
|
<option>Draft</option>
|
|
<option>Sent</option>
|
|
<option>Paid</option>
|
|
<option>Overdue</option>
|
|
<option>Cancelled</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Customer */}
|
|
<div className="space-y-2">
|
|
<Label>Customer *</Label>
|
|
<select
|
|
defaultValue={invoice?.customerId ?? ""}
|
|
className={selectClass}
|
|
>
|
|
<option value="" disabled>
|
|
Select customer
|
|
</option>
|
|
{customers.map((c) => (
|
|
<option key={c.id} value={c.id}>
|
|
{c.company}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Booking */}
|
|
<div className="space-y-2">
|
|
<Label>Booking Reference</Label>
|
|
<select
|
|
defaultValue={invoice?.bookingReference ?? ""}
|
|
className={selectClass}
|
|
>
|
|
<option value="">No linked booking</option>
|
|
{bookings.map((b) => (
|
|
<option key={b.id} value={b.reference}>
|
|
{b.reference} — {b.customer}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Amount */}
|
|
<div className="space-y-2">
|
|
<Label>Amount *</Label>
|
|
<div className="relative">
|
|
<DollarSign className="absolute left-3 top-3 h-4 w-4 text-slate-400" />
|
|
<Input
|
|
type="number"
|
|
min={0}
|
|
step="0.01"
|
|
defaultValue={invoice?.amount ?? 0}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Currency */}
|
|
<div className="space-y-2">
|
|
<Label>Currency</Label>
|
|
<select
|
|
defaultValue={invoice?.currency ?? "USD"}
|
|
className={selectClass}
|
|
>
|
|
<option>USD</option>
|
|
<option>ETB</option>
|
|
<option>DJF</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Issue Date */}
|
|
<div className="space-y-2">
|
|
<Label>Issue Date *</Label>
|
|
<div className="relative">
|
|
<Calendar className="absolute left-3 top-3 h-4 w-4 text-slate-400" />
|
|
<Input
|
|
type="date"
|
|
defaultValue={invoice?.issueDate ?? ""}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Due Date */}
|
|
<div className="space-y-2">
|
|
<Label>Due Date *</Label>
|
|
<div className="relative">
|
|
<Calendar className="absolute left-3 top-3 h-4 w-4 text-slate-400" />
|
|
<Input
|
|
type="date"
|
|
defaultValue={invoice?.dueDate ?? ""}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Notes */}
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label>Notes</Label>
|
|
<Textarea
|
|
defaultValue={invoice?.notes ?? ""}
|
|
placeholder="Payment terms, references, etc."
|
|
/>
|
|
</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>
|
|
);
|
|
}
|