call card finished
This commit is contained in:
parent
3492cdbf9e
commit
2e57c47c7f
@ -1,58 +1,12 @@
|
|||||||
import { Store, MapPin, TrendingUp, Calendar, Clock, Info } from 'lucide-react';
|
import { Store, MapPin, TrendingUp, Calendar, Clock, Info, Truck } from 'lucide-react';
|
||||||
import { formatValue } from '../../lib/format';
|
import { formatValue } from '../../lib/format';
|
||||||
import { BASE_URL, APP_ID } from '../../api/config';
|
|
||||||
import './card.css';
|
import './card.css';
|
||||||
|
|
||||||
// {
|
|
||||||
// "42b7f47d-96f0-4289-a6d9-38f455ad57dd__user_id": {
|
|
||||||
// "email": "surya.c@getzino.com",
|
|
||||||
// "job_title": "",
|
|
||||||
// "name": "Surya C",
|
|
||||||
// "user_id": 29113
|
|
||||||
// },
|
|
||||||
// "current_state_id": "33d7ab1f-ddf4-4549-ac99-7d23172c4ca5",
|
|
||||||
// "current_state_name": "Productive Call",
|
|
||||||
// "date_of_visit": "2026-07-15",
|
|
||||||
// "instance_id": 6592,
|
|
||||||
// "is_productive_call": "yes",
|
|
||||||
// "select_store": {
|
|
||||||
// "area": "Dummy Area",
|
|
||||||
// "business_name": "Business Name 777",
|
|
||||||
// "complete_address": "Dummy notes for Complete Address",
|
|
||||||
// "distributor_email": "test@example.com",
|
|
||||||
// "distributor_name": "Banashree Multi Millet Flour",
|
|
||||||
// "distributor_owner_name": "Dummy Distributor Owner Name",
|
|
||||||
// "distributor_phone_number": {
|
|
||||||
// "dial_code": "+91",
|
|
||||||
// "phone": "9876543210",
|
|
||||||
// "phone_with_dial_code": "+919876543210"
|
|
||||||
// },
|
|
||||||
// "email": "test@example.com",
|
|
||||||
// "instance_id": 6118,
|
|
||||||
// "notes": "Dummy notes for Notes",
|
|
||||||
// "owner_name": "Dummy Owner Name",
|
|
||||||
// "phone_number": {
|
|
||||||
// "dial_code": "+91",
|
|
||||||
// "phone": "9876543210",
|
|
||||||
// "phone_with_dial_code": "+919876543210"
|
|
||||||
// },
|
|
||||||
// "pin_code": "100000",
|
|
||||||
// "potential": "[{\"row_kgs\": 0, \"quantity\": 20, \"product_category\": \"MAIDA SUPER VALUE\"}, {\"row_kgs\": 0, \"quantity\": 30, \"product_category\": \"SUJI\"}, {\"row_kgs\": 0, \"quantity\": 10, \"product_category\": \"FORTIFIED MAIDA\"}, {\"row_kgs\": 0, \"quantity\": 300, \"product_category\": \"ATTA REGULAR\"}]",
|
|
||||||
// "route_code": "k",
|
|
||||||
// "route_name": "Krishna",
|
|
||||||
// "store_code": "STR-00024",
|
|
||||||
// "store_image": "[{\"uuid\": \"a449f6cb-0dce-4e21-9599-ab4d3e47042b\", \"blob_path\": \"434/a449f6cb-0dce-4e21-9599-ab4d3e47042b.png\", \"mime_type\": \"image/png\", \"size_bytes\": 32174, \"original_name\": \"profile.png\"}]",
|
|
||||||
// "store_location": "{\"latitude\":12.9716,\"longitude\":77.5946}",
|
|
||||||
// "sub_route": "A"
|
|
||||||
// },
|
|
||||||
// "time_of_visit": "13:02:00",
|
|
||||||
// "upload_image": null
|
|
||||||
// }
|
|
||||||
|
|
||||||
export function CallCard({ row, isDetailView = false }: { row: Record<string, any>; isDetailView?: boolean }) {
|
export function CallCard({ row, isDetailView = false }: { row: Record<string, any>; isDetailView?: boolean }) {
|
||||||
// Status Mapping
|
// Status Mapping
|
||||||
const stateName = row.current_state_name;
|
const stateName = row.current_state_name;
|
||||||
const lowerState = stateName.toLowerCase();
|
const lowerState = stateName.toLowerCase();
|
||||||
|
const showOrderDetails = lowerState.includes('ordered') || lowerState.includes('order closed') || lowerState.includes('close order');
|
||||||
|
|
||||||
let statusClass = 'z-card-status--neutral';
|
let statusClass = 'z-card-status--neutral';
|
||||||
if (lowerState.includes('productive call')) {
|
if (lowerState.includes('productive call')) {
|
||||||
@ -72,26 +26,37 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
const userKey = Object.keys(row).find(k => k.includes('user_id'));
|
const userKey = Object.keys(row).find(k => k.includes('user_id'));
|
||||||
const userObj = userKey ? row[userKey] : null;
|
const userObj = userKey ? row[userKey] : null;
|
||||||
|
|
||||||
// Image
|
|
||||||
let imageObj = null;
|
|
||||||
let imgData = row.upload_image || storeObj?.store_image || row.store_image || row.image;
|
|
||||||
if (typeof imgData === 'string' && imgData.startsWith('[')) {
|
|
||||||
try { imgData = JSON.parse(imgData); } catch (e) { }
|
|
||||||
}
|
|
||||||
if (Array.isArray(imgData) && imgData.length > 0) {
|
|
||||||
imageObj = imgData[0];
|
|
||||||
} else if (imgData && typeof imgData === 'object' && imgData.blob_path) {
|
|
||||||
imageObj = imgData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store Details
|
|
||||||
const storeName = formatValue(storeObj?.business_name || storeObj?.distributor_name || row.store_name || row.customer_name || (typeof row.store === 'string' ? row.store : 'Unknown Store'));
|
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 || '—');
|
const storeCode = formatValue(storeObj?.store_code || row.store_code || '—');
|
||||||
|
|
||||||
// Grid Details (Calculate from potential if missing)
|
// Phone and Email
|
||||||
let totalWeightVal = Number(row.total_kgs || row.total_weight || row.weight || 0);
|
const storeEmail = formatValue(storeObj?.email || row.email || row.store_email || '—');
|
||||||
let totalQuantityVal = Number(row.total_bags || row.total_quantity || row.quantity || 0);
|
const storePhoneRaw = storeObj?.phone_number || row.phone_number || row.phone || row.store_phone;
|
||||||
let totalOrdersVal = Number(row.order_count || row.total_orders || 0);
|
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) {
|
if (totalWeightVal === 0 && totalQuantityVal === 0 && storeObj?.potential) {
|
||||||
try {
|
try {
|
||||||
@ -209,47 +174,18 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
{/* Detail View Additions */}
|
{/* Detail View Additions */}
|
||||||
{isDetailView && (
|
{isDetailView && (
|
||||||
<div className="z-card-details">
|
<div className="z-card-details">
|
||||||
|
{storePhone !== '—' && (
|
||||||
{storeCode !== '—' && (
|
|
||||||
<p className="z-card-meta">
|
<p className="z-card-meta">
|
||||||
Code: <span className="z-card-meta-code">{storeCode}</span>
|
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>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{imageObj && imageObj.uuid && (
|
|
||||||
<div className="z-card-image-box">
|
|
||||||
<img
|
|
||||||
src={`${BASE_URL}/app/${APP_ID}/view/files/${imageObj.uuid}/preview`}
|
|
||||||
alt="Store"
|
|
||||||
className="z-card-image"
|
|
||||||
onError={(e) => {
|
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Logistics Grid */}
|
|
||||||
<div className="z-card-grid">
|
|
||||||
<div>
|
|
||||||
<p className="z-card-grid-item-label">Total Weight</p>
|
|
||||||
<p className="z-card-grid-item-val">
|
|
||||||
{totalWeight} <span className="z-card-grid-item-unit">Kgs</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="z-card-grid-item-label">Total Quantity</p>
|
|
||||||
<p className="z-card-grid-item-val">
|
|
||||||
{totalQuantity} <span className="z-card-grid-item-unit">Bags</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="z-card-grid-footer">
|
|
||||||
<span className="z-card-grid-footer-label">Total Orders</span>
|
|
||||||
<span className="z-card-grid-footer-badge">
|
|
||||||
{totalOrders} {Number(totalOrders) === 1 ? 'Order' : 'Orders'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Route & Assignment */}
|
{/* Route & Assignment */}
|
||||||
<div className="z-card-route">
|
<div className="z-card-route">
|
||||||
@ -277,6 +213,89 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
</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 bags = formatValue(prod.bags_3 || 0);
|
||||||
|
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' }}>{bags} Bags</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -164,10 +164,15 @@
|
|||||||
|
|
||||||
.z-card-meta {
|
.z-card-meta {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--z-text-neutral-500);
|
color: var(--z-text-neutral-600);
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.z-card-meta-val {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
}
|
||||||
|
|
||||||
.z-card-meta-code {
|
.z-card-meta-code {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
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 type { WiredDetailViewProps } from './OrderDetail';
|
import type { WiredDetailViewProps } from './OrderDetail';
|
||||||
|
import { useDetailViewData } from './useDetailViewData';
|
||||||
|
import { Spinner } from '../reusable/Spinner';
|
||||||
|
import { EmptyState } from '../reusable/EmptyState';
|
||||||
|
import { CallCard } from '../cards/CallCard';
|
||||||
|
|
||||||
|
export function CallDetail({ instanceId }: WiredDetailViewProps) {
|
||||||
|
const { data, loading, error } = useDetailViewData(orderBookingClient, ORDER_BOOKING.detailViews.CALLS, instanceId);
|
||||||
|
|
||||||
|
if (error) return <EmptyState title="Couldn't load record" hint={error} />;
|
||||||
|
if (loading) return <div className="py-8 flex justify-center"><Spinner label="Loading Call Details..." /></div>;
|
||||||
|
if (!data) return <EmptyState title="No details found" />;
|
||||||
|
|
||||||
export function CallDetail({ instanceId, columns = 2 }: WiredDetailViewProps) {
|
|
||||||
return (
|
return (
|
||||||
<DetailView
|
<div className="w-full space-y-6 pb-24">
|
||||||
client={orderBookingClient}
|
<CallCard row={data} isDetailView={true} />
|
||||||
dvUid={ORDER_BOOKING.detailViews.CALLS}
|
</div>
|
||||||
instanceId={instanceId}
|
|
||||||
title="Call Details"
|
|
||||||
columns={columns}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user