414 lines
15 KiB
TypeScript
414 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import { ArrowRight, Check, Package, Search, 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 { Input } from "@/components/ui/input";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type OrderStatus = "confirmed" | "processing" | "shipped" | "delivered";
|
|
|
|
interface OrderItem {
|
|
name: string;
|
|
designer: string;
|
|
image: string;
|
|
price: number;
|
|
size?: string;
|
|
color?: string;
|
|
}
|
|
|
|
interface Order {
|
|
id: string;
|
|
orderNumber: string;
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|
orderDate: string;
|
|
status: OrderStatus;
|
|
total: number;
|
|
items: OrderItem[];
|
|
trackingNumber?: string;
|
|
/** ISO 8601 date range for estimated delivery */
|
|
estimatedDeliveryFrom?: string;
|
|
estimatedDeliveryTo?: string;
|
|
}
|
|
|
|
const DEFAULT_ORDERS: Order[] = [
|
|
{
|
|
id: "1",
|
|
orderNumber: "SB-2025-00847",
|
|
orderDate: "2025-01-15",
|
|
status: "shipped",
|
|
total: 2450000.0,
|
|
trackingNumber: "1Z999AA10123456784",
|
|
estimatedDeliveryFrom: "2025-01-20",
|
|
estimatedDeliveryTo: "2025-01-22",
|
|
items: [
|
|
{
|
|
name: "صندلی چرمی مجسمهوار",
|
|
designer: "آتلیه کالکشن",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Leather-Sofa-1.png",
|
|
price: 1890000.0,
|
|
color: "کانیاک",
|
|
},
|
|
{
|
|
name: "آویز شیشهدمیده دستساز",
|
|
designer: "استودیو لومیِر",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-1.png",
|
|
price: 560000.0,
|
|
color: "کهربایی",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "2",
|
|
orderNumber: "SB-2025-00831",
|
|
orderDate: "2025-01-08",
|
|
status: "delivered",
|
|
total: 3200000.0,
|
|
items: [
|
|
{
|
|
name: "میز کنسول گردو",
|
|
designer: "نوردیک کرفت",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Wooden-Sideboard-1.png",
|
|
price: 2400000.0,
|
|
size: "۱۶۰ × ۴۰ × ۸۵ سانتیمتر",
|
|
},
|
|
{
|
|
name: "گلدان مجسمهای سرامیکی",
|
|
designer: "کالکتیو هنرمند",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-2.png",
|
|
price: 420000.0,
|
|
color: "سفید مات",
|
|
},
|
|
{
|
|
name: "پتو پشمی کتان",
|
|
designer: "مزون تکستایل",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Loveseat-1.png",
|
|
price: 380000.0,
|
|
color: "طبیعی",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "3",
|
|
orderNumber: "SB-2024-00798",
|
|
orderDate: "2024-12-28",
|
|
status: "delivered",
|
|
total: 1650000.0,
|
|
items: [
|
|
{
|
|
name: "چراغ ایستاده مینیمال",
|
|
designer: "لایت اند فرم",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Wooden-Cabinet-1.png",
|
|
price: 1650000.0,
|
|
color: "برنجی برسخورده",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
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 formatDeliveryRange = (from: string, to: string) =>
|
|
`${formatOrderDate(from)} تا ${formatOrderDate(to)}`;
|
|
|
|
const statusSteps: { key: OrderStatus; label: string; icon: typeof Check }[] = [
|
|
{ key: "confirmed", label: "تأییدشده", icon: Check },
|
|
{ key: "processing", label: "در حال پردازش", icon: Package },
|
|
{ key: "shipped", label: "ارسالشده", icon: Truck },
|
|
{ key: "delivered", label: "تحویلشده", icon: Check },
|
|
];
|
|
|
|
const getStatusIndex = (status: OrderStatus) => {
|
|
return statusSteps.findIndex((s) => s.key === status);
|
|
};
|
|
|
|
interface OrderHistory4Props {
|
|
orders?: Order[];
|
|
className?: string;
|
|
}
|
|
|
|
const OrderHistory4 = ({
|
|
orders = DEFAULT_ORDERS,
|
|
className,
|
|
}: OrderHistory4Props) => {
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
const filteredOrders = orders.filter((order) => {
|
|
if (searchQuery === "") return true;
|
|
const query = searchQuery.toLowerCase();
|
|
return (
|
|
order.orderNumber.toLowerCase().includes(query) ||
|
|
order.items.some(
|
|
(item) =>
|
|
item.name.toLowerCase().includes(query) ||
|
|
item.designer.toLowerCase().includes(query),
|
|
)
|
|
);
|
|
});
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("py-16 md:py-24", className)}
|
|
>
|
|
<div className="container max-w-6xl">
|
|
<div className="mb-12 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
|
|
<div>
|
|
<p className="text-xs font-medium tracking-[0.2em] text-muted-foreground">
|
|
حساب کاربری
|
|
</p>
|
|
<h1 className="mt-2 text-3xl font-light tracking-tight md:text-xl">
|
|
تاریخچه سفارشها
|
|
</h1>
|
|
</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-64"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-16">
|
|
{filteredOrders.map((order) => {
|
|
const currentStatusIndex = getStatusIndex(order.status);
|
|
|
|
return (
|
|
<article key={order.id} className="group">
|
|
<div className="mb-8 flex flex-col gap-4 border-b pb-6 md:flex-row md:items-center md:justify-between">
|
|
<div className="flex flex-wrap items-baseline gap-x-6 gap-y-2">
|
|
<h2
|
|
className="text-lg font-medium tracking-wide"
|
|
dir="rtl"
|
|
>
|
|
{order.orderNumber}
|
|
</h2>
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatOrderDate(order.orderDate)}
|
|
</span>
|
|
<span className="text-sm font-medium tabular-nums">
|
|
{formatCurrency(order.total)}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
{order.status === "delivered" ? (
|
|
<Badge
|
|
variant="secondary"
|
|
className="rounded-none bg-emerald-50 px-3 py-1 text-xs font-medium tracking-wide text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
|
|
>
|
|
تحویلشده
|
|
</Badge>
|
|
) : (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-9 rounded-none text-xs tracking-wide"
|
|
>
|
|
پیگیری سفارش
|
|
<ArrowRight
|
|
className="ms-2 size-3 rtl:rotate-180"
|
|
aria-hidden="true"
|
|
/>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{order.status !== "delivered" && (
|
|
<div className="mb-10">
|
|
<div className="flex items-center justify-between">
|
|
{statusSteps.map((step, index) => {
|
|
const isCompleted = index <= currentStatusIndex;
|
|
const isCurrent = index === currentStatusIndex;
|
|
const StepIcon = step.icon;
|
|
|
|
return (
|
|
<div
|
|
key={step.key}
|
|
className="flex flex-1 items-center"
|
|
>
|
|
<div className="flex flex-col items-center">
|
|
<div
|
|
className={cn(
|
|
"flex size-10 items-center justify-center rounded-full border-2 transition-colors",
|
|
isCompleted
|
|
? "border-primary bg-primary text-primary-foreground"
|
|
: "border-muted-foreground/30 text-muted-foreground/30",
|
|
isCurrent && "ring-4 ring-primary/20",
|
|
)}
|
|
>
|
|
<StepIcon
|
|
className="size-4"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
<span
|
|
className={cn(
|
|
"mt-2 text-xs font-medium tracking-wide",
|
|
isCompleted
|
|
? "text-foreground"
|
|
: "text-muted-foreground/50",
|
|
)}
|
|
>
|
|
{step.label}
|
|
</span>
|
|
</div>
|
|
{index < statusSteps.length - 1 && (
|
|
<div
|
|
className={cn(
|
|
"mx-2 h-0.5 flex-1",
|
|
index < currentStatusIndex
|
|
? "bg-primary"
|
|
: "bg-muted-foreground/20",
|
|
)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{order.estimatedDeliveryFrom &&
|
|
order.estimatedDeliveryTo && (
|
|
<p className="mt-4 text-center text-sm text-muted-foreground">
|
|
تحویل تقریبی:{" "}
|
|
<span className="font-medium text-foreground">
|
|
{formatDeliveryRange(
|
|
order.estimatedDeliveryFrom,
|
|
order.estimatedDeliveryTo,
|
|
)}
|
|
</span>
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
|
{order.items.map((item, index) => (
|
|
<div key={index} className="group/item">
|
|
<div className="relative mb-4 overflow-hidden bg-muted/50">
|
|
<AspectRatio ratio={4 / 5}>
|
|
<img
|
|
src={item.image}
|
|
alt={item.name}
|
|
className="size-full object-cover transition-transform duration-500 group-hover/item:scale-105"
|
|
/>
|
|
</AspectRatio>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
className="absolute end-4 bottom-4 h-9 rounded-none bg-background/90 text-xs tracking-wide opacity-0 backdrop-blur-sm transition-opacity group-hover/item:opacity-100"
|
|
>
|
|
خرید مجدد
|
|
</Button>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-[10px] font-medium tracking-[0.15em] text-muted-foreground">
|
|
{item.designer}
|
|
</p>
|
|
<h3 className="mt-1 text-sm font-medium">
|
|
{item.name}
|
|
</h3>
|
|
<div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground">
|
|
{item.color && <span>{item.color}</span>}
|
|
{item.size && <span>{item.size}</span>}
|
|
{(item.color || item.size) && (
|
|
<Separator
|
|
orientation="vertical"
|
|
className="h-3"
|
|
/>
|
|
)}
|
|
<span className="font-medium text-foreground tabular-nums">
|
|
{formatCurrency(item.price)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-8 flex flex-wrap items-center justify-between gap-4 border-t pt-6">
|
|
<div className="flex gap-6 text-xs">
|
|
<button
|
|
type="button"
|
|
className="font-medium tracking-wide text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
مشاهده جزئیات
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="font-medium tracking-wide text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
فاکتور
|
|
</button>
|
|
{order.status === "delivered" && (
|
|
<button
|
|
type="button"
|
|
className="font-medium tracking-wide text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
بازگشت کالا
|
|
</button>
|
|
)}
|
|
</div>
|
|
{order.trackingNumber && order.status !== "delivered" && (
|
|
<p className="text-xs text-muted-foreground">
|
|
رهگیری:{" "}
|
|
<span className="font-mono tabular-nums" dir="rtl">
|
|
{order.trackingNumber}
|
|
</span>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{filteredOrders.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center py-24 text-center">
|
|
<p className="text-sm text-muted-foreground">
|
|
سفارشی با عبارت «{searchQuery}» یافت نشد.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { OrderHistory4 };
|