krishnasales_mobile/src/components/dv/CallDetail.tsx
2026-08-03 13:39:53 +05:30

924 lines
46 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, any>;
potentialMiningAction?: ReactNode;
refreshKey?: number;
onBack?: () => void;
onDataLoad?: (data: Record<string, unknown>) => void;
}
export function CallDetail({
instanceId,
selectedRow,
potentialMiningAction,
refreshKey,
onBack,
onDataLoad,
}: CallDetailProps) {
const [data, setData] = useState<Record<string, unknown> | null>(null);
const [potentialData, setPotentialData] = useState<any[] | 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.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 (
<div className="bg-[var(--tiles-card-bg)] p-8 rounded-2xl border border-border-default shadow-sm text-center">
<EmptyState title="Couldnt load record" hint={error} />
</div>
);
}
if (loading) {
return (
<div className="bg-[var(--tiles-card-bg)] p-12 rounded-2xl border border-border-default shadow-sm flex justify-center items-center">
<Spinner label="Loading call details…" />
</div>
);
}
const rowSrc = selectedRow || data || {};
// Extract store info with fallbacks matching reference
const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record<string, any>;
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 (
<div className="text-slate-800 py-6 lg:py-8 font-sans space-y-6">
{/* 1. Primary Summary & Status Banner */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-6">
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6">
{/* Title & Badges */}
<div className="flex items-start gap-3 sm:gap-4">
{onBack && (
<button onClick={onBack} className="mt-1 p-1.5 sm:p-2 text-slate-800 hover:text-slate-800 hover:bg-slate-100/80 rounded-full transition-colors shrink-0 -ml-2">
<ArrowLeft size={20} strokeWidth={2.5} />
</button>
)}
<div className="space-y-3">
<div className="flex flex-wrap items-center gap-2 text-xs">
{/* {isPlaceOrderDone && (
<span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200">
<CheckCircle2 size={12} /> Ordered
</span>
)} */}
{currentStateName !== 'No data' && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-blue-50 text-blue-700 border border-blue-200">
{currentStateName}
</span>
)}
{/* <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-purple-50 text-purple-700 border border-purple-200">
Productive call
</span>
<span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-slate-100 text-slate-700 border border-slate-200">
<Phone size={11} /> On call
</span> */}
{/* <span className="text-xs font-medium text-slate-800">
{isPlaceOrderDone ? `Order ${orderId}` : 'Order Not Placed'}
</span> */}
</div>
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">
{storeName}
</h1>
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-800 font-medium">
<span className="flex items-center gap-1">
<MapPin size={13} className="text-slate-800" /> {areaLoc}
</span>
<span></span>
<span>{route}</span>
<span></span>
<span className="flex items-center gap-1">
<Clock size={13} className="text-slate-800" /> {fullDateTimeOrder}
</span>
</div>
</div>
</div>
{/* Top Right KPI Grid */}
<div className="flex flex-nowrap items-center gap-2 w-full min-w-0 mt-2">
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2.5 text-center flex-1 min-w-0">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">TOTAL BAGS</div>
<div className="text-lg font-extrabold text-primary leading-tight">{totalBags.toLocaleString()}</div>
</div>
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2.5 text-center flex-1 min-w-0">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">TOTAL KGS</div>
<div className="text-lg font-extrabold text-primary leading-tight">{totalKgs.toLocaleString()}</div>
</div>
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2.5 text-center flex-1 min-w-0">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">ORDER ITEMS</div>
<div className="text-lg font-extrabold text-primary leading-tight">{lineItemsCount}</div>
</div>
</div>
</div>
{/* Workflow Progress Stepper - 3 Timestamps */}
<div className="border-t border-slate-100 pt-4">
<div className="flex-col flex-nowrap items-center justify-between gap-6 overflow-x-auto pb-2">
{/* Step 1: Log Visit */}
<div className="flex items-center gap-3 pb-2">
{isLogVisitDone ? (
<div className="w-6 h-6 rounded-full bg-emerald-500 text-white flex items-center justify-center text-xs font-bold shrink-0">
</div>
) : (
<div className="w-6 h-6 rounded-full border-2 border-slate-300 bg-[var(--tiles-card-bg)] text-slate-800 flex items-center justify-center text-xs shrink-0">
<Clock size={12} />
</div>
)}
<div className="text-xs">
<div className={`font-bold ${isLogVisitDone ? 'text-slate-800' : 'text-slate-800'}`}>Visit logged</div>
<div className="text-slate-800 font-medium text-[11px] leading-tight mt-0.5">
{isLogVisitDone ? `${time1}${date1}` : 'Not performed'}
</div>
</div>
</div>
{/* Step 2: Productivity of Visit */}
<div className="flex items-center gap-3 pb-2">
{isProductivityDone ? (
<div className="w-6 h-6 rounded-full bg-emerald-500 text-white flex items-center justify-center text-xs font-bold shrink-0">
</div>
) : (
<div className="w-6 h-6 rounded-full border-2 border-slate-300 bg-[var(--tiles-card-bg)] text-slate-800 flex items-center justify-center text-xs shrink-0">
<Clock size={12} />
</div>
)}
<div className="text-xs">
<div className={`font-bold ${isProductivityDone ? 'text-slate-800' : 'text-slate-800'}`}>Productive call</div>
<div className="text-slate-800 font-medium text-[11px] leading-tight mt-0.5">
{isProductivityDone ? `${time2}${date2}` : 'Not performed'}
</div>
</div>
</div>
{/* Step 3: Potential Mining */}
<div className="flex items-center gap-3 pb-2">
{isPotentialMiningDone ? (
<div className="w-6 h-6 rounded-full bg-emerald-500 text-white flex items-center justify-center text-xs font-bold shrink-0">
</div>
) : (
<div className="w-6 h-6 rounded-full border-2 border-slate-300 bg-[var(--tiles-card-bg)] text-slate-800 flex items-center justify-center text-xs shrink-0">
<Clock size={12} />
</div>
)}
<div className="text-xs pb-2">
<div className={`font-bold ${isPotentialMiningDone ? 'text-slate-800' : 'text-slate-800'}`}>Potential Mining</div>
<div className="text-slate-800 font-medium text-[11px] leading-tight mt-0.5">
{isPotentialMiningDone ? `${timePm}${datePm}` : 'Not performed'}
</div>
</div>
</div>
{/* Step 4: Place Order */}
<div className="flex items-center gap-3">
{isPlaceOrderDone ? (
<div className="w-6 h-6 rounded-full bg-emerald-500 text-white flex items-center justify-center text-xs font-bold shrink-0">
</div>
) : (
<div className="w-6 h-6 rounded-full border-2 border-slate-300 bg-[var(--tiles-card-bg)] text-slate-800 flex items-center justify-center text-xs shrink-0">
<Clock size={12} />
</div>
)}
<div className="text-xs">
<div className={`font-bold ${isPlaceOrderDone ? 'text-slate-800' : 'text-slate-800'}`}>Ordered</div>
<div className="text-slate-800 font-medium text-[11px] leading-tight mt-0.5">
{isPlaceOrderDone ? `${time3}${date3}` : 'Not performed'}
</div>
</div>
</div>
</div>
</div>
</div>
{/* 2. Main Two-Column Layout */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
{/* MAIN CONTENT (Now on Right, 2/3 width) */}
<div className="lg:col-span-2 lg:order-last space-y-6">
{/* Order Details Card */}
{currentStateName.toLowerCase() !== 'no order' && (
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between border-b border-slate-100 pb-3 gap-3">
<div className="flex items-center gap-2">
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
<ShoppingCart size={14} color="currentColor" className="text-primary" />
</div>
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Order Details</h2>
</div>
<div className="flex items-center gap-3">
<span className="text-xs font-semibold text-slate-800">
{isPlaceOrderDone ? `${orderId}${dateOfOrder}` : 'Not Performed'}
</span>
</div>
</div>
{/* Table */}
<div className="flex flex-col gap-4">
{orderItems.length > 0 ? (
<>
<div className="flex flex-col gap-3">
{orderItems.map((item, idx) => {
const bagsStr = item.bags;
const kgsStr = item.totalKgs > 0 ? `${item.totalKgs.toLocaleString()} kg` : '0 kg';
return (
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
<div className="flex flex-col gap-1">
<span className="text-primary font-semibold text-sm">{item.name}</span>
</div>
<div className="text-right whitespace-nowrap">
<span className="text-primary font-bold text-sm">{bagsStr} Bags</span>
<span className="text-primary text-[13px] ml-1.5">({kgsStr})</span>
</div>
</div>
);
})}
</div>
<div className="mt-2 rounded-xl p-4 flex items-center justify-between shadow-sm bg-primary text-white">
<span className="text-xs font-bold uppercase tracking-wider opacity-90">Total Order</span>
<div className="flex items-center gap-6">
<div className="flex flex-col items-end">
<span className="text-[10px] font-medium uppercase opacity-80">Bags</span>
<span className="text-lg font-black">{totalBags}</span>
</div>
<div className="flex flex-col items-end">
<span className="text-[10px] font-medium uppercase opacity-80">Total Kgs</span>
<span className="text-lg font-black text-emerald-300">{totalKgs.toLocaleString()}</span>
</div>
</div>
</div>
</>
) : (
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl px-6 py-8 flex flex-col items-center justify-center text-center gap-2">
<ShoppingCart size={24} color="currentColor" className="text-primary opacity-50" />
<span className="text-[13px] font-semibold text-primary">Order not placed</span>
</div>
)}
</div>
{/* Total Summary Row (Moved to table footer) */}
{/* Meta Footer */}
{isPlaceOrderDone && (
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-slate-100 pt-4 text-xs text-slate-800 font-medium">
<div>
CREATED BY <span className="text-slate-800 font-bold ml-1">{salesOfficer} {officerJob || 'Sales Officer'}</span>
</div>
<div>
CREATED AT <span className="text-slate-800 font-bold ml-1">{fullDateTimeOrder}</span>
</div>
</div>
)}
</div>
)}
{/* Call Potential Card (Below Orders) */}
{callPotentialList.length > 0 && (
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-5 shadow-sm flex flex-col gap-3">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<div className="flex items-center gap-2">
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
<TrendingUp size={14} color="currentColor" className="text-primary" />
</div>
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Potential vs Ordered</h2>
</div>
<span className="text-xs font-medium text-slate-800">Ordered vs opportunity</span>
</div>
{(() => {
const regularPotential = callPotentialList.filter(item => item.actualPotential > 0);
const extraOrdered = callPotentialList.filter(item => item.actualPotential === 0);
const renderTable = (items: any[]) => (
<div className="flex flex-col gap-2.5">
{items.map((item, idx) => (
<div key={idx} className="bg-primary-light-bg border border-primary-light-border rounded-xl px-3 py-2.5 flex flex-col gap-1">
<div className="flex justify-between items-center">
<span className="text-primary font-bold text-[13px] uppercase tracking-wide">{item.name}</span>
</div>
<div className="flex justify-between items-center bg-white rounded-lg py-1.5 px-3 border border-primary-light-border mt-0.5">
<div className="flex flex-col gap-0.5">
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Potential</span>
<span className="text-sm font-semibold text-primary">{item.actualPotential}</span>
</div>
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Ordered</span>
<span className="text-sm font-semibold text-primary">{item.totalOrdered}</span>
</div>
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Diff</span>
<span className={`text-[15px] font-black ${item.difference >= 0 ? 'text-emerald-600' : 'text-red-500'}`}>
{item.difference > 0 ? '+' : ''}{item.difference}
</span>
</div>
</div>
{item.reason && (
<div className="flex items-center justify-between px-3 py-2 rounded-xl border border-red-200 bg-red-50 mt-1">
<span className="text-[10px] font-bold text-red-500 uppercase tracking-wider">Reason</span>
<span className="text-[11px] font-bold text-red-600">{item.reason}</span>
</div>
)}
</div>
))}
</div>
);
return (
<div className="flex flex-col gap-4">
{regularPotential.length > 0 && renderTable(regularPotential)}
{extraOrdered.length > 0 && (
<div className="flex flex-col gap-3 pt-2 border-t border-slate-100 mt-2">
<div className="flex items-center gap-2">
<ShoppingCart className="text-slate-800" size={14} />
<h3 className="text-[11px] font-bold text-slate-800 uppercase tracking-wider">Ordered Outside Potential</h3>
</div>
{renderTable(extraOrdered)}
</div>
)}
</div>
);
})()}
{potentialMiningAction && (
<div className="flex justify-end border-t border-slate-100 pt-3">
{potentialMiningAction}
</div>
)}
</div>
)}
{/* Visit Log Card */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<div className="flex items-center gap-2">
<ClipboardList className="text-slate-700" size={18} />
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Visit Log</h2>
</div>
<span className="text-xs font-medium text-slate-800">Logged at {time1}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-start">
{/* Left Key Value Details */}
<div className="space-y-3 text-xs">
<div className="flex justify-between py-1.5 border-b border-slate-100">
<span className="font-bold text-slate-800 uppercase">DATE OF VISIT</span>
<span className="font-bold text-slate-800">{dateOfVisit}</span>
</div>
<div className="flex justify-between py-1.5 border-b border-slate-100">
<span className="font-bold text-slate-800 uppercase">TIME OF VISIT</span>
<span className="font-bold text-slate-800">{timeOfVisit}</span>
</div>
<div className="flex justify-between py-1.5 border-b border-slate-100">
<span className="font-bold text-slate-800 uppercase">STORE STATUS</span>
<span className="font-bold text-slate-800">{storeStatus}</span>
</div>
<div className="flex justify-between py-1.5 border-b border-slate-100">
<span className="font-bold text-slate-800 uppercase">CHANNEL</span>
<span className="font-bold text-slate-800">{channel}</span>
</div>
</div>
{/* Right Store Proof Photo */}
<div className="space-y-2">
<div className="text-[11px] font-bold text-slate-800 uppercase tracking-wider flex items-center gap-1">
<FileText size={12} /> UPLOADED PROOF
</div>
{uploadedImages.length > 0 ? (
<div className="space-y-3">
{uploadedImages.map((file: any, idx: number) => {
const previewUrl = `${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${file.uuid}/preview`;
return (
<div key={file.uuid || idx} className="relative w-full rounded-xl overflow-hidden border border-slate-200 bg-slate-100 flex items-center justify-center group shadow-sm">
<img
src={previewUrl}
alt={file.original_name || 'Uploaded Proof'}
className="w-full h-auto max-h-[300px] object-cover transition-transform group-hover:scale-[1.01]"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
(e.target as HTMLImageElement).parentElement!.innerHTML = `<div class="p-6 text-center text-xs text-slate-800 font-mono">${file.original_name || 'Proof Image'}</div>`;
}}
/>
<a href={previewUrl} target="_blank" rel="noopener noreferrer" className="absolute inset-0 z-10" aria-label="View Full Image" />
</div>
);
})}
</div>
) : (
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl p-6 flex flex-col items-center justify-center text-center gap-2">
<Camera size={24} color="currentColor" className="text-primary" />
<span className="text-xs font-semibold text-primary">No proof image uploaded</span>
</div>
)}
</div>
</div>
</div>
</div>
{/* SIDEBAR (Now on Left, 1/3 width) */}
<div className="lg:order-first space-y-6">
{/* Store Banner Image Card */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 overflow-hidden shadow-sm">
{storeImages.length > 0 ? (
<div className="relative w-full h-44 bg-slate-100 flex items-center justify-center overflow-hidden">
<img
src={`${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${storeImages[0].uuid}/preview`}
alt="Store Front"
className="w-full h-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
</div>
) : (
<div className="bg-primary-light-bg border-b border-primary-light-border p-6 flex flex-col items-center justify-center gap-2 h-[160px] text-center">
<div className="w-12 h-12 rounded-full bg-white border border-primary-light-border flex items-center justify-center shadow-sm">
<ImageIcon size={22} color="currentColor" className="text-primary" />
</div>
<span className="text-xs font-bold text-primary uppercase tracking-wider">{storeName}</span>
</div>
)}
</div>
{/* Store Info Card */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<div className="flex items-center gap-2">
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
<Store size={14} color="currentColor" className="text-primary" />
</div>
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store</h2>
</div>
<span className="px-2 py-0.5 text-[10px] font-bold text-slate-600 bg-slate-100 rounded border border-slate-200">
{storeCode}
</span>
</div>
<div className="space-y-3 text-xs">
<div className="flex items-start justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">BUSINESS</span>
<span className="font-bold text-slate-900 text-right">{storeName}</span>
</div>
<div className="flex items-start justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">OWNER</span>
<span className="font-bold text-slate-900 text-right">{ownerName}</span>
</div>
<div className="flex items-start justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">PHONE</span>
<span className="font-bold text-slate-900 text-right flex items-center gap-1">
<Phone size={11} className="text-slate-800" /> {phone}
</span>
</div>
<div className="flex items-start justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">EMAIL</span>
<span className="font-bold text-slate-900 text-right flex items-center gap-1 break-all">
<Mail size={11} className="text-slate-800" /> {email}
</span>
</div>
<div className="py-1 border-b border-slate-100 space-y-1">
<span className="text-slate-800 font-bold uppercase block">ADDRESS</span>
<span className="font-medium text-slate-800 block leading-relaxed">{address}</span>
{lat && lng && (
<a
href={`https://www.google.com/maps/search/?api=1&query=${lat},${lng}`}
target="_blank"
rel="noopener noreferrer"
className="mt-1.5 inline-flex items-center gap-1.5 text-[11px] font-bold text-blue-600 bg-blue-50/50 hover:bg-blue-50 px-2 py-1 rounded transition-colors"
>
<MapPin size={12} /> View on Google Maps
</a>
)}
</div>
<div className="flex items-start justify-between py-1">
<span className="text-slate-800 font-bold uppercase">ROUTE</span>
<span className="font-bold text-slate-900 text-right">{route}</span>
</div>
</div>
{/* Notes Container */}
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl p-3.5 gap-1.5 flex flex-col">
<div className="text-[11px] font-bold text-primary uppercase tracking-wider flex items-center gap-1.5">
<FileText size={12} color="currentColor" className="text-primary" /> NOTES
</div>
<p className="text-primary leading-relaxed font-medium m-0 text-xs">
{storeNotes}
</p>
</div>
</div>
{/* Store Potential Card */}
{storePotentialList.length > 0 && (
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<div className="flex items-center gap-2">
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
<TrendingUp size={14} color="currentColor" className="text-primary" />
</div>
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store Potential</h2>
</div>
<span className="text-xs font-medium text-slate-800">Total capacity</span>
</div>
<div className="flex flex-col gap-3">
{storePotentialList.map((item, idx) => (
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
<div className="flex flex-col gap-1">
<span className="text-primary font-semibold text-sm">{item.name}</span>
</div>
<div className="text-right whitespace-nowrap">
<span className="text-primary font-bold text-sm">{item.actualPotential} KG Potential</span>
</div>
</div>
))}
</div>
</div>
)}
{/* Distributor Card */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex items-center gap-2 border-b border-slate-100 pb-3">
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
<Truck size={14} color="currentColor" className="text-primary" />
</div>
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Distributor</h2>
</div>
<div className="space-y-3 text-xs">
<div className="flex justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">COMPANY</span>
<span className="font-bold text-slate-900">{distCompany}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">CONTACT</span>
<span className="font-bold text-slate-900">{distContact}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">PHONE</span>
<span className="font-bold text-slate-900 flex items-center gap-1">
<Phone size={11} className="text-slate-800" /> {distPhone}
</span>
</div>
<div className="flex justify-between py-1">
<span className="text-slate-800 font-bold uppercase">EMAIL</span>
<span className="font-bold text-slate-900 flex items-center gap-1">
<Mail size={11} className="text-slate-800" /> {distEmail}
</span>
</div>
</div>
</div>
{/* Performed By Card */}
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
<div className="flex items-center gap-2 border-b border-slate-100 pb-3">
<User className="text-slate-700" size={18} />
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Performed by</h2>
</div>
<div className="space-y-3 text-xs">
<div className="flex justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">NAME</span>
<span className="font-bold text-slate-900">{salesOfficer}</span>
</div>
<div className="flex justify-between py-1 border-b border-slate-100">
<span className="text-slate-800 font-bold uppercase">ROLE</span>
<span className="font-bold text-slate-900">{officerJob || 'Sales Officer'}</span>
</div>
<div className="flex justify-between py-1">
<span className="text-slate-800 font-bold uppercase">EMAIL</span>
<span className="font-bold text-slate-900 flex items-center gap-1">
<Mail size={11} className="text-slate-800" /> {officerEmail}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
}