feat(orders): update API routes and enhance order models for improved data handling
This commit is contained in:
parent
aec7db2c19
commit
5cda2bd5d2
|
|
@ -38,11 +38,11 @@ export const API_ROUTES = {
|
||||||
DELETE_PERMISSION: (id: string) => `permissions/${id}`,
|
DELETE_PERMISSION: (id: string) => `permissions/${id}`,
|
||||||
|
|
||||||
// Product Options APIs (non-admin)
|
// Product Options APIs (non-admin)
|
||||||
GET_PRODUCT_OPTIONS: "api/v1/product-options",
|
GET_PRODUCT_OPTIONS: "products/options",
|
||||||
GET_PRODUCT_OPTION: (id: string) => `api/v1/product-options/${id}`,
|
GET_PRODUCT_OPTION: (id: string) => `products/options/${id}`,
|
||||||
CREATE_PRODUCT_OPTION: "api/v1/product-options",
|
CREATE_PRODUCT_OPTION: "products/options",
|
||||||
UPDATE_PRODUCT_OPTION: (id: string) => `api/v1/product-options/${id}`,
|
UPDATE_PRODUCT_OPTION: (id: string) => `products/options/${id}`,
|
||||||
DELETE_PRODUCT_OPTION: (id: string) => `api/v1/product-options/${id}`,
|
DELETE_PRODUCT_OPTION: (id: string) => `products/options/${id}`,
|
||||||
|
|
||||||
// Categories APIs (non-admin)
|
// Categories APIs (non-admin)
|
||||||
GET_CATEGORIES: "api/v1/products/categories",
|
GET_CATEGORIES: "api/v1/products/categories",
|
||||||
|
|
@ -79,7 +79,7 @@ export const API_ROUTES = {
|
||||||
DELETE_IMAGE: (imageId: string) => `api/v1/products/images/${imageId}`,
|
DELETE_IMAGE: (imageId: string) => `api/v1/products/images/${imageId}`,
|
||||||
|
|
||||||
// Landing Hero APIs
|
// Landing Hero APIs
|
||||||
GET_LANDING_HERO: "api/v1/settings/landing/hero", // non-admin
|
GET_LANDING_HERO: "settings/landing/hero", // non-admin
|
||||||
UPDATE_LANDING_HERO: "settings/landing/hero", // admin
|
UPDATE_LANDING_HERO: "settings/landing/hero", // admin
|
||||||
|
|
||||||
// Discount Codes APIs
|
// Discount Codes APIs
|
||||||
|
|
|
||||||
|
|
@ -22,25 +22,37 @@ export interface OrderItem {
|
||||||
product_image?: string;
|
product_image?: string;
|
||||||
variant_id?: number;
|
variant_id?: number;
|
||||||
variant_name?: string;
|
variant_name?: string;
|
||||||
|
product_variant_id?: number;
|
||||||
|
product_variant_name?: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
total_price: number;
|
total_price: number;
|
||||||
discount_amount?: number;
|
discount_amount?: number;
|
||||||
|
weight?: number;
|
||||||
|
final_weight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderAddress {
|
export interface OrderAddress {
|
||||||
id: number;
|
id: number;
|
||||||
type: "billing" | "shipping";
|
type?: "billing" | "shipping";
|
||||||
first_name: string;
|
// legacy fields
|
||||||
last_name: string;
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
company?: string;
|
company?: string;
|
||||||
address_line_1: string;
|
address_line_1?: string;
|
||||||
address_line_2?: string;
|
address_line_2?: string;
|
||||||
city: string;
|
city?: string;
|
||||||
state: string;
|
state?: string;
|
||||||
postal_code: string;
|
postal_code?: string;
|
||||||
country: string;
|
country?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
// new fields
|
||||||
|
name?: string;
|
||||||
|
address?: string;
|
||||||
|
region?: string;
|
||||||
|
plaque?: number;
|
||||||
|
unit?: number;
|
||||||
|
receiving_address?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderPayment {
|
export interface OrderPayment {
|
||||||
|
|
@ -65,18 +77,37 @@ export interface Order {
|
||||||
order: {
|
order: {
|
||||||
id: number;
|
id: number;
|
||||||
order_number: string;
|
order_number: string;
|
||||||
customer: OrderCustomer;
|
customer?: OrderCustomer;
|
||||||
status: OrderStatus;
|
status: OrderStatus;
|
||||||
items: OrderItem[];
|
items: OrderItem[];
|
||||||
billing_address: OrderAddress;
|
billing_address: OrderAddress;
|
||||||
shipping_address: OrderAddress;
|
shipping_address: OrderAddress;
|
||||||
payment: OrderPayment;
|
payment?: OrderPayment;
|
||||||
subtotal: number;
|
// new flat fields from API
|
||||||
tax_amount: number;
|
invoice_id?: number;
|
||||||
shipping_amount: number;
|
user_id?: number;
|
||||||
discount_amount: number;
|
user?: {
|
||||||
total_amount: number;
|
id: number;
|
||||||
final_total: number;
|
phone_number: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
national_code?: string;
|
||||||
|
verified: boolean;
|
||||||
|
avatar?: string;
|
||||||
|
};
|
||||||
|
payment_status?: PaymentStatus;
|
||||||
|
net_total?: number;
|
||||||
|
vat_total?: number;
|
||||||
|
shipping_total?: number;
|
||||||
|
discount_total?: number;
|
||||||
|
// legacy totals kept for compatibility
|
||||||
|
subtotal?: number;
|
||||||
|
tax_amount?: number;
|
||||||
|
shipping_amount?: number;
|
||||||
|
discount_amount?: number;
|
||||||
|
total_amount?: number;
|
||||||
|
final_total?: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
tracking_number?: string;
|
tracking_number?: string;
|
||||||
|
|
|
||||||
|
|
@ -169,10 +169,7 @@ const OrderDetailPage = () => {
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">تاریخ آخرین بروزرسانی</h4>
|
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">تاریخ آخرین بروزرسانی</h4>
|
||||||
<p className="text-gray-600 dark:text-gray-400">{order?.updated_at ? formatDate(order.updated_at) : 'نامشخص'}</p>
|
<p className="text-gray-600 dark:text-gray-400">{order?.updated_at ? formatDate(order.updated_at) : 'نامشخص'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{/* روش حمل و نقل در دادههای فعلی وجود ندارد */}
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">روش حمل و نقل</h4>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">{order?.shipping_method_id || 'تعریف نشده'}</p>
|
|
||||||
</div>
|
|
||||||
{order?.tracking_number && (
|
{order?.tracking_number && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">کد رهگیری</h4>
|
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-2">کد رهگیری</h4>
|
||||||
|
|
@ -207,55 +204,72 @@ const OrderDetailPage = () => {
|
||||||
<SectionTitle>محصولات سفارش</SectionTitle>
|
<SectionTitle>محصولات سفارش</SectionTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-0">
|
||||||
<div className="space-y-4">
|
{order?.items && order.items.length > 0 ? (
|
||||||
{order?.items && order.items.length > 0 ? order.items.map((item) => (
|
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<div key={item.id} className="flex items-center gap-4 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
<div className="grid grid-cols-12 px-6 py-3 text-xs text-gray-500 dark:text-gray-400">
|
||||||
{item.product_image && (
|
<div className="col-span-5">محصول</div>
|
||||||
<img
|
<div className="col-span-2 text-center">تعداد</div>
|
||||||
src={item.product_image}
|
<div className="col-span-2 text-center">وزن (گرم)</div>
|
||||||
alt={item.product_name}
|
<div className="col-span-1 text-center">قیمت واحد</div>
|
||||||
className="w-16 h-16 object-cover rounded-lg"
|
<div className="col-span-2 text-left">جمع</div>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex-1">
|
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{item.product_name || `محصول شناسه: ${item.product_id}`}
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{item.product_variant_name || `واریانت شناسه: ${item.product_variant_id}`}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-400 dark:text-gray-500">
|
|
||||||
شناسه آیتم: {item.id}
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center gap-4 mt-2">
|
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
تعداد: {item.quantity}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
قیمت واحد: {formatCurrency(item.unit_price || 0)}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
وزن: {item.weight || 0} کگ
|
|
||||||
</span>
|
|
||||||
{item.final_weight && item.final_weight !== item.weight && (
|
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
وزن نهایی: {item.final_weight} کگ
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{formatCurrency(item.total_price || 0)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)) : (
|
{order.items.map((item) => {
|
||||||
<p className="text-gray-500 dark:text-gray-400 text-center py-4">
|
const baseWeight = (item.final_weight ?? item.weight ?? 0) as number;
|
||||||
محصولی در این سفارش یافت نشد
|
const weightGr = Math.round(baseWeight * 1000);
|
||||||
</p>
|
const formatFa = (n: number) => new Intl.NumberFormat('fa-IR').format(n);
|
||||||
)}
|
return (
|
||||||
|
<div key={item.id} className="grid grid-cols-12 px-6 py-4 items-center">
|
||||||
|
<div className="col-span-5">
|
||||||
|
<div className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{item.product_name || `محصول شناسه: ${item.product_id}`}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{item.product_variant_name || `واریانت شناسه: ${item.product_variant_id}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 text-center text-sm text-gray-700 dark:text-gray-300">{formatFa(item.quantity || 0)}</div>
|
||||||
|
<div className="col-span-2 text-center text-sm text-gray-700 dark:text-gray-300">{formatFa(weightGr)}</div>
|
||||||
|
<div className="col-span-1 text-center text-sm text-gray-700 dark:text-gray-300">{formatCurrency(item.unit_price || 0)}</div>
|
||||||
|
<div className="col-span-2 text-left font-semibold text-gray-900 dark:text-gray-100">{formatCurrency(item.total_price || 0)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-500 dark:text-gray-400 text-center py-6">
|
||||||
|
محصولی در این سفارش یافت نشد
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* آدرسها - منتقل شده به زیر محصولات */}
|
||||||
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-orange-50 to-red-50 dark:from-gray-700 dark:to-gray-600 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-orange-100 dark:bg-orange-900 rounded-lg">
|
||||||
|
<MapPin className="h-5 w-5 text-orange-600 dark:text-orange-300" />
|
||||||
|
</div>
|
||||||
|
<SectionTitle>آدرسها</SectionTitle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 space-y-6">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-3">آدرس ارسال</h4>
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400 space-y-1 break-words">
|
||||||
|
<p className="break-words"><strong>نام:</strong> {order?.shipping_address?.name || 'نام نامشخص'}</p>
|
||||||
|
<p className="break-words"><strong>آدرس:</strong> {order?.shipping_address?.address || 'آدرس نامشخص'}</p>
|
||||||
|
<p className="break-words"><strong>شهر:</strong> {order?.shipping_address?.city || 'شهر نامشخص'}, <strong>استان:</strong> {order?.shipping_address?.state || 'استان نامشخص'}</p>
|
||||||
|
<p className="break-words"><strong>کشور:</strong> {order?.shipping_address?.country || 'کشور نامشخص'}</p>
|
||||||
|
<p className="break-words"><strong>منطقه:</strong> {order?.shipping_address?.region || 'منطقه نامشخص'}</p>
|
||||||
|
<p className="break-words"><strong>کد پستی:</strong> {order?.shipping_address?.postal_code || 'نامشخص'}</p>
|
||||||
|
{order?.shipping_address?.plaque && (
|
||||||
|
<p className="break-words"><strong>پلاک:</strong> {order.shipping_address.plaque}, <strong>واحد:</strong> {order.shipping_address.unit || 'ندارد'}</p>
|
||||||
|
)}
|
||||||
|
{order?.shipping_address?.receiving_address && (
|
||||||
|
<p className="break-words"><strong>آدرس تحویل:</strong> {order.shipping_address.receiving_address}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -308,72 +322,9 @@ const OrderDetailPage = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* اطلاعات مشتری */}
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
||||||
<div className="bg-gradient-to-r from-purple-50 to-pink-50 dark:from-gray-700 dark:to-gray-600 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="p-2 bg-purple-100 dark:bg-purple-900 rounded-lg">
|
|
||||||
<User className="h-5 w-5 text-purple-600 dark:text-purple-300" />
|
|
||||||
</div>
|
|
||||||
<SectionTitle>اطلاعات مشتری</SectionTitle>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-6">
|
|
||||||
{order?.customer ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-1">نام</h4>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
|
||||||
{order.customer.first_name || 'نامشخص'} {order.customer.last_name || ''}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Mail className="h-4 w-4 text-gray-400" />
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">{order.customer.email || 'ایمیل نامشخص'}</p>
|
|
||||||
</div>
|
|
||||||
{order.customer.phone && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Phone className="h-4 w-4 text-gray-400" />
|
|
||||||
<p className="text-gray-600 dark:text-gray-400" dir="ltr">{order.customer.phone}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-gray-500 dark:text-gray-400">اطلاعات مشتری در دسترس نیست</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* آدرسها */}
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
||||||
<div className="bg-gradient-to-r from-orange-50 to-red-50 dark:from-gray-700 dark:to-gray-600 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="p-2 bg-orange-100 dark:bg-orange-900 rounded-lg">
|
|
||||||
<MapPin className="h-5 w-5 text-orange-600 dark:text-orange-300" />
|
|
||||||
</div>
|
|
||||||
<SectionTitle>آدرسها</SectionTitle>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-6 space-y-6">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-3">آدرس ارسال</h4>
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
|
||||||
<p><strong>نام:</strong> {order?.shipping_address?.name || 'نام نامشخص'}</p>
|
|
||||||
<p><strong>آدرس:</strong> {order?.shipping_address?.address || 'آدرس نامشخص'}</p>
|
|
||||||
<p><strong>شهر:</strong> {order?.shipping_address?.city || 'شهر نامشخص'}, <strong>استان:</strong> {order?.shipping_address?.state || 'استان نامشخص'}</p>
|
|
||||||
<p><strong>کشور:</strong> {order?.shipping_address?.country || 'کشور نامشخص'}</p>
|
|
||||||
<p><strong>منطقه:</strong> {order?.shipping_address?.region || 'منطقه نامشخص'}</p>
|
|
||||||
<p><strong>کد پستی:</strong> {order?.shipping_address?.postal_code || 'نامشخص'}</p>
|
|
||||||
{order?.shipping_address?.plaque && (
|
|
||||||
<p><strong>پلاک:</strong> {order.shipping_address.plaque}, <strong>واحد:</strong> {order.shipping_address.unit || 'ندارد'}</p>
|
|
||||||
)}
|
|
||||||
{order?.shipping_address?.receiving_address && (
|
|
||||||
<p><strong>آدرس تحویل:</strong> {order.shipping_address.receiving_address}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* اطلاعات پرداخت */}
|
{/* اطلاعات پرداخت */}
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
|
|
@ -401,7 +352,7 @@ const OrderDetailPage = () => {
|
||||||
{(order?.discount_total || 0) > 0 && (
|
{(order?.discount_total || 0) > 0 && (
|
||||||
<div className="flex justify-between text-green-600 dark:text-green-400">
|
<div className="flex justify-between text-green-600 dark:text-green-400">
|
||||||
<span>تخفیف</span>
|
<span>تخفیف</span>
|
||||||
<span className="font-medium">-{formatCurrency(order.discount_total)}</span>
|
<span className="font-medium">-{formatCurrency(order?.discount_total || 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<hr className="border-gray-200 dark:border-gray-700" />
|
<hr className="border-gray-200 dark:border-gray-700" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue