canina/frontend/application/components/OrderDetailsModal.tsx
parsa aghaei 7615aa0e51 fix: resolve seed encoding, search param, and wiki data source issues
- 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
2026-07-11 15:40:49 +03:30

266 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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