krishnasales_mobile/src/components/cards/CallCard.tsx
2026-07-27 13:03:20 +05:30

308 lines
13 KiB
TypeScript

import { Store, MapPin, TrendingUp, Calendar, Clock, Info, Truck } from 'lucide-react';
import { formatValue } from '../../lib/format';
import './card.css';
export function CallCard({ row, isDetailView = false }: { row: Record<string, any>; isDetailView?: boolean }) {
// Status Mapping
const stateName = row.current_state_name;
const lowerState = stateName.toLowerCase();
const showOrderDetails = lowerState.includes('ordered') || lowerState.includes('order closed') || lowerState.includes('close order');
let statusClass = 'z-card-status--neutral';
if (lowerState.includes('productive call')) {
statusClass = 'z-card-status--success';
} else if (lowerState.includes('close order') || lowerState.includes('completed') || lowerState.includes('success')) {
statusClass = 'z-card-status--success';
} else if (lowerState.includes('no order')) {
statusClass = 'z-card-status--danger';
} else if (lowerState.includes('visited')) {
statusClass = 'z-card-status--primary';
} else {
statusClass = 'z-card-status--primary'; // default fallback for other active states
}
// Extract nested objects if they exist
const storeObj = typeof row.select_store === 'object' ? row.select_store : (typeof row.store === 'object' ? row.store : {});
const userKey = Object.keys(row).find(k => k.includes('user_id'));
const userObj = userKey ? row[userKey] : null;
const storeName = formatValue(storeObj?.business_name || storeObj?.distributor_name || row.store_name || row.customer_name || (typeof row.store === 'string' ? row.store : 'Unknown Store'));
const storeCode = formatValue(storeObj?.store_code || row.store_code || '—');
// Phone and Email
const storeEmail = formatValue(storeObj?.email || row.email || row.store_email || '—');
const storePhoneRaw = storeObj?.phone_number || row.phone_number || row.phone || row.store_phone;
let storePhone = '—';
if (typeof storePhoneRaw === 'object' && storePhoneRaw !== null) {
storePhone = formatValue(storePhoneRaw.phone_with_dial_code || storePhoneRaw.phone || '—');
} else if (typeof storePhoneRaw === 'string') {
storePhone = formatValue(storePhoneRaw);
}
// Distributor Details
const distName = formatValue(storeObj?.distributor_name || '—');
const distOwner = formatValue(storeObj?.distributor_owner_name || '—');
const distEmail = formatValue(storeObj?.distributor_email || '—');
const distPhoneRaw = storeObj?.distributor_phone_number;
let distPhone = '—';
if (typeof distPhoneRaw === 'object' && distPhoneRaw !== null) {
distPhone = formatValue(distPhoneRaw.phone_with_dial_code || distPhoneRaw.phone || '—');
} else if (typeof distPhoneRaw === 'string') {
distPhone = formatValue(distPhoneRaw);
}
const hasDistributor = distName !== '—' || distOwner !== '—' || distEmail !== '—' || distPhone !== '—';
// Grid Details (Calculate from potential or orders if missing)
let totalWeightVal = Number(row.total_kgs_3 || row.total_kgs || row.total_weight || row.weight || 0);
let totalQuantityVal = Number(row.total_bags_3 || row.total_bags || row.total_quantity || row.quantity || 0);
let totalOrdersVal = Number(row.order_count || row.total_orders || (row.order_details_3 ? row.order_details_3.length : 0));
if (totalWeightVal === 0 && totalQuantityVal === 0 && storeObj?.potential) {
try {
const pot = typeof storeObj.potential === 'string' ? JSON.parse(storeObj.potential) : storeObj.potential;
if (Array.isArray(pot)) {
totalOrdersVal = pot.length;
pot.forEach((p: any) => {
totalWeightVal += Number(p.row_kgs || 0);
totalQuantityVal += Number(p.quantity || 0);
});
}
} catch (e) { }
}
const totalWeight = formatValue(totalWeightVal);
const totalQuantity = formatValue(totalQuantityVal);
const totalOrders = formatValue(totalOrdersVal);
// Route & Assignment
const routeName = formatValue(storeObj?.route_name || storeObj?.route || row.route_name || row.route || '—');
const areaName = formatValue(storeObj?.area || row.area || row.area_name || '—');
// User
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';
// Date & Time
let dateStr = '—';
let timeStr = '—';
if (row.date_of_visit || row.visit_date) {
try {
const dVal = String(row.date_of_visit || row.visit_date);
const d = new Date(dVal);
if (!isNaN(d.getTime())) {
dateStr = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }).replace(/ /g, '-');
} else dateStr = dVal.split('T')[0];
} catch {
dateStr = String(row.date_of_visit || row.visit_date).split('T')[0];
}
}
if (row.time_of_visit || row.visit_time) {
try {
const t = String(row.time_of_visit || row.visit_time);
let timeParts = t.split(':');
if (t.includes('T')) {
timeParts = t.split('T')[1].replace('Z', '').split(':');
}
if (timeParts.length >= 2) {
let h = parseInt(timeParts[0]);
const m = timeParts[1];
const ampm = h >= 12 ? 'PM' : 'AM';
h = h % 12;
h = h ? h : 12;
const hStr = h < 10 ? '0' + h : h;
timeStr = `${hStr}:${m} ${ampm}`;
} else {
timeStr = t;
}
} catch {
timeStr = String(row.time_of_visit || row.visit_time);
}
}
return (
<div className="z-card">
{/* Top Row */}
<div className="z-card-header">
<div className="z-card-header-icon">
<TrendingUp size={16} />
<span>{storeCode}</span>
</div>
<span className={`z-card-status ${statusClass}`}>
{stateName}
</span>
</div>
{/* Store Name & Location */}
<div className="z-card-store-row">
<Store size={18} className="z-card-store-icon" />
<h2 className="z-card-store-name">{storeName}</h2>
</div>
<div className="z-card-area-row">
<MapPin size={16} className="z-card-area-icon" />
<p className="z-card-area-name">{areaName}</p>
</div>
{/* Divider */}
<div className="z-card-divider"></div>
{/* Date & Time */}
<div className="z-card-footer">
<div className="z-card-footer-item">
<div className="z-card-footer-label"><Calendar size={14} /> DATE</div>
<span className="z-card-footer-value">{dateStr}</span>
</div>
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
<div className="z-card-footer-label"><Clock size={14} /> TIME</div>
<span className="z-card-footer-value">{timeStr}</span>
</div>
</div>
{/* Button (List View Only) */}
{!isDetailView && (
<div className="z-card-action-btn">
<Info size={16} />
<span>See More Details</span>
</div>
)}
{/* Detail View Additions */}
{isDetailView && (
<div className="z-card-details">
{storePhone !== '—' && (
<p className="z-card-meta">
Phone: <span className="z-card-meta-val">{storePhone}</span>
</p>
)}
{storeEmail !== '—' && (
<p className="z-card-meta">
Email: <span className="z-card-meta-val lowercase">{storeEmail}</span>
</p>
)}
{/* 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>
{/* Call Meta Chips */}
<div className="z-card-route" style={{ marginTop: '1rem', display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{row.order_received_channel && (
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-700">
Channel: {String(row.order_received_channel).replace(/_/g, ' ').toUpperCase()}
</span>
)}
{row.remarks && (
<div className="w-full mt-2 text-sm text-gray-600 bg-gray-50 p-3 rounded-lg border border-gray-100">
<span className="font-semibold text-gray-800">Remarks: </span>
{row.remarks}
</div>
)}
</div>
{/* Product Cards (Order Details) */}
{showOrderDetails && row.order_details_3 && Array.isArray(row.order_details_3) && row.order_details_3.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">
{totalOrders} {Number(totalOrders) === 1 ? 'Item' : 'Items'}
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{row.order_details_3.map((prod: any, idx: number) => {
const productName = formatValue(prod.product_name_3 || prod.product_category_3 || 'Unknown Product');
const bagsStr = formatValue(prod.bags_3 || 0);
const bagsNum = Number(prod.bags_3 || 0);
const skuNum = Number(prod.sku_3 || prod.sku || 0);
const kgsNum = skuNum > 0 ? skuNum * bagsNum : Number(prod.row_kgs_3 || prod.row_kgs || prod.kgs_3 || prod.weight_3 || 0);
const kgsStr = kgsNum > 0 ? ` (${kgsNum} kg)` : '';
return (
<div key={idx} 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' }}>{bagsStr} Bags{kgsStr}</span>
</div>
);
})}
</div>
</div>
)}
{/* Logistics Totals at the bottom */}
{showOrderDetails && (Number(totalWeightVal) > 0 || Number(totalQuantityVal) > 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">{totalWeight} <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">{totalQuantity} <span className="text-sm font-bold text-gray-600">Bags</span></span>
</div>
</div>
)}
{/* Distributor Details */}
{hasDistributor && (
<div className="mt-6 border-t border-gray-100 pt-6">
<p className="z-card-meta" style={{ marginBottom: '12px', color: '#334155', fontWeight: 700 }}>Distributor Details</p>
{distName !== '—' && (
<div className="z-card-store-row" style={{ marginBottom: '12px' }}>
<Truck size={18} className="z-card-store-icon" />
<h2 className="z-card-store-name">{distName}</h2>
</div>
)}
{distOwner !== '—' && (
<p className="z-card-meta">
Owner: <span className="z-card-meta-val">{distOwner}</span>
</p>
)}
{distPhone !== '—' && (
<p className="z-card-meta">
Phone: <span className="z-card-meta-val">{distPhone}</span>
</p>
)}
{distEmail !== '—' && (
<p className="z-card-meta">
Email: <span className="z-card-meta-val lowercase">{distEmail}</span>
</p>
)}
</div>
)}
</div>
)}
</div>
);
}