import { useEffect, useState, type ReactNode } from 'react'; import { orderBookingClient } from '../../api/clients'; import { ORDER_BOOKING, APP_ID } from '../../api/config'; import { Spinner } from '../reusable/Spinner'; import { EmptyState } from '../reusable/EmptyState'; import { ShoppingCart, Store, User, ClipboardList, TrendingUp, MapPin, Clock, Phone, Mail, Truck, FileText, Camera, Image as ImageIcon, ArrowLeft, } from 'lucide-react'; export interface CallDetailProps { instanceId: number | string; selectedRow?: Record; potentialMiningAction?: ReactNode; refreshKey?: number; onBack?: () => void; onDataLoad?: (data: Record) => void; } export function CallDetail({ instanceId, selectedRow, potentialMiningAction, refreshKey, onBack, onDataLoad, }: CallDetailProps) { const [data, setData] = useState | null>(null); const [potentialData, setPotentialData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let live = true; async function run() { setLoading(true); setError(null); try { const r = await orderBookingClient.detailView(ORDER_BOOKING.detailViews.CALLS, instanceId); if (live) { setData(r.data || {}); if (onDataLoad) onDataLoad(r.data || {}); const storeCode = (r.data?.select_store as any)?.store_code_2 || (r.data?.select_store as any)?.code || (r.data as any)?.store_code || (r.data as any)?.store?.store_code; if (storeCode) { orderBookingClient.request<{ potential: { potential: any[] } }>( 'POST', '/api/papi2/potential-mining', { store_code: storeCode, instance_id: String(instanceId) }, { 'TemplateID': '146' } ).then(res => { if (live) setPotentialData(res.potential?.potential || []); }).catch(e => console.warn("Failed to fetch potential mining", e)); } } } catch (e) { if (live) setError((e as { message?: string })?.message ?? 'Failed to load details'); } finally { if (live) setLoading(false); } } run(); return () => { live = false; }; }, [instanceId, refreshKey]); if (error) { return (
); } if (loading) { return (
); } const rowSrc = selectedRow || data || {}; // Extract store info with fallbacks matching reference const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record; const storeName = selectStore.business_name || selectStore.store_name || rowSrc.store_name || 'No data'; const storeCode = selectStore.store_code_2 || selectStore.code || 'No data'; const ownerName = selectStore.owner_name || selectStore.contact_person || 'No data'; const phone = selectStore.phone_number?.phone || selectStore.phone_number?.phone_with_dial_code || selectStore.phone || 'No data'; const email = selectStore.email || 'No data'; const address = selectStore.complete_address || selectStore.address || 'No data'; const route = selectStore.route_name ? `${selectStore.route_name} (${selectStore.route_code || 'No data'}) • ${selectStore.sub_route || 'No data'}` : 'No data'; const areaLoc = selectStore.area || 'No data'; let lat = ''; let lng = ''; try { const locStr = selectStore.store_location; if (locStr) { const loc = typeof locStr === 'string' ? JSON.parse(locStr) : locStr; if (loc?.latitude && loc?.longitude) { lat = loc.latitude; lng = loc.longitude; } } } catch (e) { } // Distributor info const distCompany = selectStore.distributor_owner_name || 'No data'; const distContact = selectStore.distributor_name || 'No data'; const distPhone = selectStore.distributor_phone_number?.phone_with_dial_code || selectStore.distributor_phone_number?.phone || 'No data'; const distEmail = selectStore.distributor_email || 'No data'; // Performed by info const userObj = rowSrc['42b7f47d-96f0-4289-a6d9-38f455ad57dd__user_id'] || data?.['42b7f47d-96f0-4289-a6d9-38f455ad57dd__user_id'] as any; const salesOfficer = userObj?.name || String(rowSrc.created_by || data?.created_by || 'No data'); const officerEmail = userObj?.email || 'No data'; const officerJob = userObj?.job_title || 'Sales Officer'; // Extract activity timestamps const formatTimeOnly = (isoStr?: string, fallback = 'No data') => { if (!isoStr) return fallback; try { const d = new Date(isoStr); if (isNaN(d.getTime())) return fallback; const hours = String(d.getHours()).padStart(2, '0'); const mins = String(d.getMinutes()).padStart(2, '0'); return `${hours}:${mins}`; } catch { return fallback; } }; const formatDateOnly = (isoStr?: string, fallback = '') => { if (!isoStr) return fallback; try { const d = new Date(isoStr); if (isNaN(d.getTime())) return fallback; const day = String(d.getDate()).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0'); const year = String(d.getFullYear()).slice(2); return `${day}/${month}/${year}`; } catch { return fallback; } }; const formatFullDateTime = (isoStr?: string, fallback = 'No data') => { if (!isoStr) return fallback; try { const d = new Date(isoStr); if (isNaN(d.getTime())) return fallback; const day = d.getDate(); const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const month = monthNames[d.getMonth()]; const year = d.getFullYear(); let hours = d.getHours(); const minutes = String(d.getMinutes()).padStart(2, '0'); const ampm = hours >= 12 ? 'pm' : 'am'; hours = hours % 12; hours = hours ? hours : 12; return `${day} ${month} ${year}, ${String(hours).padStart(2, '0')}:${minutes} ${ampm}`; } catch { return fallback; } }; // Activity 1: Log Visit timestamp (42b7f47d-96f0-4289-a6d9-38f455ad57dd__created_at) const rawLogVisitTs = rowSrc['42b7f47d-96f0-4289-a6d9-38f455ad57dd__created_at'] ?? data?.['42b7f47d-96f0-4289-a6d9-38f455ad57dd__created_at']; // Activity 2: Productivity of Visit timestamp (11bd10f9-a001-470e-867f-33dee8eabe4b__created_at) const rawProductivityTs = rowSrc['11bd10f9-a001-470e-867f-33dee8eabe4b__created_at'] ?? data?.['11bd10f9-a001-470e-867f-33dee8eabe4b__created_at']; // Activity 3: Potential Mining timestamp (af8ac8df-d868-4f7d-88b2-123955d69c56__created_at) const rawPotentialMiningTs = rowSrc['af8ac8df-d868-4f7d-88b2-123955d69c56__created_at'] ?? data?.['af8ac8df-d868-4f7d-88b2-123955d69c56__created_at']; // Activity 4: Place Order timestamp (f66403d7-e31f-4d64-bcfd-ff5485aec8b8__created_at) const rawPlaceOrderTs = rowSrc['f66403d7-e31f-4d64-bcfd-ff5485aec8b8__created_at'] ?? data?.['f66403d7-e31f-4d64-bcfd-ff5485aec8b8__created_at']; const isLogVisitDone = rawLogVisitTs != null && String(rawLogVisitTs).trim() !== ''; const isProductivityDone = rawProductivityTs != null && String(rawProductivityTs).trim() !== ''; const isPotentialMiningDone = rawPotentialMiningTs != null && String(rawPotentialMiningTs).trim() !== ''; const isPlaceOrderDone = rawPlaceOrderTs != null && String(rawPlaceOrderTs).trim() !== ''; const time1 = isLogVisitDone ? formatTimeOnly(String(rawLogVisitTs)) : null; const time2 = isProductivityDone ? formatTimeOnly(String(rawProductivityTs)) : null; const timePm = isPotentialMiningDone ? formatTimeOnly(String(rawPotentialMiningTs)) : null; const time3 = isPlaceOrderDone ? formatTimeOnly(String(rawPlaceOrderTs)) : null; const date1 = isLogVisitDone ? formatDateOnly(String(rawLogVisitTs)) : null; const date2 = isProductivityDone ? formatDateOnly(String(rawProductivityTs)) : null; const datePm = isPotentialMiningDone ? formatDateOnly(String(rawPotentialMiningTs)) : null; const date3 = isPlaceOrderDone ? formatDateOnly(String(rawPlaceOrderTs)) : null; const fullDateTimeOrder = formatFullDateTime(String(rawPlaceOrderTs || rawLogVisitTs || '')); // Call Potential items (below orders) let callPotentialList: any[] = []; const parseCallPotentialItem = (p: any) => { const name = p.product_category_ || p.product_category || p.name || p.category || 'No data'; const actualPotential = Number(p.actual_potential || p.quantity || p.store_potential || 0); const totalOrdered = Number(p.total_ordered || 0); const difference = Number(p.difference || 0); const reason = p.reason || null; return { name, actualPotential, totalOrdered, difference, reason, kgs: `${actualPotential} kgs`, val: actualPotential, maxVal: Math.max(actualPotential, totalOrdered, 400), raw: p, }; }; const savedPotentialStr = rowSrc.potential || data?.potential; let savedPotential: any[] = []; if (Array.isArray(savedPotentialStr)) { savedPotential = savedPotentialStr; } else if (typeof savedPotentialStr === 'string') { try { savedPotential = JSON.parse(savedPotentialStr); } catch (e) { } } let mergedCallPot = potentialData ? [...potentialData] : [...savedPotential]; if (potentialData && potentialData.length > 0 && savedPotential.length > 0) { mergedCallPot = potentialData.map((pd: any) => { const pdCat = String(pd.product_category_ || pd.product_category || pd.name || pd.category).toLowerCase().trim(); const match = savedPotential.find((sp: any) => { const spCat = String(sp.product_category_ || sp.product_category || sp.name || sp.category).toLowerCase().trim(); return spCat === pdCat; }); return { ...pd, reason: match?.reason || pd.reason }; }); savedPotential.forEach(sp => { const spCat = String(sp.product_category_ || sp.product_category || sp.name || sp.category).toLowerCase().trim(); const exists = mergedCallPot.some((mp: any) => { const mpCat = String(mp.product_category_ || mp.product_category || mp.name || mp.category).toLowerCase().trim(); return mpCat === spCat; }); if (!exists) mergedCallPot.push(sp); }); } if (Array.isArray(mergedCallPot) && mergedCallPot.length > 0) { callPotentialList = mergedCallPot.map(parseCallPotentialItem); } // Store Potential items (below store details) let storePotentialList: any[] = []; const rawStorePot = selectStore.potential; const parseStorePotentialItem = (p: any) => { const name = p.product_category_ || p.product_category || p.name || p.category || 'No data'; const actualPotential = Number(p.actual_potential || p.quantity || p.store_potential || 0); return { name, actualPotential, }; }; if (typeof rawStorePot === 'string') { try { const parsed = JSON.parse(rawStorePot); if (Array.isArray(parsed)) { storePotentialList = parsed.map(parseStorePotentialItem); } } catch (e) { console.error("Failed to parse store potential JSON:", e); } } else if (Array.isArray(rawStorePot) && rawStorePot.length > 0) { storePotentialList = rawStorePot.map(parseStorePotentialItem); } // Extract order line items let orderItems: any[] = []; const rawOrderItems = rowSrc.order_details || data?.order_details || rowSrc.order_details_3 || data?.order_details_3; if (Array.isArray(rawOrderItems) && rawOrderItems.length > 0) { orderItems = rawOrderItems.map((item: any) => { const bags = Number(item.bags || item.bags_3 || item.quantity || 0); const skuVal = Number(item.sku || item.sku_3 || '30'); return { name: item.product_name || item.product_name_3 || item.product_category || 'MAIDA PUFF 30 kgs', category: item.product_category || item.product_category_3 || item.category || 'MAIDA PUFF', sku: item.sku || item.sku_3 || '30', skuCode: item.sku_code || item.sku_code_3 || 'MP30', brCode: item.br_code || item.br_code_3 || item.br || 'PM', bags, totalKgs: bags * skuVal, }; }); } else { orderItems = []; } // Calculate totals const totalBags = Number(rowSrc.total_bags_3 || rowSrc.total_bags || data?.total_bags_3 || data?.total_bags || orderItems.reduce((acc, i) => acc + i.bags, 0)); const totalKgs = Number(rowSrc.total_kgs_3 || rowSrc.total_kgs || data?.total_kgs_3 || data?.total_kgs || orderItems.reduce((acc, i) => acc + (i.bags * (Number(i.sku) || 1)), 0)); const lineItemsCount = orderItems.length; const orderId = String(rowSrc.order_id || data?.order_id || rowSrc.order_number || `No data`); const dateOfOrder = String(rowSrc.date_of_order_3 || data?.date_of_order_3 || rowSrc.date_of_order || 'No data'); // Extract uploaded proof photos from backend API const uploadedImages = (() => { const imgData = rowSrc.upload_image || data?.upload_image || selectStore.upload_image; if (Array.isArray(imgData)) return imgData; if (imgData && typeof imgData === 'object' && (imgData as any).uuid) return [imgData]; return []; })(); // Extract store banner photo from backend API const storeImages = (() => { const rawImg = selectStore.store_image || selectStore.upload_image || rowSrc.store_image || data?.store_image; if (typeof rawImg === 'string') { try { const parsed = JSON.parse(rawImg); if (Array.isArray(parsed)) return parsed; } catch (e) { console.error("Failed to parse store_image JSON:", e); } } if (Array.isArray(rawImg)) return rawImg; if (rawImg && typeof rawImg === 'object' && (rawImg as any).uuid) return [rawImg]; return []; })(); // Visit log details const dateOfVisit = String(rowSrc.date_of_visit || data?.date_of_visit || 'No data'); const timeOfVisit = String(rowSrc.time_of_visit || data?.time_of_visit || 'No data'); const storeStatus = String(rowSrc.store_status || data?.store_status || 'No data'); const channel = String(rowSrc.order_received_channel || rowSrc.channel || data?.order_received_channel || 'No data'); const storeNotes = selectStore.notes || 'No notes available.'; const currentStateName = String(rowSrc.current_state_name || data?.current_state_name || 'No data'); return (
{/* 1. Primary Summary & Status Banner */}
{/* Title & Badges */}
{onBack && ( )}
{/* {isPlaceOrderDone && ( Ordered )} */} {currentStateName !== 'No data' && ( {currentStateName} )} {/* Productive call On call */} {/* {isPlaceOrderDone ? `Order ${orderId}` : 'Order Not Placed'} */}

{storeName}

{areaLoc} {route} {fullDateTimeOrder}
{/* Top Right KPI Grid */}
TOTAL BAGS
{totalBags.toLocaleString()}
TOTAL KGS
{totalKgs.toLocaleString()}
ORDER ITEMS
{lineItemsCount}
{/* Workflow Progress Stepper - 3 Timestamps */}
{/* Step 1: Log Visit */}
{isLogVisitDone ? (
) : (
)}
Visit logged
{isLogVisitDone ? `${time1} • ${date1}` : 'Not performed'}
{/* Step 2: Productivity of Visit */}
{isProductivityDone ? (
) : (
)}
Productive call
{isProductivityDone ? `${time2} • ${date2}` : 'Not performed'}
{/* Step 3: Potential Mining */}
{isPotentialMiningDone ? (
) : (
)}
Potential Mining
{isPotentialMiningDone ? `${timePm} • ${datePm}` : 'Not performed'}
{/* Step 4: Place Order */}
{isPlaceOrderDone ? (
) : (
)}
Ordered
{isPlaceOrderDone ? `${time3} • ${date3}` : 'Not performed'}
{/* 2. Main Two-Column Layout */}
{/* MAIN CONTENT (Now on Right, 2/3 width) */}
{/* Order Details Card */} {currentStateName.toLowerCase() !== 'no order' && (

Order Details

{isPlaceOrderDone ? `${orderId} • ${dateOfOrder}` : 'Not Performed'}
{/* Table */}
{orderItems.length > 0 ? ( <>
{orderItems.map((item, idx) => { const bagsStr = item.bags; const kgsStr = item.totalKgs > 0 ? `${item.totalKgs.toLocaleString()} kg` : '0 kg'; return (
{item.name}
{bagsStr} Bags ({kgsStr})
); })}
Total Order
Bags {totalBags}
Total Kgs {totalKgs.toLocaleString()}
) : (
Order not placed
)}
{/* Total Summary Row (Moved to table footer) */} {/* Meta Footer */} {isPlaceOrderDone && (
CREATED BY {salesOfficer} • {officerJob || 'Sales Officer'}
CREATED AT {fullDateTimeOrder}
)}
)} {/* Call Potential Card (Below Orders) */} {callPotentialList.length > 0 && (

Potential vs Ordered

Ordered vs opportunity
{(() => { const regularPotential = callPotentialList.filter(item => item.actualPotential > 0); const extraOrdered = callPotentialList.filter(item => item.actualPotential === 0); const renderTable = (items: any[]) => (
{items.map((item, idx) => (
{item.name}
Potential {item.actualPotential}
Ordered {item.totalOrdered}
Diff = 0 ? 'text-emerald-600' : 'text-red-500'}`}> {item.difference > 0 ? '+' : ''}{item.difference}
{item.reason && (
Reason {item.reason}
)}
))}
); return (
{regularPotential.length > 0 && renderTable(regularPotential)} {extraOrdered.length > 0 && (

Ordered Outside Potential

{renderTable(extraOrdered)}
)}
); })()} {potentialMiningAction && (
{potentialMiningAction}
)}
)} {/* Visit Log Card */}

Visit Log

Logged at {time1}
{/* Left Key Value Details */}
DATE OF VISIT {dateOfVisit}
TIME OF VISIT {timeOfVisit}
STORE STATUS {storeStatus}
CHANNEL {channel}
{/* Right Store Proof Photo */}
UPLOADED PROOF
{uploadedImages.length > 0 ? (
{uploadedImages.map((file: any, idx: number) => { const previewUrl = `${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${file.uuid}/preview`; return (
{file.original_name { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).parentElement!.innerHTML = `
${file.original_name || 'Proof Image'}
`; }} />
); })}
) : (
No proof image uploaded
)}
{/* SIDEBAR (Now on Left, 1/3 width) */}
{/* Store Banner Image Card */}
{storeImages.length > 0 ? (
Store Front { (e.target as HTMLImageElement).style.display = 'none'; }} />
) : (
{storeName}
)}
{/* Store Info Card */}
{/* Store Potential Card */} {storePotentialList.length > 0 && (

Store Potential

Total capacity
{storePotentialList.map((item, idx) => (
{item.name}
{item.actualPotential} KG Potential
))}
)} {/* Distributor Card */}

Distributor

COMPANY {distCompany}
CONTACT {distContact}
PHONE {distPhone}
EMAIL {distEmail}
{/* Performed By Card */}

Performed by

NAME {salesOfficer}
ROLE {officerJob || 'Sales Officer'}
EMAIL {officerEmail}
); }