krishnasales_mobile/src/components/dv/OrderDetail.tsx
2026-07-27 19:29:46 +05:30

309 lines
15 KiB
TypeScript

import { useEffect, useState } from 'react';
import { orderBookingClient } from '../../api/clients';
import { ORDER_BOOKING } from '../../api/config';
import { Card } from '../reusable/Card';
import { Spinner } from '../reusable/Spinner';
import { EmptyState } from '../reusable/EmptyState';
import { Store, User, Package, Calendar, Hash, Mail, Weight, List, MapPin, ArrowLeft } from 'lucide-react';
export interface WiredDetailViewProps {
instanceId: number | string;
columns?: 1 | 2 | 3;
onDataLoad?: (data: Record<string, unknown>) => void;
onBack?: () => void;
}
export function OrderDetail({ instanceId, onDataLoad, onBack }: WiredDetailViewProps) {
const [data, setData] = useState<Record<string, unknown> | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let live = true;
async function run() {
setLoading(true);
setError(null);
try {
const r = await orderBookingClient.detailView(ORDER_BOOKING.detailViews.ORDERS, instanceId);
if (live) {
setData(r.data || {});
if (onDataLoad) onDataLoad(r.data || {});
}
} catch (e) {
if (live) setError((e as { message?: string })?.message ?? 'Failed to load');
} finally {
if (live) setLoading(false);
}
}
run();
return () => { live = false; };
}, [instanceId]);
if (error) {
return (
<Card title={`Order #${instanceId}`}>
<EmptyState title={`Couldn't load record`} hint={error} />
</Card>
);
}
if (loading) {
return (
<Card title={`Order #${instanceId}`}>
<div className="py-8 flex justify-center"><Spinner label="Loading details..." /></div>
</Card>
);
}
if (!data || Object.keys(data).length === 0) {
return (
<Card title={`Order #${instanceId}`}>
<EmptyState title="No details found" />
</Card>
);
}
const remainingData = { ...data };
const extract = (key: string, obj: any = data) => {
if (obj && key in obj) {
const val = obj[key];
delete obj[key];
return val;
}
return null;
};
// State
const stateName = extract('current_state_name', remainingData) || 'Ordered';
delete remainingData.current_state_id;
// Order Details
const orderId = extract('order_id', remainingData) || '-';
let dateOfOrder = extract('date_of_order_3', remainingData);
if (dateOfOrder) {
dateOfOrder = new Date(dateOfOrder as string).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
} else {
dateOfOrder = '-';
}
// SO Info
const soKey = Object.keys(remainingData).find(k => k.endsWith('__user_id')) || 'so_name';
const soRaw = extract(soKey, remainingData);
let soName = '-';
let soEmail = '-';
let soId = '-';
if (soRaw && typeof soRaw === 'object') {
soName = (soRaw as any).name || '-';
soEmail = (soRaw as any).email || '-';
soId = (soRaw as any).user_id || (soRaw as any).id || (soRaw as any).uid || '-';
}
const soInitials = soName.length > 2 ? soName.substring(0,2).toUpperCase() : 'SO';
// Store Info
const storeRaw = extract('select_store', remainingData);
let storeName = '-';
let distributorName = '-';
let routeName = '-';
let routeCode = '-';
if (storeRaw && typeof storeRaw === 'object') {
storeName = (storeRaw as any).business_name || '-';
distributorName = (storeRaw as any).distributor_name || '-';
routeName = (storeRaw as any).route_name || '-';
routeCode = (storeRaw as any).route_code || '-';
}
// Line Items
const orderDetailsGrid = extract('order_details_3', remainingData);
const itemsArray = Array.isArray(orderDetailsGrid) ? orderDetailsGrid : [];
// Totals
const totalBags = extract('total_bags_3', remainingData) || 0;
const totalKgs = extract('total_kgs_3', remainingData) || 0;
return (
<div className="flex flex-col gap-4 bg-transparent p-1">
{/* Top Section */}
<div className="border border-slate-200 rounded-xl p-6 bg-[var(--tiles-card-bg)] shadow-sm flex flex-col md:flex-row md:items-start justify-between gap-4">
<div className="flex flex-col">
<div className="flex items-center gap-3 mb-1">
{onBack && (
<button onClick={onBack} className="p-1.5 text-slate-800 hover:text-slate-800 hover:bg-slate-100 rounded-full transition-colors focus:outline-none -ml-2">
<ArrowLeft size={18} strokeWidth={2.5} />
</button>
)}
<div className="text-[10px] font-bold text-slate-800 uppercase tracking-wider">Order ID</div>
</div>
<div className={`text-2xl font-black text-slate-900 mb-2 ${onBack ? 'ml-8' : ''}`}>{String(orderId)}</div>
<div className={`flex items-center gap-3 text-xs text-slate-800 font-medium ${onBack ? 'ml-8' : ''}`}>
<span className="flex items-center gap-1.5"><Calendar size={14} /> {String(dateOfOrder)}</span>
<span className="flex items-center gap-1.5"><Hash size={14} /> Instance #{instanceId}</span>
</div>
</div>
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '16px', display: 'flex', flexDirection: 'column', gap: '4px', minWidth: '240px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '4px' }}>
<Store size={14} /> Store
</div>
<div style={{ fontSize: '14px', fontWeight: 800, color: 'var(--primary-color)' }}>{String(storeName)}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '12px', color: 'var(--primary-color)', marginTop: '4px', fontWeight: 500 }}>
<span className="flex items-center gap-1.5"><MapPin size={13} /> {String(routeCode)} - {String(routeName)}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '12px', color: 'var(--primary-color)', marginTop: '2px', fontWeight: 500 }}>
<span className="flex items-center gap-1.5"><User size={13} /> {String(distributorName)}</span>
</div>
</div>
</div>
{/* Middle Stats Section */}
<div className="grid grid-cols-3 gap-3">
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL BAGS</div>
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{String(totalBags)}</div>
</div>
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL KGS</div>
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{Number(totalKgs).toLocaleString()}</div>
</div>
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>LINE ITEMS</div>
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{itemsArray.length}</div>
</div>
</div>
{/* Bottom Section */}
<div className="flex flex-col lg:flex-row gap-4">
{/* Left Table */}
<div className="flex-[2] border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden flex flex-col min-w-0" style={{ padding: '20px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
<div style={{ backgroundColor: '#e6f9ed', padding: '6px', borderRadius: '50%' }}>
<Package size={16} color="#0d7d81" />
</div>
<span style={{ color: '#0d7d81', fontWeight: 600, fontSize: '13px', letterSpacing: '0.05em' }}>
ORDER PRODUCTS
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{itemsArray.map((item: any, idx: number) => {
const productName = String(item.product_name_3 || item.product_name || 'Unknown Product');
const skuCodeRaw = item.sku_code_3 || item.sku_code || '';
const skuCode = isNaN(Number(skuCodeRaw)) || String(skuCodeRaw).length > 3 ? String(skuCodeRaw) : '';
const category = String(item.product_category_3 || item.product_category || '');
const brCode = String(item.br_code_3 || item.br_code || '');
const subtitleArr = [skuCode, category, brCode].filter(v => v && v !== '-' && v !== '—');
const subtitle = subtitleArr.join(' . ').toUpperCase();
const bagsNum = Number(item.bags_3 || item.bags || 0);
const bagsStr = String(bagsNum);
const skuNum = Number(item.sku_3 || item.sku || 0);
const rowKgsRaw = item.row_kgs_3 || item.row_kgs || item.weight;
const kgsNum = skuNum > 0 ? skuNum * bagsNum : Number(rowKgsRaw || 0);
const kgsStr = kgsNum > 0 ? `${kgsNum} kg` : '0 kg';
return (
<div key={idx} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#e8fbf0',
border: '1px solid #bcecd0',
borderRadius: '24px',
padding: '14px 20px',
}}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{productName}</span>
</div>
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{bagsStr} Bags</span>
<span style={{ color: 'var(--primary-color)', fontSize: '13px', marginLeft: '6px' }}>({kgsStr})</span>
</div>
</div>
);
})}
{itemsArray.length > 0 && (
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: '8px',
padding: '16px 20px',
border: '1px dashed #cbd5e1',
borderRadius: '24px'
}}>
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '13px' }}>TOTAL</span>
<div>
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '15px' }}>{totalBags} Bags</span>
<span style={{ color: 'var(--primary-color)', fontSize: '14px', marginLeft: '6px' }}>({Number(totalKgs).toLocaleString()} kg)</span>
</div>
</div>
)}
</div>
</div>
{/* Right Sidebar */}
<div className="flex-1 flex flex-col gap-4 min-w-[280px]">
{/* SO Card */}
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5">
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-800 uppercase tracking-wider mb-4">
<User size={14} className="text-slate-800" /> Sales Officer
</div>
<div className="flex items-center gap-3 mb-5">
<div className="w-10 h-10 rounded-full bg-slate-900 text-white flex items-center justify-center font-bold text-sm shrink-0">
{soInitials}
</div>
<div>
<div className="text-sm font-bold text-slate-900">{String(soName)}</div>
<div className="text-[10px] text-slate-800">Sales Officer - ID {String(soId)}</div>
</div>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0">
<Mail size={12} className="text-slate-800" />
</div>
<div className="overflow-hidden">
<div className="text-[10px] font-bold text-slate-800 uppercase tracking-wider mb-0.5">Email</div>
<div className="text-xs font-semibold text-slate-900 truncate">{String(soEmail)}</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0">
<Hash size={12} className="text-slate-800" />
</div>
<div>
<div className="text-[10px] font-bold text-slate-800 uppercase tracking-wider mb-0.5">User ID</div>
<div className="text-xs font-semibold text-slate-900">{String(soId)}</div>
</div>
</div>
</div>
</div>
{/* Order Meta */}
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5">
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-800 uppercase tracking-wider mb-4">
<Hash size={14} className="text-slate-800" /> Order Meta
</div>
<div className="flex flex-col gap-3">
<div className="flex justify-between items-center text-xs">
<span className="text-slate-800">Instance</span>
<span className="font-bold text-slate-900">#{instanceId}</span>
</div>
<div className="flex justify-between items-center text-xs">
<span className="text-slate-800">State</span>
<span className="font-bold text-slate-900">{String(stateName)}</span>
</div>
<div className="flex justify-between items-center text-xs">
<span className="text-slate-800">Date of Order</span>
<span className="font-bold text-slate-900">{String(dateOfOrder)}</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
}