527 lines
17 KiB
TypeScript
527 lines
17 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ArrowUpRight,
|
|
ChevronRight,
|
|
Package,
|
|
RotateCcw,
|
|
Search,
|
|
Star,
|
|
Truck,
|
|
} from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type OrderStatus = "processing" | "shipped" | "delivered" | "returned";
|
|
|
|
interface OrderItem {
|
|
id: string;
|
|
name: string;
|
|
image: string;
|
|
price: number;
|
|
size: string;
|
|
color: string;
|
|
quantity: number;
|
|
canReturn?: boolean;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
returnByDate?: string;
|
|
}
|
|
|
|
interface Order {
|
|
id: string;
|
|
orderNumber: string;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
orderDate: string;
|
|
status: OrderStatus;
|
|
total: number;
|
|
items: OrderItem[];
|
|
/** ISO 8601 date(s) for expected delivery */
|
|
deliveryDateFrom?: string;
|
|
deliveryDateTo?: string;
|
|
trackingUrl?: string;
|
|
}
|
|
|
|
const DEFAULT_ORDERS: Order[] = [
|
|
{
|
|
id: "1",
|
|
orderNumber: "WEB-9847562",
|
|
orderDate: "2024-12-12",
|
|
status: "shipped",
|
|
total: 1279700.0,
|
|
deliveryDateFrom: "2024-12-18",
|
|
deliveryDateTo: "2024-12-20",
|
|
trackingUrl: "#",
|
|
items: [
|
|
{
|
|
id: "1a",
|
|
name: "هودی پنبهای اورسایز",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/black-hoodie-against-light-background.png",
|
|
price: 499900.0,
|
|
size: "L",
|
|
color: "مشکی",
|
|
quantity: 1,
|
|
},
|
|
{
|
|
id: "1b",
|
|
name: "جین راسته فاق بلند",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/pexels-jay-soundo-2148060180-30624171-1.png",
|
|
price: 599900.0,
|
|
size: "32",
|
|
color: "آبی متوسط",
|
|
quantity: 1,
|
|
},
|
|
{
|
|
id: "1c",
|
|
name: "تیشرت یقه گرد ساده",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/minimalist-tank-top-flatlay.png",
|
|
price: 179900.0,
|
|
size: "M",
|
|
color: "سفید",
|
|
quantity: 1,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "2",
|
|
orderNumber: "WEB-9841203",
|
|
orderDate: "2024-12-05",
|
|
status: "delivered",
|
|
total: 899800.0,
|
|
deliveryDateFrom: "2024-12-10",
|
|
items: [
|
|
{
|
|
id: "2a",
|
|
name: "کیف دوشی چرمی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/maroon-leather-handbag.png",
|
|
price: 699900.0,
|
|
size: "یک سایز",
|
|
color: "زرشکی",
|
|
quantity: 1,
|
|
canReturn: true,
|
|
returnByDate: "2025-01-10",
|
|
},
|
|
{
|
|
id: "2b",
|
|
name: "کلاه بافتنی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Classic-Black-Fedora-Hat-1.png",
|
|
price: 199900.0,
|
|
size: "یک سایز",
|
|
color: "ذغالی",
|
|
quantity: 1,
|
|
canReturn: true,
|
|
returnByDate: "2025-01-10",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "3",
|
|
orderNumber: "WEB-9835678",
|
|
orderDate: "2024-11-28",
|
|
status: "delivered",
|
|
total: 1599700.0,
|
|
deliveryDateFrom: "2024-12-03",
|
|
items: [
|
|
{
|
|
id: "3a",
|
|
name: "پالتو پشمی مخلوط",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-White-Hoodie-1.png",
|
|
price: 129990.0,
|
|
size: "M",
|
|
color: "شتری",
|
|
quantity: 1,
|
|
canReturn: false,
|
|
},
|
|
{
|
|
id: "3b",
|
|
name: "شال کشمیر",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/elegant-peach-scarf.png",
|
|
price: 299800.0,
|
|
size: "یک سایز",
|
|
color: "کرم جو دوسر",
|
|
quantity: 2,
|
|
canReturn: false,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "4",
|
|
orderNumber: "WEB-9828901",
|
|
orderDate: "2024-11-15",
|
|
status: "returned",
|
|
total: 459900.0,
|
|
items: [
|
|
{
|
|
id: "4a",
|
|
name: "شلوار چینو اسلیم",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/pexels-jay-soundo-2148060180-30624171-2.png",
|
|
price: 459900.0,
|
|
size: "30",
|
|
color: "خاکی",
|
|
quantity: 1,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
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 formatDeliveryEstimate = (from: string, to?: string) => {
|
|
if (to && to !== from) {
|
|
return `${formatOrderDate(from)} تا ${formatOrderDate(to)}`;
|
|
}
|
|
return formatOrderDate(from);
|
|
};
|
|
|
|
const statusConfig: Record<
|
|
OrderStatus,
|
|
{ label: string; color: string; dotColor: string }
|
|
> = {
|
|
processing: {
|
|
label: "در حال پردازش",
|
|
color: "text-amber-600",
|
|
dotColor: "bg-amber-500",
|
|
},
|
|
shipped: {
|
|
label: "در مسیر تحویل",
|
|
color: "text-blue-600",
|
|
dotColor: "bg-blue-500",
|
|
},
|
|
delivered: {
|
|
label: "تحویلشده",
|
|
color: "text-emerald-600",
|
|
dotColor: "bg-emerald-500",
|
|
},
|
|
returned: {
|
|
label: "مرجوعشده",
|
|
color: "text-muted-foreground",
|
|
dotColor: "bg-muted-foreground",
|
|
},
|
|
};
|
|
|
|
const tabLabels: Record<string, string> = {
|
|
all: "همه سفارشها",
|
|
active: "در حال انجام",
|
|
delivered: "تحویلشده",
|
|
returned: "مرجوعات",
|
|
};
|
|
|
|
interface OrderHistory5Props {
|
|
orders?: Order[];
|
|
className?: string;
|
|
}
|
|
|
|
const OrderHistory5 = ({
|
|
orders = DEFAULT_ORDERS,
|
|
className,
|
|
}: OrderHistory5Props) => {
|
|
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()) ||
|
|
order.items.some((item) =>
|
|
item.name.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-semibold tracking-tight md:text-3xl">
|
|
سفارشهای شما
|
|
</h1>
|
|
<p className="mt-1 text-muted-foreground">
|
|
پیگیری، بازگشت یا خرید مجدد اقلام
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mb-8 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="returned">{tabLabels.returned}</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>
|
|
|
|
<div className="space-y-6">
|
|
{filteredOrders.map((order) => {
|
|
const status = statusConfig[order.status];
|
|
|
|
return (
|
|
<Card
|
|
key={order.id}
|
|
className="gap-0 overflow-hidden p-0 shadow-none"
|
|
>
|
|
<div
|
|
className={cn(
|
|
"flex flex-wrap items-center justify-between gap-4 px-5 py-4",
|
|
!(
|
|
order.status === "shipped" && order.deliveryDateFrom
|
|
) && "border-b",
|
|
)}
|
|
>
|
|
<div className="flex flex-wrap items-center gap-x-5 gap-y-1">
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">
|
|
تاریخ ثبت سفارش
|
|
</p>
|
|
<p className="text-sm font-medium">
|
|
{formatOrderDate(order.orderDate)}
|
|
</p>
|
|
</div>
|
|
<Separator orientation="vertical" className="h-8" />
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">
|
|
شماره سفارش
|
|
</p>
|
|
<p className="text-sm font-medium" dir="rtl">
|
|
{order.orderNumber}
|
|
</p>
|
|
</div>
|
|
<Separator orientation="vertical" className="h-8" />
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">مجموع</p>
|
|
<p className="text-sm font-medium tabular-nums">
|
|
{formatCurrency(order.total)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className={cn("size-2 rounded-full", status.dotColor)}
|
|
aria-hidden="true"
|
|
/>
|
|
<span className={cn("text-sm font-medium", status.color)}>
|
|
{status.label}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{order.status === "shipped" && order.deliveryDateFrom && (
|
|
<div className="flex items-center justify-between gap-4 border-b bg-muted/30 px-5 py-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex size-9 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-950">
|
|
<Truck
|
|
className="size-4 text-blue-600"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium">
|
|
تحویل:{" "}
|
|
{formatDeliveryEstimate(
|
|
order.deliveryDateFrom,
|
|
order.deliveryDateTo,
|
|
)}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
بسته شما در راه است
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{order.trackingUrl && (
|
|
<Button variant="outline" size="sm" asChild>
|
|
<a href={order.trackingUrl}>
|
|
پیگیری
|
|
<ArrowUpRight
|
|
className="ms-1.5 size-3.5 rtl:-rotate-90"
|
|
aria-hidden="true"
|
|
/>
|
|
</a>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<CardContent className="p-0">
|
|
{order.items.map((item, index) => (
|
|
<div key={item.id}>
|
|
{index > 0 && <Separator />}
|
|
<div className="flex gap-5 p-5">
|
|
<div className="size-24 shrink-0 overflow-hidden rounded-lg bg-muted sm:size-28">
|
|
<AspectRatio ratio={1}>
|
|
<img
|
|
src={item.image}
|
|
alt={item.name}
|
|
className="size-full object-cover"
|
|
/>
|
|
</AspectRatio>
|
|
</div>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h3 className="font-medium">{item.name}</h3>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
{item.color} ·{" "}
|
|
<span dir="rtl">{item.size}</span>
|
|
{item.quantity > 1 && (
|
|
<>
|
|
{" "}
|
|
· تعداد:{" "}
|
|
{item.quantity.toLocaleString("fa-IR")}
|
|
</>
|
|
)}
|
|
</p>
|
|
</div>
|
|
<p className="shrink-0 text-sm font-semibold tabular-nums">
|
|
{formatCurrency(item.price)}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-auto flex flex-wrap items-center gap-2 pt-3">
|
|
{order.status === "delivered" && (
|
|
<>
|
|
<Button variant="secondary" size="sm">
|
|
<RotateCcw
|
|
className="me-1.5 size-3.5"
|
|
aria-hidden="true"
|
|
/>
|
|
خرید مجدد
|
|
</Button>
|
|
{item.canReturn && (
|
|
<Button variant="outline" size="sm">
|
|
شروع بازگشت
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="sm">
|
|
<Star
|
|
className="me-1.5 size-3.5"
|
|
aria-hidden="true"
|
|
/>
|
|
ثبت نظر
|
|
</Button>
|
|
</>
|
|
)}
|
|
{order.status === "shipped" && (
|
|
<Button variant="secondary" size="sm">
|
|
<Package
|
|
className="me-1.5 size-3.5"
|
|
aria-hidden="true"
|
|
/>
|
|
پیگیری قلم
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{item.canReturn && item.returnByDate && (
|
|
<p className="mt-3 text-xs text-muted-foreground">
|
|
امکان بازگشت تا{" "}
|
|
{formatOrderDate(item.returnByDate)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
|
|
<div className="flex items-center justify-between border-t bg-muted/20 px-5 py-3">
|
|
<Button
|
|
variant="link"
|
|
size="sm"
|
|
className="h-auto p-0 text-foreground"
|
|
>
|
|
مشاهده جزئیات سفارش
|
|
<ChevronRight
|
|
className="ms-1 size-4 rtl:rotate-180"
|
|
aria-hidden="true"
|
|
/>
|
|
</Button>
|
|
<Button variant="ghost" size="sm">
|
|
به کمک نیاز دارید؟
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
|
|
{filteredOrders.length === 0 && (
|
|
<Card className="gap-0 p-0 shadow-none">
|
|
<CardContent className="flex flex-col items-center justify-center py-16 text-center">
|
|
<div className="mb-4 flex size-14 items-center justify-center rounded-full bg-muted">
|
|
<Package
|
|
className="size-6 text-muted-foreground"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
<h2 className="text-lg font-semibold">سفارشی یافت نشد</h2>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
{searchQuery
|
|
? `نتیجهای برای «${searchQuery}» یافت نشد.`
|
|
: "هنوز سفارشی ثبت نکردهاید."}
|
|
</p>
|
|
<Button className="mt-5">شروع خرید</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { OrderHistory5 };
|