order detail view is finished
This commit is contained in:
parent
2e57c47c7f
commit
8b022bfbce
@ -1,24 +1,37 @@
|
|||||||
import { formatValue } from '../../lib/format';
|
import { formatValue } from '../../lib/format';
|
||||||
import { Store, Calendar, Package, ShoppingBag } from 'lucide-react';
|
import { Store, Calendar, ShoppingBag, Clock, Truck, Package } from 'lucide-react';
|
||||||
import './card.css';
|
import './card.css';
|
||||||
|
|
||||||
export function OrderCard({ row, fields }: { row: Record<string, any>; fields: any[] }) {
|
export function OrderCard({ row, fields, isDetailView = false }: { row: Record<string, any>; fields: any[]; isDetailView?: boolean }) {
|
||||||
const orderIdStr = row.order_id || '—';
|
const orderIdStr = row.order_id || '—';
|
||||||
|
|
||||||
// Extract store lookup object if it exists
|
// Extract store lookup object if it exists
|
||||||
const storeObj = (row.select_store || row.store) as Record<string, unknown> | null;
|
const storeObj = (row.select_store || row.store) as Record<string, unknown> | null;
|
||||||
const storeVal = formatValue(storeObj?.business_name || row.business_name || row.store_name || '—');
|
const storeVal = formatValue(storeObj?.business_name || storeObj?.store_name || row.business_name || row.store_name || '—');
|
||||||
|
const routeName = formatValue(storeObj?.route_name || storeObj?.route || row.route_name || row.route || '—');
|
||||||
|
const areaName = formatValue(storeObj?.area || row.area || row.area_name || '—');
|
||||||
|
const distName = formatValue(storeObj?.distributor_name || '—');
|
||||||
|
|
||||||
|
// Extract User object
|
||||||
|
const userKey = Object.keys(row).find(k => k.includes('user_id'));
|
||||||
|
const userObj = userKey ? (row[userKey] as any) : null;
|
||||||
|
const userName = formatValue(userObj?.name || row.sales_officer || row.user_name || '—');
|
||||||
|
const userEmail = formatValue(userObj?.email || row.performed_by_email || row.user_email || '—');
|
||||||
|
const initials = userName !== '—' ? String(userName).substring(0, 2).toUpperCase() : 'SO';
|
||||||
|
|
||||||
// Extract date
|
// Extract date
|
||||||
const dateKey = Object.keys(row).find(k => k.includes('date_of_order'));
|
const dateKey = Object.keys(row).find(k => k.includes('date_of_order'));
|
||||||
let dateVal = dateKey && row[dateKey] ? formatValue(row[dateKey]) : '—';
|
let dateVal = dateKey && row[dateKey] ? formatValue(row[dateKey]) : '—';
|
||||||
|
|
||||||
// Try to append time if it exists
|
let timeVal = '—';
|
||||||
const timeKey = Object.keys(row).find(k => k.includes('time_of_order'));
|
const timeKey = Object.keys(row).find(k => k.includes('time_of_order'));
|
||||||
if (timeKey && row[timeKey]) {
|
if (timeKey && row[timeKey]) {
|
||||||
dateVal += ` ${formatValue(row[timeKey])}`;
|
timeVal = formatValue(row[timeKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dateTimeStr = dateVal;
|
||||||
|
if (timeVal !== '—') dateTimeStr += ` ${timeVal}`;
|
||||||
|
|
||||||
const stateName = String(row.current_state_name || '');
|
const stateName = String(row.current_state_name || '');
|
||||||
const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('closed') || stateName.toLowerCase().includes('ordered');
|
const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('closed') || stateName.toLowerCase().includes('ordered');
|
||||||
const productiveLabel = stateName || 'Pending';
|
const productiveLabel = stateName || 'Pending';
|
||||||
@ -30,13 +43,20 @@ export function OrderCard({ row, fields }: { row: Record<string, any>; fields: a
|
|||||||
const gridField = fields.find(f => f.data_type === 'grid' || String(f.field_key).includes('order_details'));
|
const gridField = fields.find(f => f.data_type === 'grid' || String(f.field_key).includes('order_details'));
|
||||||
const gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3);
|
const gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3);
|
||||||
const gridVal = Array.isArray(gridValRaw) ? gridValRaw : [];
|
const gridVal = Array.isArray(gridValRaw) ? gridValRaw : [];
|
||||||
|
const totalItems = gridVal.length;
|
||||||
|
|
||||||
// Total Kgs
|
// Totals
|
||||||
let totalKgsVal = Number(row.total_kgs || row.total_kgs_2 || row.total_kgs_3 || row.total_weight || row.weight || 0);
|
let totalKgsVal = Number(row.total_kgs || row.total_kgs_2 || row.total_kgs_3 || row.total_weight || row.weight || 0);
|
||||||
if (totalKgsVal === 0 && gridVal.length > 0) {
|
let totalBagsVal = Number(row.total_bags || row.total_bags_2 || row.total_bags_3 || row.total_quantity || row.quantity || 0);
|
||||||
gridVal.forEach(p => { totalKgsVal += Number(p.row_kgs || p.kgs || p.weight || 0); })
|
|
||||||
|
if (totalKgsVal === 0 && totalBagsVal === 0 && gridVal.length > 0) {
|
||||||
|
gridVal.forEach(p => {
|
||||||
|
totalKgsVal += Number(p.row_kgs || p.kgs || p.weight || 0);
|
||||||
|
totalBagsVal += Number(p.bags || p.total_bags || p.quantity || 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const totalKgs = formatValue(totalKgsVal);
|
const totalKgs = formatValue(totalKgsVal);
|
||||||
|
const totalBags = formatValue(totalBagsVal);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-card">
|
<div className="z-card">
|
||||||
@ -55,19 +75,19 @@ export function OrderCard({ row, fields }: { row: Record<string, any>; fields: a
|
|||||||
<h2 className="z-card-store-name">{storeVal}</h2>
|
<h2 className="z-card-store-name">{storeVal}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!isDetailView && (
|
||||||
|
<>
|
||||||
<div className="z-card-divider"></div>
|
<div className="z-card-divider"></div>
|
||||||
|
|
||||||
<div className="z-card-footer">
|
<div className="z-card-footer">
|
||||||
<div className="z-card-footer-item">
|
<div className="z-card-footer-item">
|
||||||
<div className="z-card-footer-label"><Calendar size={14} /> DATE & TIME</div>
|
<div className="z-card-footer-label"><Calendar size={14} /> DATE & TIME</div>
|
||||||
<span className="z-card-footer-value">{dateVal}</span>
|
<span className="z-card-footer-value">{dateTimeStr}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
|
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
|
||||||
<div className="z-card-footer-label"><Package size={14} /> TOTAL KGS</div>
|
<div className="z-card-footer-label"><Package size={14} /> TOTAL KGS</div>
|
||||||
<span className="z-card-footer-value">{totalKgs} kg</span>
|
<span className="z-card-footer-value">{totalKgs} kg</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{gridVal.length > 0 && (
|
{gridVal.length > 0 && (
|
||||||
<div className="z-card-details" style={{ display: 'block', borderTop: '1px solid #f1f5f9' }}>
|
<div className="z-card-details" style={{ display: 'block', borderTop: '1px solid #f1f5f9' }}>
|
||||||
<p className="z-card-meta" style={{ marginBottom: '8px' }}>Order Products</p>
|
<p className="z-card-meta" style={{ marginBottom: '8px' }}>Order Products</p>
|
||||||
@ -93,6 +113,104 @@ export function OrderCard({ row, fields }: { row: Record<string, any>; fields: a
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isDetailView && (
|
||||||
|
<>
|
||||||
|
{distName !== '—' && (
|
||||||
|
<div className="z-card-store-row" style={{ marginBottom: '16px', marginTop: '12px' }}>
|
||||||
|
<Truck size={18} className="z-card-store-icon" />
|
||||||
|
<h2 className="z-card-store-name text-gray-700 text-sm">{distName}</h2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Route & Assignment */}
|
||||||
|
<div className="z-card-route">
|
||||||
|
<div>
|
||||||
|
<p className="z-card-grid-item-label">Route</p>
|
||||||
|
<p className="z-card-route-val">{routeName}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="z-card-grid-item-label">Area</p>
|
||||||
|
<p className="z-card-route-val">{areaName}</p>
|
||||||
|
</div>
|
||||||
|
<div className="z-card-so-box">
|
||||||
|
<p className="z-card-grid-item-label">Sales Officer</p>
|
||||||
|
<div className="z-card-so-info">
|
||||||
|
<div className="z-card-so-avatar">
|
||||||
|
{initials}
|
||||||
|
</div>
|
||||||
|
<div className="z-card-so-details">
|
||||||
|
<p className="z-card-so-name">{userName}</p>
|
||||||
|
{userEmail !== '—' && (
|
||||||
|
<p className="z-card-so-email">{userEmail}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="z-card-route" style={{ marginTop: '1rem', paddingTop: '16px', gridTemplateColumns: 'repeat(2, 1fr)' }}>
|
||||||
|
<div>
|
||||||
|
<p className="z-card-grid-item-label flex items-center gap-1"><Calendar size={12} /> DATE</p>
|
||||||
|
<p className="z-card-route-val">{dateVal}</p>
|
||||||
|
</div>
|
||||||
|
{timeVal !== '—' && (
|
||||||
|
<div>
|
||||||
|
<p className="z-card-grid-item-label flex items-center gap-1"><Clock size={12} /> TIME</p>
|
||||||
|
<p className="z-card-route-val">{timeVal}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{gridVal.length > 0 && (
|
||||||
|
<div className="mt-6 border-t border-gray-100 pt-6">
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
||||||
|
<p className="z-card-meta" style={{ margin: 0, color: '#334155', fontWeight: 700 }}>Order Products</p>
|
||||||
|
<span className="text-xs font-bold text-gray-800 bg-gray-100 px-3 py-1 rounded-full border border-gray-200">
|
||||||
|
{totalItems} {totalItems === 1 ? 'Item' : 'Items'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
{gridVal.map((item, i) => {
|
||||||
|
const getVal = (key: string) => {
|
||||||
|
if (item[key] !== undefined) return item[key];
|
||||||
|
const keyPattern = new RegExp(`^${key}(_\\d+)?$`);
|
||||||
|
const match = Object.keys(item).find(k => keyPattern.test(k) || k.includes(key));
|
||||||
|
return match ? item[match] : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const productName = formatValue(getVal('product_name') || getVal('product_category') || 'Unknown Product');
|
||||||
|
const bags = formatValue(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '14px', background: '#f8fafc', padding: '12px 16px', borderRadius: '8px', border: '1px solid #e2e8f0' }}>
|
||||||
|
<span style={{ fontWeight: 700, color: '#1e293b' }}>{productName}</span>
|
||||||
|
<span style={{ fontWeight: 800, color: '#3730a3' }}>{bags} Bags</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Logistics Totals at the bottom */}
|
||||||
|
{(Number(totalKgsVal) > 0 || Number(totalBagsVal) > 0) && (
|
||||||
|
<div className="mt-4 flex justify-between items-center bg-gray-50 p-4 rounded-xl border border-gray-200 shadow-sm">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-[11px] uppercase tracking-wider font-bold text-gray-700">Total Weight</span>
|
||||||
|
<span className="text-xl font-black text-gray-900 mt-1">{totalKgs} <span className="text-sm font-bold text-gray-600">Kgs</span></span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-right">
|
||||||
|
<span className="text-[11px] uppercase tracking-wider font-bold text-gray-700">Total Quantity</span>
|
||||||
|
<span className="text-xl font-black text-gray-900 mt-1">{totalBags} <span className="text-sm font-bold text-gray-600">Bags</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,27 @@
|
|||||||
import { orderBookingClient } from '../../api/clients';
|
import { orderBookingClient } from '../../api/clients';
|
||||||
import { ORDER_BOOKING } from '../../api/config';
|
import { ORDER_BOOKING } from '../../api/config';
|
||||||
import { DetailView } from './DetailView';
|
import { useDetailViewData } from './useDetailViewData';
|
||||||
|
import { OrderCard } from '../cards/OrderCard';
|
||||||
|
|
||||||
export interface WiredDetailViewProps {
|
export interface WiredDetailViewProps {
|
||||||
instanceId: string | number;
|
instanceId: string | number;
|
||||||
columns?: 1 | 2 | 3;
|
columns?: 1 | 2 | 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OrderDetail({ instanceId, columns = 2 }: WiredDetailViewProps) {
|
export function OrderDetail({ instanceId }: WiredDetailViewProps) {
|
||||||
|
const { data, config, loading, error } = useDetailViewData(
|
||||||
|
orderBookingClient,
|
||||||
|
ORDER_BOOKING.detailViews.ORDERS,
|
||||||
|
instanceId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) return <div className="p-4 text-gray-500">Loading Order Details...</div>;
|
||||||
|
if (error) return <div className="p-4 text-red-500">{error}</div>;
|
||||||
|
if (!data) return <div className="p-4 text-gray-500">No data found.</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailView
|
<div className="pb-24">
|
||||||
client={orderBookingClient}
|
<OrderCard row={data} fields={config?.fields || []} isDetailView={true} />
|
||||||
dvUid={ORDER_BOOKING.detailViews.ORDERS}
|
</div>
|
||||||
instanceId={instanceId}
|
|
||||||
title="Order Details"
|
|
||||||
columns={columns}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user