132 lines
5.5 KiB
TypeScript
132 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import type { OrderWithDetails, OrderStatus } from "@/lib/types";
|
|
import { StatusBadge } from "./StatusBadge";
|
|
|
|
export function OrderTable({ orders }: { orders: OrderWithDetails[] }) {
|
|
const [search, setSearch] = useState("");
|
|
const [statusFilter, setStatusFilter] = useState<OrderStatus | "">("");
|
|
|
|
const filtered = orders.filter((order) => {
|
|
const matchesSearch =
|
|
!search ||
|
|
order.title.toLowerCase().includes(search.toLowerCase()) ||
|
|
order.customers.some((oc) =>
|
|
oc.customer.name.toLowerCase().includes(search.toLowerCase())
|
|
);
|
|
const matchesStatus = !statusFilter || order.status === statusFilter;
|
|
return matchesSearch && matchesStatus;
|
|
});
|
|
|
|
const getTotal = (order: OrderWithDetails) =>
|
|
order.items.reduce((sum, item) => sum + item.finalPrice, 0);
|
|
|
|
return (
|
|
<div>
|
|
{/* Filters */}
|
|
<div className="flex flex-col sm:flex-row gap-3 mb-4">
|
|
<input
|
|
type="text"
|
|
placeholder="Search orders..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="flex-1 border-2 border-border rounded-xl px-4 py-2.5 text-sm bg-white focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none"
|
|
/>
|
|
<select
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value as OrderStatus | "")}
|
|
className="border-2 border-border rounded-xl px-4 py-2.5 text-sm bg-white focus:border-accent outline-none"
|
|
>
|
|
<option value="">All Statuses</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="purchased">Purchased</option>
|
|
<option value="delivered">Delivered</option>
|
|
<option value="closed">Closed</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Mobile cards */}
|
|
<div className="sm:hidden flex flex-col gap-3">
|
|
{filtered.length === 0 ? (
|
|
<div className="px-4 py-8 text-center text-muted border-2 border-border rounded-2xl">
|
|
No orders found
|
|
</div>
|
|
) : (
|
|
filtered.map((order) => (
|
|
<Link
|
|
key={order.id}
|
|
href={`/orders/${order.id}`}
|
|
className="block border-2 border-border rounded-2xl p-4 hover:bg-surface/50 transition-colors"
|
|
>
|
|
<div className="flex items-start justify-between gap-2 mb-2">
|
|
<h3 className="font-bold text-fg text-sm truncate flex-1">{order.title}</h3>
|
|
<StatusBadge status={order.status} />
|
|
</div>
|
|
<div className="flex items-center justify-between text-xs text-muted">
|
|
<span>{order.items.length} item{order.items.length !== 1 ? "s" : ""}</span>
|
|
<span className="font-mono font-bold text-fg">${getTotal(order).toFixed(2)}</span>
|
|
</div>
|
|
{order.customers.length > 0 && (
|
|
<p className="text-xs text-muted mt-1 truncate">
|
|
{order.customers.map((oc) => oc.customer.name).join(", ")}
|
|
</p>
|
|
)}
|
|
</Link>
|
|
))
|
|
)}
|
|
</div>
|
|
|
|
{/* Desktop table */}
|
|
<div className="hidden sm:block border-2 border-border rounded-2xl overflow-hidden">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="bg-surface border-b-2 border-border">
|
|
<th className="text-left px-4 py-3 text-sm font-medium text-muted">Title</th>
|
|
<th className="text-left px-4 py-3 text-sm font-medium text-muted">Status</th>
|
|
<th className="text-left px-4 py-3 text-sm font-medium text-muted hidden md:table-cell">Customers</th>
|
|
<th className="text-right px-4 py-3 text-sm font-medium text-muted">Items</th>
|
|
<th className="text-right px-4 py-3 text-sm font-medium text-muted">Total</th>
|
|
<th className="text-right px-4 py-3 text-sm font-medium text-muted hidden lg:table-cell">Updated</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filtered.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={6} className="px-4 py-8 text-center text-muted">
|
|
No orders found
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
filtered.map((order) => (
|
|
<tr
|
|
key={order.id}
|
|
className="border-b border-border last:border-b-0 hover:bg-surface/50 transition-colors"
|
|
>
|
|
<td className="px-4 py-3">
|
|
<Link href={`/orders/${order.id}`} className="font-medium text-fg hover:text-accent transition-colors">
|
|
{order.title}
|
|
</Link>
|
|
</td>
|
|
<td className="px-4 py-3">
|
|
<StatusBadge status={order.status} />
|
|
</td>
|
|
<td className="px-4 py-3 text-sm text-muted hidden md:table-cell">
|
|
{order.customers.map((oc) => oc.customer.name).join(", ") || "\u2014"}
|
|
</td>
|
|
<td className="px-4 py-3 text-right font-mono text-sm">{order.items.length}</td>
|
|
<td className="px-4 py-3 text-right font-mono text-sm font-medium">${getTotal(order).toFixed(2)}</td>
|
|
<td className="px-4 py-3 text-right text-sm text-muted hidden lg:table-cell font-mono">
|
|
{new Date(order.updatedAt).toLocaleDateString()}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|