order detail view is finished

This commit is contained in:
suryacp23 2026-07-17 18:13:42 +05:30
parent 2e57c47c7f
commit 8b022bfbce
2 changed files with 178 additions and 53 deletions

View File

@ -1,24 +1,37 @@
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';
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 || '—';
// Extract store lookup object if it exists
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
const dateKey = Object.keys(row).find(k => k.includes('date_of_order'));
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'));
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 isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('closed') || stateName.toLowerCase().includes('ordered');
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 gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3);
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);
if (totalKgsVal === 0 && gridVal.length > 0) {
gridVal.forEach(p => { totalKgsVal += Number(p.row_kgs || p.kgs || p.weight || 0); })
let totalBagsVal = Number(row.total_bags || row.total_bags_2 || row.total_bags_3 || row.total_quantity || row.quantity || 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 totalBags = formatValue(totalBagsVal);
return (
<div className="z-card">
@ -55,44 +75,142 @@ export function OrderCard({ row, fields }: { row: Record<string, any>; fields: a
<h2 className="z-card-store-name">{storeVal}</h2>
</div>
<div className="z-card-divider"></div>
<div className="z-card-footer">
<div className="z-card-footer-item">
<div className="z-card-footer-label"><Calendar size={14} /> DATE & TIME</div>
<span className="z-card-footer-value">{dateVal}</span>
</div>
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
<div className="z-card-footer-label"><Package size={14} /> TOTAL KGS</div>
<span className="z-card-footer-value">{totalKgs} kg</span>
</div>
</div>
{gridVal.length > 0 && (
<div className="z-card-details" style={{ display: 'block', borderTop: '1px solid #f1f5f9' }}>
<p className="z-card-meta" style={{ marginBottom: '8px' }}>Order Products</p>
<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: '13px', background: '#f8fafc', padding: '8px 12px', borderRadius: '8px' }}>
<span style={{ fontWeight: 600, color: '#334155' }}>{productName}</span>
<span style={{ fontWeight: 700, color: '#4f46e5' }}>{bags} Bags</span>
</div>
);
})}
{!isDetailView && (
<>
<div className="z-card-divider"></div>
<div className="z-card-footer">
<div className="z-card-footer-item">
<div className="z-card-footer-label"><Calendar size={14} /> DATE & TIME</div>
<span className="z-card-footer-value">{dateTimeStr}</span>
</div>
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
<div className="z-card-footer-label"><Package size={14} /> TOTAL KGS</div>
<span className="z-card-footer-value">{totalKgs} kg</span>
</div>
</div>
</div>
{gridVal.length > 0 && (
<div className="z-card-details" style={{ display: 'block', borderTop: '1px solid #f1f5f9' }}>
<p className="z-card-meta" style={{ marginBottom: '8px' }}>Order Products</p>
<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: '13px', background: '#f8fafc', padding: '8px 12px', borderRadius: '8px' }}>
<span style={{ fontWeight: 600, color: '#334155' }}>{productName}</span>
<span style={{ fontWeight: 700, color: '#4f46e5' }}>{bags} Bags</span>
</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>
);
}

View File

@ -1,20 +1,27 @@
import { orderBookingClient } from '../../api/clients';
import { ORDER_BOOKING } from '../../api/config';
import { DetailView } from './DetailView';
import { useDetailViewData } from './useDetailViewData';
import { OrderCard } from '../cards/OrderCard';
export interface WiredDetailViewProps {
instanceId: string | number;
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 (
<DetailView
client={orderBookingClient}
dvUid={ORDER_BOOKING.detailViews.ORDERS}
instanceId={instanceId}
title="Order Details"
columns={columns}
/>
<div className="pb-24">
<OrderCard row={data} fields={config?.fields || []} isDetailView={true} />
</div>
);
}