fix(app): improve wallet history display, fix order details modal layout and lint errors
This commit is contained in:
parent
56e8029fff
commit
2c377cab88
@ -59,8 +59,8 @@ export default function AnalyticsDeferred({ gtmId, gaId, clarityId }: AnalyticsD
|
||||
(function (c: any, l: any, a: any, r: any, i: any) {
|
||||
c[a] =
|
||||
c[a] ||
|
||||
function () {
|
||||
(c[a].q = c[a].q || []).push(arguments);
|
||||
function (...args: unknown[]) {
|
||||
(c[a].q = c[a].q || []).push(args);
|
||||
};
|
||||
const t = l.createElement(r);
|
||||
t.async = 1;
|
||||
|
||||
@ -386,11 +386,11 @@ export default function OrderDetailsModal({
|
||||
)}
|
||||
|
||||
{/* Content Scrollable */}
|
||||
<div className="max-h-[60vh] overflow-y-auto px-6 sm:px-10 py-6 space-y-6 custom-scrollbar">
|
||||
<div className="max-h-[65vh] overflow-y-auto px-6 sm:px-10 py-6 space-y-6 custom-scrollbar">
|
||||
{/* Summary Stats */}
|
||||
<div className="grid grid-cols-3 gap-3 sm: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 className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||||
وضعیت سفارش
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -398,155 +398,178 @@ export default function OrderDetailsModal({
|
||||
<span className="text-xs sm:text-sm font-black text-medical-gray-900">
|
||||
{getStatusLabel(orderStatus)}
|
||||
</span>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<ShoppingBag className="w-4 h-4 text-canina-blue" />
|
||||
سبد اقلام سفارش
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
{items.map((item: Record<string, unknown>, idx: number) => {
|
||||
const prod = item.product as Record<string, unknown> | undefined;
|
||||
const pName = getProductName(prod);
|
||||
const pImg = getProductImage(prod);
|
||||
const pPrice = Number(prod?.priceValue || item.priceValue || 0);
|
||||
const itemQty = Number(item.quantity || 1);
|
||||
const pSlug = String(prod?.slug || prod?.id || '');
|
||||
return (
|
||||
<div
|
||||
key={String(prod?.id || idx)}
|
||||
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
|
||||
onClick={() => setZoomedImage(pImg)}
|
||||
className="w-14 h-14 bg-medical-gray-50 rounded-xl p-1 flex items-center justify-center shrink-0 border border-medical-gray-200 cursor-zoom-in hover:scale-105 transition-transform overflow-hidden"
|
||||
title="مشاهده تصویر بزرگ"
|
||||
>
|
||||
<SafeImage
|
||||
src={pImg}
|
||||
alt={pName}
|
||||
className="w-full h-full"
|
||||
imgClassName="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{pSlug ? (
|
||||
<Link
|
||||
href={`/shop/${pSlug}`}
|
||||
onClick={onClose}
|
||||
className="font-black text-medical-gray-900 hover:text-canina-blue transition-colors text-xs sm:text-sm inline-block font-vazir"
|
||||
>
|
||||
{pName}
|
||||
</Link>
|
||||
) : (
|
||||
<h5 className="font-black text-medical-gray-900 text-xs sm:text-sm font-vazir">
|
||||
{pName}
|
||||
</h5>
|
||||
)}
|
||||
<p className="text-xs text-medical-gray-400 mt-0.5 font-vazir">
|
||||
{toPersian(itemQty)} عدد × {toPersian(pPrice.toLocaleString())} تومان
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-left font-black text-canina-blue text-xs sm:text-sm font-vazir">
|
||||
{toPersian((pPrice * itemQty).toLocaleString())} تومان
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Shipping & Payment */}
|
||||
<div className="grid md:grid-cols-2 gap-6 pt-4 border-t border-medical-gray-100">
|
||||
<div>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4 text-canina-blue" />
|
||||
اطلاعات تحویل و آدرس
|
||||
</h4>
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2 text-xs font-bold text-medical-gray-700">
|
||||
<div>
|
||||
<span className="text-medical-gray-400">گیرنده: </span>
|
||||
{String(shippingAddress.recipientName || shippingAddress.name || 'کاربر')}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-medical-gray-400">شماره تماس: </span>
|
||||
<span className="font-mono">{toPersian(String(shippingAddress.phone || shippingAddress.mobile || '-'))}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-medical-gray-400">نشانی کامل: </span>
|
||||
{String(shippingAddress.fullAddress || shippingAddress.address || '-')}
|
||||
</div>
|
||||
{Boolean(shippingAddress.postalCode) && (
|
||||
<div>
|
||||
<span className="text-medical-gray-400">کد پستی: </span>
|
||||
<span className="font-mono">{toPersian(String(shippingAddress.postalCode))}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<CreditCard className="w-4 h-4 text-canina-blue" />
|
||||
اطلاعات و وضعیت پرداخت
|
||||
</h4>
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2.5 text-xs font-bold text-medical-gray-700">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">شیوه پرداخت:</span>
|
||||
<span className="text-medical-gray-900">{getPaymentMethodTitle(order.paymentMethod)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">مجموع اقلام:</span>
|
||||
<span>{toPersian(itemsSubtotal.toLocaleString())} تومان</span>
|
||||
</div>
|
||||
{charityAmount > 0 && (
|
||||
<div className="flex justify-between items-center text-pink-500">
|
||||
<span>ردپای مهربانی:</span>
|
||||
<span>{toPersian(charityAmount.toLocaleString())}+ تومان</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">هزینه ارسال:</span>
|
||||
<span className="text-green-600">رایگان</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-medical-gray-200/60 flex justify-between items-center text-sm font-black text-medical-gray-900">
|
||||
<span>مبلغ نهایی فاکتور:</span>
|
||||
<span className="text-canina-blue">{toPersian(totalAmount.toLocaleString())} تومان</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="p-6 sm:px-10 bg-medical-gray-50 border-t border-medical-gray-100 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="w-full sm:w-auto px-5 py-2.5 bg-white border border-medical-gray-200 text-medical-gray-700 rounded-xl text-xs font-black hover:border-canina-blue hover:text-canina-blue transition-all flex items-center justify-center gap-2 shadow-xs cursor-pointer"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
<span>چاپ فاکتور رسمی</span>
|
||||
</button>
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||||
کد رهگیری سفارش
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm font-mono font-black text-canina-blue">
|
||||
{toPersian(displayTrackingCode)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||
{isPendingPayment && (
|
||||
<button
|
||||
onClick={() => setRetryModalOpen(true)}
|
||||
className="w-full sm:w-auto px-6 py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-xs font-black shadow-lg shadow-green-600/20 transition-all flex items-center justify-center gap-2 cursor-pointer"
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||||
زمان ثبت سفارش
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-xs sm:text-sm font-black text-medical-gray-900">
|
||||
<Clock className="w-4 h-4 text-medical-gray-400" />
|
||||
<span>{toPersian(formattedTime)} - {toPersian(formattedDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Items Section */}
|
||||
<div>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<ShoppingBag className="w-4 h-4 text-canina-blue" />
|
||||
سبد اقلام سفارش
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
{items.map((item: Record<string, unknown>, idx: number) => {
|
||||
const prod = item.product as Record<string, unknown> | undefined;
|
||||
const pName = getProductName(prod);
|
||||
const pImg = getProductImage(prod);
|
||||
const pPrice = Number(prod?.priceValue || item.priceValue || 0);
|
||||
const itemQty = Number(item.quantity || 1);
|
||||
const pSlug = String(prod?.slug || prod?.id || '');
|
||||
return (
|
||||
<div
|
||||
key={String(prod?.id || idx)}
|
||||
className="flex items-center justify-between p-4 border border-medical-gray-100 rounded-2xl hover:border-canina-blue transition-all"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" />
|
||||
<span>پرداخت سفارش</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
onClick={() => setZoomedImage(pImg)}
|
||||
className="w-14 h-14 bg-medical-gray-50 rounded-xl p-1 flex items-center justify-center shrink-0 border border-medical-gray-200 cursor-zoom-in hover:scale-105 transition-transform overflow-hidden"
|
||||
title="مشاهده تصویر بزرگ"
|
||||
>
|
||||
<SafeImage
|
||||
src={pImg}
|
||||
alt={pName}
|
||||
className="w-full h-full"
|
||||
imgClassName="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{pSlug ? (
|
||||
<Link
|
||||
href={`/shop/${pSlug}`}
|
||||
onClick={onClose}
|
||||
className="font-black text-medical-gray-900 hover:text-canina-blue transition-colors text-xs sm:text-sm inline-block font-vazir"
|
||||
>
|
||||
{pName}
|
||||
</Link>
|
||||
) : (
|
||||
<h5 className="font-black text-medical-gray-900 text-xs sm:text-sm font-vazir">
|
||||
{pName}
|
||||
</h5>
|
||||
)}
|
||||
<p className="text-xs text-medical-gray-400 mt-0.5 font-vazir">
|
||||
{toPersian(itemQty)} عدد × {toPersian(pPrice.toLocaleString())} تومان
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-left font-black text-canina-blue text-xs sm:text-sm font-vazir">
|
||||
{toPersian((pPrice * itemQty).toLocaleString())} تومان
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Shipping & Payment */}
|
||||
<div className="grid md:grid-cols-2 gap-6 pt-4 border-t border-medical-gray-100">
|
||||
<div>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4 text-canina-blue" />
|
||||
اطلاعات تحویل و آدرس
|
||||
</h4>
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2 text-xs font-bold text-medical-gray-700">
|
||||
<div>
|
||||
<span className="text-medical-gray-400">گیرنده: </span>
|
||||
{String(shippingAddress.recipientName || shippingAddress.name || 'کاربر')}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-medical-gray-400">شماره تماس: </span>
|
||||
<span className="font-mono">{toPersian(String(shippingAddress.phone || shippingAddress.mobile || '-'))}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-medical-gray-400">نشانی کامل: </span>
|
||||
{String(shippingAddress.fullAddress || shippingAddress.address || '-')}
|
||||
</div>
|
||||
{Boolean(shippingAddress.postalCode) && (
|
||||
<div>
|
||||
<span className="text-medical-gray-400">کد پستی: </span>
|
||||
<span className="font-mono">{toPersian(String(shippingAddress.postalCode))}</span>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full sm:w-auto px-6 py-2.5 bg-medical-gray-900 hover:bg-canina-blue text-white rounded-xl text-xs font-black transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<span>بازگشت</span>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||||
<CreditCard className="w-4 h-4 text-canina-blue" />
|
||||
اطلاعات و وضعیت پرداخت
|
||||
</h4>
|
||||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2.5 text-xs font-bold text-medical-gray-700">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">شیوه پرداخت:</span>
|
||||
<span className="text-medical-gray-900">{getPaymentMethodTitle(order.paymentMethod)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">مجموع اقلام:</span>
|
||||
<span>{toPersian(itemsSubtotal.toLocaleString())} تومان</span>
|
||||
</div>
|
||||
{charityAmount > 0 && (
|
||||
<div className="flex justify-between items-center text-pink-500">
|
||||
<span>ردپای مهربانی:</span>
|
||||
<span>{toPersian(charityAmount.toLocaleString())}+ تومان</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-medical-gray-400">هزینه ارسال:</span>
|
||||
<span className="text-green-600">رایگان</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-medical-gray-200/60 flex justify-between items-center text-sm font-black text-medical-gray-900">
|
||||
<span>مبلغ نهایی فاکتور:</span>
|
||||
<span className="text-canina-blue">{toPersian(totalAmount.toLocaleString())} تومان</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="p-6 sm:px-10 bg-medical-gray-50 border-t border-medical-gray-100 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="w-full sm:w-auto px-5 py-2.5 bg-white border border-medical-gray-200 text-medical-gray-700 rounded-xl text-xs font-black hover:border-canina-blue hover:text-canina-blue transition-all flex items-center justify-center gap-2 shadow-xs cursor-pointer"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
<span>چاپ فاکتور رسمی</span>
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||
{isPendingPayment && (
|
||||
<button
|
||||
onClick={() => setRetryModalOpen(true)}
|
||||
className="w-full sm:w-auto px-6 py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-xs font-black shadow-lg shadow-green-600/20 transition-all flex items-center justify-center gap-2 cursor-pointer"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" />
|
||||
<span>پرداخت سفارش</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full sm:w-auto px-6 py-2.5 bg-medical-gray-900 hover:bg-canina-blue text-white rounded-xl text-xs font-black transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<span>بازگشت</span>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -55,6 +55,16 @@ export default function ProductImageZoomModal({
|
||||
};
|
||||
}, [isOpen, activeImage, resetZoom]);
|
||||
|
||||
const navigateImage = useCallback((direction: number) => {
|
||||
if (!images || images.length <= 1) return;
|
||||
const currentIndex = images.indexOf(activeImage);
|
||||
if (currentIndex === -1) return;
|
||||
let nextIndex = (currentIndex + direction) % images.length;
|
||||
if (nextIndex < 0) nextIndex = images.length - 1;
|
||||
onSelectImage(images[nextIndex]);
|
||||
resetZoom();
|
||||
}, [images, activeImage, onSelectImage, resetZoom]);
|
||||
|
||||
// Keyboard navigation & zoom shortcuts
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
@ -81,7 +91,7 @@ export default function ProductImageZoomModal({
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [isOpen, onClose, images, activeImage, resetZoom]);
|
||||
}, [isOpen, onClose, navigateImage, resetZoom]);
|
||||
|
||||
const handleZoomIn = () => {
|
||||
setScale((prev) => Math.min(prev + 0.5, 4));
|
||||
@ -158,15 +168,7 @@ export default function ProductImageZoomModal({
|
||||
}
|
||||
};
|
||||
|
||||
const navigateImage = (direction: number) => {
|
||||
if (!images || images.length <= 1) return;
|
||||
const currentIndex = images.indexOf(activeImage);
|
||||
if (currentIndex === -1) return;
|
||||
let nextIndex = (currentIndex + direction) % images.length;
|
||||
if (nextIndex < 0) nextIndex = images.length - 1;
|
||||
onSelectImage(images[nextIndex]);
|
||||
resetZoom();
|
||||
};
|
||||
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "motion/react";
|
||||
import { User, ShoppingBag, Wallet, MapPin, LogOut, ChevronRight, Package, Calendar, UserCircle, ShoppingCart, Trash2, Edit2, Phone, Hash, CheckCircle2, ArrowUpCircle, ArrowDownCircle, Info, Clock, CheckCircle, Heart, Sparkles, MessageSquare, Send, Stethoscope, FileText, Lock, Key, Eye, EyeOff, ShieldCheck, Dog, X, Plus, CreditCard, Activity } from "lucide-react";
|
||||
import { User, ShoppingBag, Wallet, MapPin, LogOut, ChevronRight, Package, Calendar, UserCircle, ShoppingCart, Trash2, Edit2, Phone, Hash, CheckCircle2, ArrowUpCircle, ArrowDownCircle, Info, Clock, CheckCircle, Heart, Sparkles, MessageSquare, Send, Stethoscope, FileText, Lock, Key, Eye, EyeOff, ShieldCheck, Dog, X, Plus, CreditCard, Activity, RotateCcw } from "lucide-react";
|
||||
import { OrderRowSkeleton } from "./Skeleton";
|
||||
import { useUserStore, Address } from "../lib/store/userStore";
|
||||
import { useCartStore } from "../lib/store/cartStore";
|
||||
@ -987,37 +987,104 @@ export default function UserDashboard() {
|
||||
<p className="text-medical-gray-400 font-bold">تراکنشی یافت نشد.</p>
|
||||
</div>
|
||||
) : (
|
||||
(profile?.transactions || []).map((trx) => (
|
||||
<div key={trx.id} className="p-4 sm:p-6 bg-white border border-medical-gray-100 rounded-[1.5rem] sm:rounded-[2rem] flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-6 hover:shadow-lg hover:border-canina-blue/10 transition-all group">
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
<div className={cn(
|
||||
"w-10 h-10 sm:w-12 sm:h-12 rounded-xl sm:rounded-2xl flex items-center justify-center transition-colors flex-shrink-0",
|
||||
trx.type === 'top_up' ? "bg-green-50 text-green-500 group-hover:bg-green-500 group-hover:text-white" : "bg-red-50 text-red-500 group-hover:bg-red-500 group-hover:text-white"
|
||||
)}>
|
||||
{trx.type === 'top_up' ? <ArrowUpCircle className="w-5 h-5 sm:w-6 sm:h-6" /> : <ShoppingCart className="w-5 h-5 sm:w-6 sm:h-6" />}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs sm:text-sm font-black text-medical-gray-900 truncate">{trx.type === 'top_up' ? "افزایش موجودی (شارژ)" : "پرداخت سفارش"}</div>
|
||||
<div className="text-[10px] font-bold text-medical-gray-400 flex flex-wrap items-center gap-1 sm:gap-2 mt-0.5">
|
||||
<span className="truncate">کد: {trx.id}</span>
|
||||
<span className="w-1 h-1 bg-medical-gray-200 rounded-full hidden sm:inline-block" />
|
||||
<span>{toPersian(new Date(trx.date).toLocaleDateString("fa-IR"))}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right sm:text-left flex sm:flex-col justify-between items-center sm:items-end border-t sm:border-t-0 pt-2 sm:pt-0 border-medical-gray-50">
|
||||
<div className={cn(
|
||||
"text-base sm:text-lg font-black italic",
|
||||
trx.type === 'top_up' ? "text-green-600" : "text-red-500"
|
||||
)}>
|
||||
{trx.type === 'top_up' ? "+" : ""}{toPersian(trx.amount.toLocaleString())} <span className="text-xs not-italic">تومان</span>
|
||||
</div>
|
||||
<div className="text-[9px] font-black uppercase text-green-500 tracking-widest sm:mt-1">
|
||||
تایید شده
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
(profile?.transactions || []).map((trx) => {
|
||||
const desc = trx.description || "";
|
||||
const isRefund = trx.type === 'refund' || desc.includes('استرداد') || desc.includes('لغو') || desc.includes('عودت');
|
||||
const isAdmin = desc.includes('مدیر') || desc.includes('دستی');
|
||||
const isOnlineTopUp = trx.type === 'top_up' && !isRefund && !isAdmin;
|
||||
const isPurchase = trx.type === 'purchase' || desc.includes('پرداخت سفارش');
|
||||
|
||||
let title = "تراکنش کیف پول";
|
||||
let badgeText = "تراکنش";
|
||||
let badgeClass = "bg-medical-gray-100 text-medical-gray-600 border-medical-gray-200";
|
||||
let iconBgClass = "bg-medical-gray-50 text-medical-gray-600 group-hover:bg-medical-gray-700 group-hover:text-white";
|
||||
let IconComponent = ArrowUpCircle;
|
||||
let isPositive = trx.type === 'top_up' || trx.type === 'refund';
|
||||
|
||||
if (isRefund) {
|
||||
title = "استرداد وجه سفارش";
|
||||
badgeText = "استرداد وجه";
|
||||
badgeClass = "bg-blue-50 text-blue-700 border-blue-200/60";
|
||||
iconBgClass = "bg-blue-50 text-blue-600 group-hover:bg-blue-600 group-hover:text-white";
|
||||
IconComponent = RotateCcw;
|
||||
isPositive = true;
|
||||
} else if (isAdmin) {
|
||||
title = isPositive ? "شارژ توسط مدیریت" : "کسر توسط مدیریت";
|
||||
badgeText = "تغییر توسط پشتیبانی";
|
||||
badgeClass = "bg-amber-50 text-amber-700 border-amber-200/60";
|
||||
iconBgClass = "bg-amber-50 text-amber-600 group-hover:bg-amber-600 group-hover:text-white";
|
||||
IconComponent = ShieldCheck;
|
||||
} else if (isOnlineTopUp) {
|
||||
title = "افزایش اعتبار آنلاین (درگاه پرداخت)";
|
||||
badgeText = "شارژ درگاه";
|
||||
badgeClass = "bg-emerald-50 text-emerald-700 border-emerald-200/60";
|
||||
iconBgClass = "bg-emerald-50 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white";
|
||||
IconComponent = ArrowUpCircle;
|
||||
isPositive = true;
|
||||
} else if (isPurchase) {
|
||||
title = "پرداخت سفارش از کیف پول";
|
||||
badgeText = "پرداخت سفارش";
|
||||
badgeClass = "bg-rose-50 text-rose-700 border-rose-200/60";
|
||||
iconBgClass = "bg-rose-50 text-rose-600 group-hover:bg-rose-600 group-hover:text-white";
|
||||
IconComponent = ShoppingCart;
|
||||
isPositive = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={trx.id} className="p-4 sm:p-6 bg-white border border-medical-gray-100 rounded-[1.5rem] sm:rounded-[2rem] flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-6 hover:shadow-lg hover:border-canina-blue/20 transition-all group">
|
||||
<div className="flex items-start sm:items-center gap-3 sm:gap-4 flex-1 min-w-0">
|
||||
<div className={cn(
|
||||
"w-10 h-10 sm:w-12 sm:h-12 rounded-xl sm:rounded-2xl flex items-center justify-center transition-colors flex-shrink-0 mt-0.5 sm:mt-0",
|
||||
iconBgClass
|
||||
)}>
|
||||
<IconComponent className="w-5 h-5 sm:w-6 sm:h-6" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-2 mb-1">
|
||||
<span className="text-xs sm:text-sm font-black text-medical-gray-900">{title}</span>
|
||||
<span className={cn("text-[10px] px-2.5 py-0.5 rounded-full border font-black", badgeClass)}>
|
||||
{badgeText}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{desc && (
|
||||
<p className="text-xs font-bold text-medical-gray-700 line-clamp-2 leading-relaxed mb-1.5">
|
||||
{desc}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="text-[10px] font-bold text-medical-gray-400 flex flex-wrap items-center gap-x-2.5 gap-y-1">
|
||||
<span className="font-mono text-medical-gray-500">کد: {trx.id}</span>
|
||||
{trx.transactionReference && (
|
||||
<>
|
||||
<span className="w-1 h-1 bg-medical-gray-200 rounded-full" />
|
||||
<span className="font-mono text-canina-blue">مرجع/پیگیری: {trx.transactionReference}</span>
|
||||
</>
|
||||
)}
|
||||
<span className="w-1 h-1 bg-medical-gray-200 rounded-full hidden sm:inline-block" />
|
||||
<span>{toPersian(new Date(trx.date).toLocaleDateString("fa-IR"))}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right sm:text-left flex sm:flex-col justify-between items-center sm:items-end border-t sm:border-t-0 pt-3 sm:pt-0 border-medical-gray-50 flex-shrink-0">
|
||||
<div className={cn(
|
||||
"text-base sm:text-lg font-black italic",
|
||||
isPositive ? "text-emerald-600" : "text-rose-500"
|
||||
)}>
|
||||
{isPositive ? "+" : "-"}{toPersian(trx.amount.toLocaleString())} <span className="text-xs not-italic">تومان</span>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"text-[10px] font-black tracking-wide sm:mt-1 px-2 py-0.5 rounded-full",
|
||||
trx.status === 'failed' ? "bg-rose-50 text-rose-600" :
|
||||
trx.status === 'pending' ? "bg-amber-50 text-amber-600" :
|
||||
"bg-emerald-50 text-emerald-600"
|
||||
)}>
|
||||
{trx.status === 'failed' ? "ناموفق" : trx.status === 'pending' ? "در انتظار تایید" : "موفق"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -24,10 +24,12 @@ export interface Address {
|
||||
|
||||
export interface Transaction {
|
||||
id: string;
|
||||
type: 'top_up' | 'purchase';
|
||||
type: 'top_up' | 'purchase' | 'refund';
|
||||
amount: number;
|
||||
date: string;
|
||||
status: 'success' | 'failed' | 'pending';
|
||||
description?: string;
|
||||
transactionReference?: string;
|
||||
}
|
||||
|
||||
interface UserProfile {
|
||||
@ -237,12 +239,14 @@ export const useUserStore = create<UserStore>()(
|
||||
const pd = profileData as unknown as Record<string, unknown>;
|
||||
const backendWallet = Number(profileData.walletBalance || 0);
|
||||
const backendCharity = Number(profileData.charityDonationTotal || 0);
|
||||
const backendTransactions = (pd.walletTransactions as Array<{ id: string; type: string; amount: number; createdAt: string; status: string }>)?.map((t) => ({
|
||||
const backendTransactions = (pd.walletTransactions as Array<{ id: string; type: string; amount: number; createdAt: string; status: string; description?: string; transactionReference?: string }>)?.map((t) => ({
|
||||
id: t.id,
|
||||
type: (t.type === 'deposit' ? 'top_up' : 'purchase') as Transaction['type'],
|
||||
type: (t.type === 'deposit' ? 'top_up' : t.type === 'refund' ? 'refund' : 'purchase') as Transaction['type'],
|
||||
amount: Number(t.amount),
|
||||
date: t.createdAt,
|
||||
status: (t.status === 'completed' ? 'success' : t.status === 'failed' ? 'failed' : 'pending') as Transaction['status']
|
||||
status: (t.status === 'completed' ? 'success' : t.status === 'failed' ? 'failed' : 'pending') as Transaction['status'],
|
||||
description: t.description || undefined,
|
||||
transactionReference: t.transactionReference || undefined
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user