122 lines
5.5 KiB
TypeScript
122 lines
5.5 KiB
TypeScript
import { createFileRoute } from "@tanstack/react-router";
|
|
import { ReceiptText, User, Clock, DollarSign } from "lucide-react";
|
|
import { DetailRow, BackLink, EmptyState } from "#/components/ui";
|
|
import { useGetBillById } from "shared-react";
|
|
|
|
export const Route = createFileRoute("/billing/$id")({
|
|
component: BillDetailsPage,
|
|
staticData: {
|
|
title: "Bill Details",
|
|
subtitle: "Loading...",
|
|
},
|
|
});
|
|
|
|
function BillDetailsPage() {
|
|
const { id } = Route.useParams();
|
|
const billId = Number(id);
|
|
const { data: bill, isLoading } = useGetBillById(billId);
|
|
|
|
if (isLoading) return <div className="text-sm text-slate-600 py-8">Loading bill...</div>;
|
|
if (!bill) {
|
|
return (
|
|
<EmptyState
|
|
icon={ReceiptText}
|
|
title="Bill not found"
|
|
description="The bill you're looking for doesn't exist."
|
|
actionLabel="Back to Billing"
|
|
actionTo="/billing"
|
|
/>
|
|
);
|
|
}
|
|
|
|
const d = new Date(bill.created_at);
|
|
const dateStr = d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
|
|
return (
|
|
<div>
|
|
<BackLink to="/billing" label="Billing" />
|
|
|
|
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-6 mb-5 flex justify-between items-start gap-5 max-w-[960px]">
|
|
<div>
|
|
<div className="text-[22px] font-bold text-blue-600">{bill.bill_no}</div>
|
|
<div className="text-[13px] text-slate-500 mt-1">{dateStr}</div>
|
|
<div className="mt-3 flex items-center gap-2 text-sm text-slate-700">
|
|
<User className="w-4 h-4" />
|
|
{bill.customer_name ? `${bill.customer_name} (${bill.customer_mobile})` : bill.customer_mobile}
|
|
</div>
|
|
</div>
|
|
<div className="text-right shrink-0">
|
|
<div className="text-[28px] font-bold text-emerald-600">₹{bill.total.toFixed(2)}</div>
|
|
<span className="inline-block mt-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-600">Completed</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-5 max-w-[960px] mb-5">
|
|
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-5">
|
|
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-slate-600 uppercase tracking-wider mb-3">
|
|
<Clock className="w-[14px] h-[14px]" />Timings & Info
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-2 text-[13px]">
|
|
<span className="text-slate-500">Generated</span><span className="font-medium">{dateStr}</span>
|
|
<span className="text-slate-500">Items</span><span className="font-medium">{bill.items.length}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-5">
|
|
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-slate-600 uppercase tracking-wider mb-3">
|
|
<DollarSign className="w-[14px] h-[14px]" />Payment Summary
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-2 text-[13px]">
|
|
<span className="text-slate-500">Subtotal</span><span className="font-medium text-right">₹{bill.subtotal.toFixed(2)}</span>
|
|
<span className="text-slate-500">Tax ({bill.tax_rate}%)</span><span className="font-medium text-right">₹{bill.tax.toFixed(2)}</span>
|
|
<span className="text-emerald-600">Discount</span><span className="font-medium text-right text-emerald-600">{bill.discount_percent}%</span>
|
|
<span className="text-slate-900 font-semibold border-t border-slate-200 pt-1.5">Total</span>
|
|
<span className="font-bold text-right text-blue-600 border-t border-slate-200 pt-1.5">₹{bill.total.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-5 max-w-[960px]">
|
|
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-slate-600 uppercase tracking-wider mb-4">
|
|
<ReceiptText className="w-[14px] h-[14px]" />Bill Items
|
|
</h3>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="text-[11px] font-semibold text-slate-600 uppercase tracking-wider border-b border-slate-200">
|
|
<th className="text-left pb-2 px-2.5">Product</th>
|
|
<th className="text-center pb-2 px-2.5">Strips</th>
|
|
<th className="text-center pb-2 px-2.5">Loose</th>
|
|
<th className="text-left pb-2 px-2.5">Price</th>
|
|
<th className="text-right pb-2 px-2.5">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{bill.items.map((item) => (
|
|
<tr key={item.id} className="border-b border-slate-100">
|
|
<td className="py-2.5 px-2.5">
|
|
<div className="font-semibold text-[13px]">{item.product_name}</div>
|
|
{item.brand && <div className="text-xs text-slate-500">{item.brand}</div>}
|
|
</td>
|
|
<td className="py-2.5 px-2.5 text-center">{item.strips}</td>
|
|
<td className="py-2.5 px-2.5 text-center">{item.loose}</td>
|
|
<td className="py-2.5 px-2.5">
|
|
{item.original_price !== item.selling_price ? (
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="text-xs text-slate-400 line-through">₹{item.original_price.toFixed(2)}</span>
|
|
<span className="font-semibold text-emerald-600">₹{item.selling_price.toFixed(2)}</span>
|
|
</div>
|
|
) : (
|
|
<span className="font-semibold">₹{item.selling_price.toFixed(2)}</span>
|
|
)}
|
|
</td>
|
|
<td className="py-2.5 px-2.5 text-right font-semibold">₹{item.total.toFixed(2)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|