361 lines
12 KiB
TypeScript
361 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ChevronRight,
|
|
Package,
|
|
RefreshCcw,
|
|
Search,
|
|
ShoppingBag,
|
|
Truck,
|
|
} from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type OrderStatus =
|
|
| "processing"
|
|
| "shipped"
|
|
| "delivered"
|
|
| "cancelled"
|
|
| "returned";
|
|
|
|
interface OrderHistoryItem {
|
|
id: string;
|
|
orderNumber: string;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
orderDate: string;
|
|
status: OrderStatus;
|
|
total: number;
|
|
itemCount: number;
|
|
thumbnails: string[];
|
|
trackingNumber?: string;
|
|
}
|
|
|
|
const DEFAULT_ORDERS: OrderHistoryItem[] = [
|
|
{
|
|
id: "1",
|
|
orderNumber: "ORD-2024-78432",
|
|
orderDate: "2024-12-14",
|
|
status: "processing",
|
|
total: 289000.0,
|
|
itemCount: 2,
|
|
thumbnails: [
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/stylish-maroon-sneaker.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/black-hoodie-against-light-background.png",
|
|
],
|
|
},
|
|
{
|
|
id: "2",
|
|
orderNumber: "ORD-2024-78401",
|
|
orderDate: "2024-12-10",
|
|
status: "shipped",
|
|
total: 549990.0,
|
|
itemCount: 3,
|
|
thumbnails: [
|
|
"/block-media/shadcnblocks/block/ecommerce/electronics/smart-watch-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/electronics/Sleek-Modern-Laptop-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/electronics/Sleek-Smartphone-Close-Up-1.png",
|
|
],
|
|
trackingNumber: "1Z999AA10123456784",
|
|
},
|
|
{
|
|
id: "3",
|
|
orderNumber: "ORD-2024-78355",
|
|
orderDate: "2024-11-28",
|
|
status: "delivered",
|
|
total: 1249000.0,
|
|
itemCount: 2,
|
|
thumbnails: [
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Leather-Sofa-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-1.png",
|
|
],
|
|
},
|
|
{
|
|
id: "4",
|
|
orderNumber: "ORD-2024-78290",
|
|
orderDate: "2024-11-15",
|
|
status: "cancelled",
|
|
total: 185000.0,
|
|
itemCount: 3,
|
|
thumbnails: [
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Stylish-Round-Sunglasses-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Sunflower-Pendant-Necklace-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Stylish-Pink-Eyeglasses-1.png",
|
|
],
|
|
},
|
|
];
|
|
|
|
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 formatItemCount = (count: number) =>
|
|
`${count.toLocaleString("fa-IR")} قلم`;
|
|
|
|
const statusConfig: Record<
|
|
OrderStatus,
|
|
{ label: string; className: string; icon: typeof Package }
|
|
> = {
|
|
processing: {
|
|
label: "در حال پردازش",
|
|
className: "bg-amber-500/10 text-amber-600 hover:bg-amber-500/10",
|
|
icon: Package,
|
|
},
|
|
shipped: {
|
|
label: "ارسالشده",
|
|
className: "bg-blue-500/10 text-blue-600 hover:bg-blue-500/10",
|
|
icon: Truck,
|
|
},
|
|
delivered: {
|
|
label: "تحویلشده",
|
|
className: "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/10",
|
|
icon: Package,
|
|
},
|
|
cancelled: {
|
|
label: "لغوشده",
|
|
className: "bg-red-500/10 text-red-600 hover:bg-red-500/10",
|
|
icon: Package,
|
|
},
|
|
returned: {
|
|
label: "مرجوعشده",
|
|
className: "bg-slate-500/10 text-slate-600 hover:bg-slate-500/10",
|
|
icon: RefreshCcw,
|
|
},
|
|
};
|
|
|
|
const tabLabels: Record<string, string> = {
|
|
all: "همه",
|
|
active: "فعال",
|
|
delivered: "تحویلشده",
|
|
cancelled: "لغوشده",
|
|
};
|
|
|
|
interface OrderHistory1Props {
|
|
orders?: OrderHistoryItem[];
|
|
className?: string;
|
|
}
|
|
|
|
const OrderHistory1 = ({
|
|
orders = DEFAULT_ORDERS,
|
|
className,
|
|
}: OrderHistory1Props) => {
|
|
const [activeTab, setActiveTab] = useState("all");
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
const filteredOrders = orders.filter((order) => {
|
|
const matchesTab =
|
|
activeTab === "all" ||
|
|
(activeTab === "active" &&
|
|
(order.status === "processing" || order.status === "shipped")) ||
|
|
order.status === activeTab;
|
|
|
|
const matchesSearch =
|
|
searchQuery === "" ||
|
|
order.orderNumber.toLowerCase().includes(searchQuery.toLowerCase());
|
|
|
|
return matchesTab && matchesSearch;
|
|
});
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("py-16 md:py-24", className)}
|
|
>
|
|
<div className="container max-w-4xl">
|
|
<div className="mb-8">
|
|
<h1 className="text-xl font-bold tracking-tight md:text-3xl">
|
|
تاریخچه سفارشها
|
|
</h1>
|
|
<p className="mt-1 text-muted-foreground">
|
|
مشاهده و مدیریت سفارشهای گذشته خود
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList>
|
|
<TabsTrigger value="all">{tabLabels.all}</TabsTrigger>
|
|
<TabsTrigger value="active">{tabLabels.active}</TabsTrigger>
|
|
<TabsTrigger value="delivered">
|
|
{tabLabels.delivered}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="cancelled">
|
|
{tabLabels.cancelled}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
|
|
<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-64"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{filteredOrders.length > 0 ? (
|
|
<div className="space-y-4">
|
|
{filteredOrders.map((order) => {
|
|
const status = statusConfig[order.status];
|
|
|
|
return (
|
|
<Card
|
|
key={order.id}
|
|
className="shadow-none transition-shadow hover:shadow-md"
|
|
>
|
|
<CardContent className="p-4 md:p-6">
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="flex gap-4">
|
|
<div className="relative flex shrink-0">
|
|
{order.thumbnails.slice(0, 3).map((thumb, index) => (
|
|
<div
|
|
key={index}
|
|
className={cn(
|
|
"relative size-14 overflow-hidden rounded-lg border-2 border-background bg-muted",
|
|
index > 0 && "-ms-4",
|
|
)}
|
|
style={{ zIndex: 3 - index }}
|
|
>
|
|
<AspectRatio ratio={1}>
|
|
<img
|
|
src={thumb}
|
|
alt=""
|
|
className="size-full object-cover"
|
|
/>
|
|
</AspectRatio>
|
|
</div>
|
|
))}
|
|
{order.itemCount > 3 && (
|
|
<div
|
|
className="relative -ms-4 flex size-14 items-center justify-center rounded-lg border-2 border-background bg-muted text-sm font-medium text-muted-foreground"
|
|
style={{ zIndex: 0 }}
|
|
>
|
|
+
|
|
{(order.itemCount - 3).toLocaleString("fa-IR")}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<p className="font-semibold" dir="rtl">
|
|
{order.orderNumber}
|
|
</p>
|
|
<Badge className={status.className}>
|
|
{status.label}
|
|
</Badge>
|
|
</div>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
{formatOrderDate(order.orderDate)} ·{" "}
|
|
{formatItemCount(order.itemCount)}
|
|
</p>
|
|
{order.trackingNumber && (
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
رهگیری:{" "}
|
|
<span dir="rtl" className="tabular-nums">
|
|
{order.trackingNumber}
|
|
</span>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between gap-4 sm:flex-col sm:items-end sm:justify-center">
|
|
<p className="text-lg font-bold tabular-nums">
|
|
{formatCurrency(order.total)}
|
|
</p>
|
|
<div className="flex gap-2">
|
|
{order.status === "shipped" && (
|
|
<Button variant="outline" size="sm">
|
|
<Truck
|
|
className="me-1.5 size-4"
|
|
aria-hidden="true"
|
|
/>
|
|
پیگیری مرسوله
|
|
</Button>
|
|
)}
|
|
{order.status === "delivered" && (
|
|
<Button variant="outline" size="sm">
|
|
<RefreshCcw
|
|
className="me-1.5 size-4"
|
|
aria-hidden="true"
|
|
/>
|
|
سفارش مجدد
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="sm">
|
|
مشاهده
|
|
<ChevronRight
|
|
className="ms-1 size-4 rtl:rotate-180"
|
|
aria-hidden="true"
|
|
/>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<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">
|
|
<ShoppingBag
|
|
className="size-8 text-muted-foreground"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
<h2 className="mb-2 text-lg font-semibold">سفارشی یافت نشد</h2>
|
|
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
|
|
{searchQuery
|
|
? `سفارشی با عبارت «${searchQuery}» یافت نشد.`
|
|
: "هنوز سفارشی ثبت نکردهاید. با شروع خرید، تاریخچه سفارشها اینجا نمایش داده میشود."}
|
|
</p>
|
|
<Button>
|
|
<ShoppingBag className="me-2 size-4" aria-hidden="true" />
|
|
شروع خرید
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{filteredOrders.length > 0 && (
|
|
<p className="mt-6 border-t pt-6 text-sm text-muted-foreground">
|
|
نمایش {filteredOrders.length.toLocaleString("fa-IR")} از{" "}
|
|
{orders.length.toLocaleString("fa-IR")} سفارش
|
|
</p>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { OrderHistory1 };
|