Files
reui-blocks/blocks/order-history4.tsx
T

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 };