- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
266 lines
12 KiB
TypeScript
266 lines
12 KiB
TypeScript
"use client";
|
||
import React from "react";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import {
|
||
X,
|
||
Printer,
|
||
Download,
|
||
Package,
|
||
ShoppingBag,
|
||
MapPin,
|
||
Calendar,
|
||
Clock,
|
||
CreditCard,
|
||
CheckCircle2,
|
||
AlertCircle,
|
||
Truck
|
||
} from "lucide-react";
|
||
import { toPersian, cn } from "../lib/utils";
|
||
import SafeImage from "./SafeImage";
|
||
|
||
interface OrderDetailsModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
order: any;
|
||
}
|
||
|
||
export default function OrderDetailsModal({ isOpen, onClose, order }: OrderDetailsModalProps) {
|
||
if (!order) return null;
|
||
|
||
const handlePrint = () => {
|
||
// Basic print logic: open a new window with a simplified version of the invoice
|
||
const printWindow = window.open('', '_blank');
|
||
if (!printWindow) return;
|
||
|
||
const itemsHtml = (order.items || []).map((item: any) => `
|
||
<tr>
|
||
<td style="padding: 12px; border-bottom: 1px solid #eee;">${item.product.name}</td>
|
||
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: center;">${toPersian(item.quantity)}</td>
|
||
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: left;">${toPersian(item.product.priceValue.toLocaleString())} تومان</td>
|
||
<td style="padding: 12px; border-bottom: 1px solid #eee; text-align: left;">${toPersian((item.product.priceValue * item.quantity).toLocaleString())} تومان</td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
printWindow.document.write(`
|
||
<html dir="rtl">
|
||
<head>
|
||
<title>فاکتور ${order.id}</title>
|
||
<style>
|
||
@font-face {
|
||
font-family: 'Vazirmatn';
|
||
src: url('https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2') format('woff2');
|
||
}
|
||
body { font-family: 'Vazirmatn', sans-serif; padding: 40px; color: #333; }
|
||
.header { display: flex; justify-between: space-between; align-items: center; border-bottom: 2px solid #0055FF; pb: 20px; mb: 40px; }
|
||
.logo { font-size: 24px; font-weight: 900; color: #0055FF; }
|
||
.info { margin-bottom: 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th { background: #f8f9fa; text-align: right; padding: 12px; }
|
||
.total-section { margin-top: 40px; text-align: left; }
|
||
.footer { margin-top: 60px; font-size: 12px; color: #888; text-align: center; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<div class="logo">CANINO | کانینا</div>
|
||
<div>تاریخ: ${toPersian(new Date(order.date).toLocaleDateString('fa-IR'))}</div>
|
||
</div>
|
||
<div class="info">
|
||
<div>
|
||
<strong>شماره فاکتور:</strong> ${toPersian(order.id)}<br/>
|
||
<strong>کد پیگیری:</strong> ${toPersian(order.trackingNumber)}
|
||
</div>
|
||
<div style="text-align: left;">
|
||
<strong>وضعیت:</strong> ${order.status === 'delivered' ? 'تسویه شده' : 'در حال پردازش'}
|
||
</div>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>محصول</th>
|
||
<th>تعداد</th>
|
||
<th>قیمت واحد</th>
|
||
<th>جمع کل</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${itemsHtml}
|
||
</tbody>
|
||
</table>
|
||
<div class="total-section">
|
||
<p>جمع کل فاکتور: ${toPersian(order.total.toLocaleString())} تومان</p>
|
||
</div>
|
||
<div class="footer">
|
||
این فاکتور به صورت سیستمی صادر شده و فاقد مهر و امضای فیزیکی است.
|
||
</div>
|
||
<script>window.print();</script>
|
||
</body>
|
||
</html>
|
||
`);
|
||
printWindow.document.close();
|
||
};
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
switch (status) {
|
||
case 'delivered': return <CheckCircle2 className="w-5 h-5 text-green-500" />;
|
||
case 'shipped': return <Truck className="w-5 h-5 text-blue-500" />;
|
||
default: return <Clock className="w-5 h-5 text-orange-500" />;
|
||
}
|
||
};
|
||
|
||
const getStatusLabel = (status: string) => {
|
||
switch (status) {
|
||
case 'delivered': return 'تحویل شده';
|
||
case 'shipped': return 'ارسال شده';
|
||
default: return 'در حال پردازش';
|
||
}
|
||
};
|
||
|
||
const orderDate = new Date(order.date);
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={onClose}
|
||
className="absolute inset-0 bg-medical-gray-900/60 backdrop-blur-md"
|
||
/>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
className="bg-white w-full max-w-2xl rounded-[3.5rem] relative z-10 shadow-2xl overflow-hidden font-vazir text-right"
|
||
dir="rtl"
|
||
>
|
||
{/* Header */}
|
||
<div className="bg-medical-gray-50 px-10 py-8 flex items-center justify-between border-b border-medical-gray-100">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-14 h-14 bg-canina-blue rounded-2xl flex items-center justify-center text-white shadow-lg">
|
||
<Package className="w-7 h-7" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-xl font-black text-medical-gray-900 italic">جزئیات سفارش {toPersian(order.id)}</h3>
|
||
<p className="text-xs font-bold text-medical-gray-400">ثبت شده در {toPersian(orderDate.toLocaleDateString("fa-IR"))}</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
className="w-10 h-10 flex items-center justify-center rounded-xl bg-white border border-medical-gray-200 text-medical-gray-400 hover:text-red-500 transition-all"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Content Scrollable */}
|
||
<div className="max-h-[70vh] overflow-y-auto px-10 py-8 space-y-10 custom-scrollbar">
|
||
{/* Summary Stats */}
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl">
|
||
<div className="text-[9px] font-black text-medical-gray-400 uppercase tracking-widest mb-1">وضعیت تحویل</div>
|
||
<div className="flex items-center gap-2">
|
||
{getStatusIcon(order.status)}
|
||
<span className="text-sm font-black text-medical-gray-900">{getStatusLabel(order.status)}</span>
|
||
</div>
|
||
</div>
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl">
|
||
<div className="text-[9px] font-black text-medical-gray-400 uppercase tracking-widest mb-1">کد رهگیری</div>
|
||
<div className="text-sm font-mono font-bold text-canina-blue">{toPersian(order.trackingNumber)}</div>
|
||
</div>
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl">
|
||
<div className="text-[9px] font-black text-medical-gray-400 uppercase tracking-widest mb-1">زمان ثبت</div>
|
||
<div className="flex items-center gap-2">
|
||
<Clock className="w-4 h-4 text-medical-gray-400" />
|
||
<span className="text-sm font-black text-medical-gray-900">{toPersian(orderDate.toLocaleTimeString("fa-IR", { hour: '2-digit', minute: '2-digit' }))}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Items List */}
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-6 flex items-center gap-2">
|
||
<ShoppingBag className="w-4 h-4 text-canina-blue" />
|
||
سبد محصولات خریده شده
|
||
</h4>
|
||
<div className="space-y-4">
|
||
{(order.items || []).map((item: any) => (
|
||
<div key={item.product.id} className="flex items-center justify-between p-4 border border-medical-gray-100 rounded-2xl hover:border-canina-blue transition-all">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-16 h-16 bg-medical-gray-50 rounded-xl p-2 flex items-center justify-center shrink-0">
|
||
<SafeImage
|
||
src={item.product.image}
|
||
alt={item.product.name}
|
||
className="w-full h-full"
|
||
imgClassName="object-contain"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<h5 className="font-black text-medical-gray-900 leading-tight mb-1">{item.product.name}</h5>
|
||
<div className="text-[10px] font-bold text-medical-gray-400">{toPersian(item.quantity)} عدد × {toPersian(item.product.priceValue.toLocaleString())} تومان</div>
|
||
</div>
|
||
</div>
|
||
<div className="text-left">
|
||
<div className="text-lg font-black text-canina-blue italic">{toPersian((item.product.priceValue * item.quantity).toLocaleString())} <span className="text-[10px] not-italic">تومان</span></div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Shipping & Payment */}
|
||
<div className="grid md:grid-cols-2 gap-8 pt-6 border-t border-medical-gray-100">
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-4 flex items-center gap-2">
|
||
<MapPin className="w-4 h-4 text-canina-blue" />
|
||
اطلاعات ارسال
|
||
</h4>
|
||
<p className="text-xs font-bold text-medical-gray-500 leading-relaxed">
|
||
تهران، خیابان ولیعصر، بالاتر از میدان ونک، بنبست آفتاب، پلاک ۱۲، واحد ۳
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-4 flex items-center gap-2">
|
||
<CreditCard className="w-4 h-4 text-canina-blue" />
|
||
جزئیات پرداخت
|
||
</h4>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-xs font-bold text-medical-gray-400">
|
||
<span>جمع ناخالص</span>
|
||
<span>{toPersian(order.total.toLocaleString())} تومان</span>
|
||
</div>
|
||
<div className="flex justify-between text-xs font-bold text-red-500">
|
||
<span>تخفیف هوشمند</span>
|
||
<span>{toPersian("۰")} تومان</span>
|
||
</div>
|
||
<div className="flex justify-between text-base font-black text-medical-gray-900 pt-2 border-t border-medical-gray-50 italic">
|
||
<span>مبلغ نهایی</span>
|
||
<span>{toPersian(order.total.toLocaleString())} تومان</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer Actions */}
|
||
<div className="p-10 bg-medical-gray-50 border-t border-medical-gray-100 flex gap-4">
|
||
<button
|
||
onClick={handlePrint}
|
||
className="flex-1 py-5 bg-white border border-medical-gray-200 text-medical-gray-900 rounded-2xl font-black text-sm flex items-center justify-center gap-3 hover:bg-medical-gray-100 transition-all shadow-sm"
|
||
>
|
||
<Download className="w-5 h-5" />
|
||
دانلود فاکتور رسمی
|
||
</button>
|
||
<button className="flex items-center justify-center w-14 h-14 bg-medical-gray-900 text-white rounded-2xl hover:bg-canina-blue transition-all shadow-lg shadow-black/10">
|
||
<Printer className="w-6 h-6" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
)}
|
||
</AnimatePresence>
|
||
);
|
||
}
|