356 lines
12 KiB
TypeScript
356 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import { CheckCircle, Clock, Search, Truck, XCircle } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type OrderStatus = "pending" | "shipped" | "delivered" | "cancelled";
|
|
|
|
interface OrderItem {
|
|
name: string;
|
|
variant: string;
|
|
image: string;
|
|
price: number;
|
|
quantity: number;
|
|
status: OrderStatus;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
statIRRate: string;
|
|
}
|
|
|
|
interface Order {
|
|
id: string;
|
|
orderNumber: string;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
orderDate: string;
|
|
total: number;
|
|
items: OrderItem[];
|
|
}
|
|
|
|
const DEFAULT_ORDERS: Order[] = [
|
|
{
|
|
id: "1",
|
|
orderNumber: "47582",
|
|
orderDate: "2025-01-15",
|
|
total: 487000.0,
|
|
items: [
|
|
{
|
|
name: "صندلی راحتی مینسنتری",
|
|
variant: "زرد خردلی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Leather-Sofa-1.png",
|
|
price: 289000.0,
|
|
quantity: 1,
|
|
status: "delivered",
|
|
statIRRate: "2025-01-22",
|
|
},
|
|
{
|
|
name: "کوسن طرح گل",
|
|
variant: "چاپ گلدار تیره",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Loveseat-1.png",
|
|
price: 490000.0,
|
|
quantity: 2,
|
|
status: "delivered",
|
|
statIRRate: "2025-01-22",
|
|
},
|
|
{
|
|
name: "گلدان گیاه آپارتمانی",
|
|
variant: "سبز متوسط",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Wooden-Sideboard-1.png",
|
|
price: 990000.0,
|
|
quantity: 1,
|
|
status: "delivered",
|
|
statIRRate: "2025-01-18",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "2",
|
|
orderNumber: "46321",
|
|
orderDate: "2025-01-10",
|
|
total: 329000.0,
|
|
items: [
|
|
{
|
|
name: "میز جلو مبلی چوبی",
|
|
variant: "بلوط طبیعی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Wooden-Cabinet-1.png",
|
|
price: 249000.0,
|
|
quantity: 1,
|
|
status: "delivered",
|
|
statIRRate: "2025-01-13",
|
|
},
|
|
{
|
|
name: "ست تابلو دیواری بافته",
|
|
variant: "طرح هندسی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-1.png",
|
|
price: 800000.0,
|
|
quantity: 1,
|
|
status: "delivered",
|
|
statIRRate: "2025-01-13",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "3",
|
|
orderNumber: "45890",
|
|
orderDate: "2024-12-28",
|
|
total: 159000.0,
|
|
items: [
|
|
{
|
|
name: "چراغ رومیزی سرامیکی",
|
|
variant: "سفید مات",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-2.png",
|
|
price: 159000.0,
|
|
quantity: 1,
|
|
status: "shipped",
|
|
statIRRate: "2025-01-05",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const formatCurrency = (value: number) =>
|
|
`${value.toLocaleString("fa-IR")} ریال`;
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat("fa-IR", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
|
|
const formatOrderDate = (isoDate: string) =>
|
|
dateFormatter.format(new Date(isoDate + "T12:00:00"));
|
|
|
|
const statusConfig: Record<
|
|
OrderStatus,
|
|
{ label: string; className: string; icon: typeof CheckCircle }
|
|
> = {
|
|
pending: {
|
|
label: "در حال پردازش،",
|
|
className: "text-slate-600",
|
|
icon: Clock,
|
|
},
|
|
shipped: {
|
|
label: "ارسالشده در",
|
|
className: "text-blue-600",
|
|
icon: Truck,
|
|
},
|
|
delivered: {
|
|
label: "تحویلشده در",
|
|
className: "text-emerald-600",
|
|
icon: CheckCircle,
|
|
},
|
|
cancelled: {
|
|
label: "لغوشده در",
|
|
className: "text-red-600",
|
|
icon: XCircle,
|
|
},
|
|
};
|
|
|
|
interface OrderHistory3Props {
|
|
orders?: Order[];
|
|
className?: string;
|
|
}
|
|
|
|
const OrderHistory3 = ({
|
|
orders = DEFAULT_ORDERS,
|
|
className,
|
|
}: OrderHistory3Props) => {
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
const filteredOrders = orders.filter((order) => {
|
|
if (searchQuery === "") return true;
|
|
const query = searchQuery.toLowerCase();
|
|
return (
|
|
order.orderNumber.includes(query) ||
|
|
order.items.some((item) => item.name.toLowerCase().includes(query))
|
|
);
|
|
});
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("py-16 md:py-24", className)}
|
|
>
|
|
<div className="container max-w-4xl">
|
|
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<h1 className="text-xl font-bold tracking-tight md:text-3xl">
|
|
سفارشهای من
|
|
</h1>
|
|
<p className="mt-1 text-muted-foreground">
|
|
مشاهده تاریخچه و جزئیات پیگیری سفارشها
|
|
</p>
|
|
</div>
|
|
|
|
<div className="relative">
|
|
<Search
|
|
className="absolute top-1/2 start-3 size-4 -translate-y-1/2 text-muted-foreground"
|
|
aria-hidden="true"
|
|
/>
|
|
<Input
|
|
placeholder="جستجو…"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
name="order-history-search"
|
|
inputMode="search"
|
|
autoComplete="off"
|
|
aria-label="جستجوی سفارشها و محصولات"
|
|
className="ps-9 sm:w-56"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
{filteredOrders.map((order) => (
|
|
<Card
|
|
key={order.id}
|
|
className="gap-0 overflow-hidden p-0 shadow-none"
|
|
>
|
|
<CardHeader className="bg-muted/40 px-4 py-3 md:px-6 md:py-4">
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="grid grid-cols-3 gap-4 sm:flex sm:gap-8">
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">
|
|
شماره سفارش
|
|
</p>
|
|
<p className="font-semibold tabular-nums" dir="rtl">
|
|
{order.orderNumber}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">
|
|
تاریخ سفارش
|
|
</p>
|
|
<p className="font-medium">
|
|
{formatOrderDate(order.orderDate)}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">مبلغ کل</p>
|
|
<p className="font-semibold tabular-nums">
|
|
{formatCurrency(order.total)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" size="sm">
|
|
مشاهده فاکتور
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
بایگانی
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent className="p-0">
|
|
{order.items.map((item, index) => {
|
|
const status = statusConfig[item.status];
|
|
const StatusIcon = status.icon;
|
|
|
|
return (
|
|
<div key={index}>
|
|
{index > 0 && <Separator />}
|
|
<div className="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between md:p-6">
|
|
<div className="flex gap-4">
|
|
<div className="size-16 shrink-0 overflow-hidden rounded-lg bg-muted md:size-20">
|
|
<AspectRatio ratio={1}>
|
|
<img
|
|
src={item.image}
|
|
alt={item.name}
|
|
className="size-full object-cover"
|
|
/>
|
|
</AspectRatio>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-medium">{item.name}</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
{item.variant}
|
|
{item.quantity > 1 && (
|
|
<>
|
|
{" "}
|
|
· تعداد:{" "}
|
|
{item.quantity.toLocaleString("fa-IR")}
|
|
</>
|
|
)}
|
|
</p>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="mt-2"
|
|
>
|
|
خرید مجدد
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 sm:text-end">
|
|
<StatusIcon
|
|
className={cn(
|
|
"size-5 shrink-0",
|
|
item.status === "delivered" && "text-emerald-500",
|
|
item.status === "shipped" && "text-blue-500",
|
|
item.status === "pending" && "text-slate-400",
|
|
item.status === "cancelled" && "text-red-500",
|
|
)}
|
|
fill={
|
|
item.status === "delivered"
|
|
? "currentColor"
|
|
: "none"
|
|
}
|
|
strokeWidth={item.status === "delivered" ? 0 : 2}
|
|
aria-hidden="true"
|
|
/>
|
|
<p className="text-sm">
|
|
<span className="text-muted-foreground">
|
|
{status.label}{" "}
|
|
</span>
|
|
<span className="font-medium">
|
|
{formatOrderDate(item.statIRRate)}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
|
|
{filteredOrders.length === 0 && (
|
|
<Card className="shadow-none">
|
|
<CardContent className="flex flex-col items-center justify-center py-16 text-center">
|
|
<div className="mb-4 flex size-16 items-center justify-center rounded-full bg-muted">
|
|
<Search
|
|
className="size-8 text-muted-foreground"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
<h2 className="mb-2 text-lg font-semibold">سفارشی یافت نشد</h2>
|
|
<p className="max-w-sm text-sm text-muted-foreground">
|
|
سفارشی با عبارت «{searchQuery}» یافت نشد.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { OrderHistory3 };
|