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

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