Compare commits

..

10 Commits

Author SHA1 Message Date
suryac
2f159af60b sorted by order date 2026-08-04 15:19:39 +05:30
suryac
09e99fcc5a order detail design done 2026-08-04 13:18:55 +05:30
suryac
29fd066314 added that required field on the top of the select 2026-08-04 12:38:59 +05:30
suryac
014fb4ee58 added new version of google map 2026-08-04 11:54:08 +05:30
suryac
3de2c1050b removed console logs 2026-08-04 11:01:05 +05:30
suryac
dcbc288faf local storage clear set fix 2026-08-04 10:21:32 +05:30
suryac
d9e932ce8c store code fix 2026-08-03 12:30:57 +05:30
suryac
b288ebf6cc made outline no border 2026-08-03 11:43:15 +05:30
suryac
a8807155a2 detail view polished 2026-08-03 10:25:31 +05:30
suryac
9575cd060e fix: call detail data rendering 2026-07-31 13:53:11 +05:30
23 changed files with 459 additions and 308 deletions

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" /> <link rel="icon" type="image/png" href="/src/assets/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Krishna Sales</title> <title>Krishna Sales</title>
</head> </head>

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -249,3 +249,7 @@ export const PIPELINE = {
salesOfficers: '/api/papi2/sales-officers' salesOfficers: '/api/papi2/sales-officers'
} }
}; };
export const HIDDEN_FORM_FIELDS = [
'store_code_3'
];

View File

@ -45,7 +45,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setUserEmail(null); setUserEmail(null);
setIsAdmin(false); setIsAdmin(false);
setRoles([]); setRoles([]);
localStorage.removeItem('krishna_sales_user_email'); localStorage.clear();
}, },
}; };

View File

@ -21,6 +21,38 @@ import {
ArrowLeft, ArrowLeft,
} from 'lucide-react'; } from 'lucide-react';
const SafeImage = ({ src, alt, type, text, className }: { src: string, alt: string, type: 'store' | 'proof', text?: string, className?: string }) => {
const [error, setError] = useState(false);
if (error) {
if (type === 'store') {
return (
<div className="h-40 bg-[#E8FBF0] p-6 flex flex-col items-center justify-center text-center gap-2 border border-emerald-200 w-full">
<div className="w-12 h-12 rounded-full bg-[var(--tiles-card-bg)] shadow-sm flex items-center justify-center text-emerald-500">
<ImageIcon size={22} />
</div>
<span className="text-xs font-bold text-emerald-700 uppercase tracking-wider">{text || 'Image not available'}</span>
</div>
);
}
return (
<div className="rounded-xl border border-emerald-200 bg-[#E8FBF0] p-6 flex flex-col items-center justify-center text-center gap-2 w-full h-full min-h-[160px]">
<Camera className="text-emerald-500" size={24} />
<span className="text-xs font-semibold text-emerald-700">{text || 'No proof image uploaded'}</span>
</div>
);
}
return (
<img
src={src}
alt={alt}
className={className}
onError={() => setError(true)}
/>
);
};
export interface CallDetailProps { export interface CallDetailProps {
instanceId: number | string; instanceId: number | string;
selectedRow?: Record<string, any>; selectedRow?: Record<string, any>;
@ -98,7 +130,7 @@ export function CallDetail({
// Extract store info with fallbacks matching reference // Extract store info with fallbacks matching reference
const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record<string, any>; 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 storeName = selectStore.business_name || selectStore.store_name || rowSrc.store_name || 'No data';
const storeCode = selectStore.store_code || selectStore.code || 'No data'; const storeCode = selectStore.store_code_2 || selectStore.code || 'No data';
const ownerName = selectStore.owner_name || selectStore.contact_person || '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 phone = selectStore.phone_number?.phone || selectStore.phone_number?.phone_with_dial_code || selectStore.phone || 'No data';
const email = selectStore.email || 'No data'; const email = selectStore.email || 'No data';
@ -289,28 +321,28 @@ export function CallDetail({
// Extract order line items // Extract order line items
let orderItems: any[] = []; let orderItems: any[] = [];
const rawOrderItems = rowSrc.order_details || data?.order_details || rowSrc.order_details_3 || data?.order_details_3; const rawOrderItems = rowSrc.order_details_3 ?? data?.order_details_3;
if (Array.isArray(rawOrderItems) && rawOrderItems.length > 0) { if (Array.isArray(rawOrderItems) && rawOrderItems.length > 0) {
orderItems = rawOrderItems.map((item: any) => { orderItems = rawOrderItems.map((item: any) => {
const bags = Number(item.bags || item.bags_3 || item.quantity || 0); const bags = Number(item.bags_3 ?? 0);
const skuVal = Number(item.sku || item.sku_3 || '30'); const skuVal = Number(item.sku_3 ?? 30);
return { return {
name: item.product_name || item.product_name_3 || item.product_category || 'MAIDA PUFF 30 kgs', name: item.product_name_3 ?? 'Unknown Product',
category: item.product_category || item.product_category_3 || item.category || 'MAIDA PUFF', category: item.product_category_3 ?? 'Unknown Category',
sku: item.sku || item.sku_3 || '30', sku: item.sku_3 ?? '30',
skuCode: item.sku_code || item.sku_code_3 || 'MP30', skuCode: item.sku_code_3 ?? 'Unknown SKU Code',
brCode: item.br_code || item.br_code_3 || item.br || 'PM', brCode: item.br_code_3 ?? 'Unknown BR Code',
bags, bags,
totalKgs: bags * skuVal, totalKgs: bags * skuVal,
}; };
}); });
} else {
orderItems = [];
} }
// Calculate totals // 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 totalBags = Number(rowSrc.total_bags_3 ?? data?.total_bags_3 ?? 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 totalKgs = Number(rowSrc.total_kgs_3 ?? data?.total_kgs_3 ?? 0);
const lineItemsCount = orderItems.length; const lineItemsCount = orderItems.length;
const orderId = String(rowSrc.order_id || data?.order_id || rowSrc.order_number || `No data`); const orderId = String(rowSrc.order_id || data?.order_id || rowSrc.order_number || `No data`);
@ -347,6 +379,7 @@ export function CallDetail({
const channel = String(rowSrc.order_received_channel const channel = String(rowSrc.order_received_channel
|| rowSrc.channel || data?.order_received_channel || 'No data'); || rowSrc.channel || data?.order_received_channel || 'No data');
const storeNotes = selectStore.notes || 'No notes available.'; const storeNotes = selectStore.notes || 'No notes available.';
const visitNotes = String(rowSrc.notes || data?.notes || rowSrc.remarks || data?.remarks || rowSrc.remark || data?.remark || '');
const currentStateName = String(rowSrc.current_state_name || data?.current_state_name || 'No data'); const currentStateName = String(rowSrc.current_state_name || data?.current_state_name || 'No data');
return ( return (
@ -406,19 +439,19 @@ export function CallDetail({
{/* Top Right KPI Grid */} {/* Top Right KPI Grid */}
<div className="flex flex-nowrap items-center gap-3 w-full lg:w-auto overflow-x-auto pb-2 min-w-0"> <div className="flex flex-nowrap items-center gap-3 w-full lg:w-auto overflow-x-auto pb-2 min-w-0">
<div className="bg-slate-50 border border-slate-200/60 rounded-xl p-3 text-center min-w-[100px] flex-1"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[100px] flex-1">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">TOTAL BAGS</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">TOTAL BAGS</div>
<div className="text-xl font-bold text-slate-900">{totalBags.toLocaleString()}</div> <div className="text-xl font-bold text-slate-900">{totalBags.toLocaleString()}</div>
</div> </div>
<div className="bg-slate-50 border border-slate-200/60 rounded-xl p-3 text-center min-w-[100px] flex-1"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[100px] flex-1">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">TOTAL KGS</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">TOTAL KGS</div>
<div className="text-xl font-bold text-slate-900">{totalKgs.toLocaleString()}</div> <div className="text-xl font-bold text-slate-900">{totalKgs.toLocaleString()}</div>
</div> </div>
<div className="bg-slate-50 border border-slate-200/60 rounded-xl p-3 text-center min-w-[100px] flex-1"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[100px] flex-1">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">LINE ITEMS</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">ORDER ITEMS</div>
<div className="text-xl font-bold text-slate-900">{lineItemsCount}</div> <div className="text-xl font-bold text-slate-900">{lineItemsCount}</div>
</div> </div>
<div className="bg-slate-50 border border-slate-200/60 rounded-xl p-3 text-center min-w-[110px] flex-1"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[110px] flex-1">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">SALES OFFICER</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">SALES OFFICER</div>
<div className="text-sm font-bold text-slate-900 truncate mt-1">{salesOfficer}</div> <div className="text-sm font-bold text-slate-900 truncate mt-1">{salesOfficer}</div>
</div> </div>
@ -728,37 +761,47 @@ export function CallDetail({
</div> </div>
{/* Right Store Proof Photo */} {/* Right Store Proof Photo */}
<div className="space-y-2"> <div className="space-y-4">
<div className="text-[11px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1"> {visitNotes && visitNotes.trim() !== '' && visitNotes.toLowerCase() !== 'undefined' && visitNotes.toLowerCase() !== 'null' && (
<FileText size={12} /> UPLOADED PROOF <div className="space-y-2">
</div> <div className="text-[11px] font-bold text-emerald-600 uppercase tracking-wider flex items-center gap-1">
<FileText size={12} className="text-emerald-500" /> VISIT NOTES
{uploadedImages.length > 0 ? ( </div>
<div className="space-y-3"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3.5 text-xs text-slate-700 leading-relaxed font-medium">
{uploadedImages.map((file: any, idx: number) => { {visitNotes}
const previewUrl = `${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${file.uuid}/preview`; </div>
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-400 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="rounded-xl border border-slate-200/80 bg-slate-50 p-6 flex flex-col items-center justify-center text-center gap-2">
<Camera className="text-slate-400" size={24} />
<span className="text-xs font-semibold text-slate-500">No proof image uploaded</span>
</div> </div>
)} )}
<div className="space-y-2">
<div className="text-[11px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1">
<Camera 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">
<SafeImage
src={previewUrl}
alt={file.original_name || 'Uploaded Proof'}
type="proof"
className="w-full h-auto max-h-[300px] object-cover transition-transform group-hover:scale-[1.01]"
/>
<a href={previewUrl} target="_blank" rel="noopener noreferrer" className="absolute inset-0 z-10" aria-label="View Full Image" />
</div>
);
})}
</div>
) : (
<div className="rounded-xl border border-emerald-200 bg-[#E8FBF0] p-6 flex flex-col items-center justify-center text-center gap-2">
<Camera className="text-emerald-500" size={24} />
<span className="text-xs font-semibold text-emerald-700">No proof image uploaded</span>
</div>
)}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -772,21 +815,20 @@ export function CallDetail({
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 overflow-hidden shadow-sm"> <div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 overflow-hidden shadow-sm">
{storeImages.length > 0 ? ( {storeImages.length > 0 ? (
<div className="relative w-full h-44 bg-slate-100 flex items-center justify-center overflow-hidden"> <div className="relative w-full h-44 bg-slate-100 flex items-center justify-center overflow-hidden">
<img <SafeImage
src={`${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${storeImages[0].uuid}/preview`} src={`${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${storeImages[0].uuid}/preview`}
alt="Store Front" alt="Store Front"
type="store"
text={storeName}
className="w-full h-full object-cover" className="w-full h-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/> />
</div> </div>
) : ( ) : (
<div className="h-40 bg-gradient-to-br from-slate-100 to-slate-200/70 p-6 flex flex-col items-center justify-center text-center gap-2 border-b border-slate-200/50"> <div className="h-40 bg-[#E8FBF0] p-6 flex flex-col items-center justify-center text-center gap-2 border-b border-emerald-200">
<div className="w-12 h-12 rounded-full bg-[var(--tiles-card-bg)] shadow-sm flex items-center justify-center text-slate-500"> <div className="w-12 h-12 rounded-full bg-[var(--tiles-card-bg)] shadow-sm flex items-center justify-center text-emerald-500">
<ImageIcon size={22} /> <ImageIcon size={22} />
</div> </div>
<span className="text-xs font-bold text-slate-600 uppercase tracking-wider">{storeName}</span> <span className="text-xs font-bold text-emerald-700 uppercase tracking-wider">{storeName}</span>
</div> </div>
)} )}
</div> </div>
@ -845,9 +887,9 @@ export function CallDetail({
</div> </div>
{/* Notes Container */} {/* Notes Container */}
<div className="bg-slate-50/80 border border-slate-200/80 rounded-xl p-3.5 space-y-1.5 text-xs"> <div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3.5 space-y-1.5 text-xs">
<div className="font-bold text-slate-600 flex items-center gap-1.5 text-[11px] uppercase tracking-wider"> <div className="font-bold text-emerald-600 flex items-center gap-1.5 text-[11px] uppercase tracking-wider">
<FileText size={12} /> NOTES <FileText size={12} className="text-emerald-500" /> NOTES
</div> </div>
<p className="text-slate-700 leading-relaxed font-medium"> <p className="text-slate-700 leading-relaxed font-medium">
{storeNotes} {storeNotes}

View File

@ -284,12 +284,12 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
<Map size={14} className="text-slate-500" /> Route Assignment <Map size={14} className="text-slate-500" /> Route Assignment
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="border border-slate-100 rounded-xl p-4 bg-slate-50 flex flex-col justify-between"> <div className="border border-emerald-200 rounded-xl p-4 bg-[#E8FBF0] flex flex-col justify-between">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">Route Code</div> <div className="text-[10px] font-bold text-emerald-600 uppercase tracking-wider mb-3">Route Code</div>
<div className="text-lg font-bold text-slate-900">{String(routeCode)}</div> <div className="text-lg font-bold text-slate-900">{String(routeCode)}</div>
</div> </div>
<div className="border border-slate-100 rounded-xl p-4 bg-slate-50 flex flex-col justify-between"> <div className="border border-emerald-200 rounded-xl p-4 bg-[#E8FBF0] flex flex-col justify-between">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">Sub Route</div> <div className="text-[10px] font-bold text-emerald-600 uppercase tracking-wider mb-3">Sub Route</div>
<div className="text-lg font-bold text-slate-900">{String(subRoute)}</div> <div className="text-lg font-bold text-slate-900">{String(subRoute)}</div>
</div> </div>
</div> </div>
@ -312,9 +312,9 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
</div> </div>
); );
}) : ( }) : (
<div className="w-full h-32 rounded-lg bg-slate-50 border border-dashed border-slate-200 flex flex-col items-center justify-center text-slate-400"> <div className="w-full h-32 rounded-lg bg-[#E8FBF0] border border-emerald-200 flex flex-col items-center justify-center text-emerald-700">
<Camera size={24} className="mb-2 opacity-50" /> <Camera size={24} className="mb-2 text-emerald-500" />
<span className="text-xs">No image uploaded</span> <span className="text-xs font-semibold">No image uploaded</span>
</div> </div>
)} )}
<div className="text-[10px] text-slate-400 mt-2">Uploaded at punch in.</div> <div className="text-[10px] text-slate-400 mt-2">Uploaded at punch in.</div>
@ -329,8 +329,8 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0"> <div className="w-8 h-8 rounded-full bg-[#E8FBF0] border border-emerald-200 flex items-center justify-center shrink-0">
<User size={12} className="text-slate-500" /> <User size={12} className="text-emerald-500" />
</div> </div>
<div> <div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">Name</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">Name</div>
@ -338,8 +338,8 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0"> <div className="w-8 h-8 rounded-full bg-[#E8FBF0] border border-emerald-200 flex items-center justify-center shrink-0">
<Mail size={12} className="text-slate-500" /> <Mail size={12} className="text-emerald-500" />
</div> </div>
<div className="overflow-hidden"> <div className="overflow-hidden">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">Email</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">Email</div>
@ -347,8 +347,8 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0"> <div className="w-8 h-8 rounded-full bg-[#E8FBF0] border border-emerald-200 flex items-center justify-center shrink-0">
<Hash size={12} className="text-slate-500" /> <Hash size={12} className="text-emerald-500" />
</div> </div>
<div> <div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">User ID</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">User ID</div>

View File

@ -4,7 +4,7 @@ import { ORDER_BOOKING } from '../../api/config';
import { Card } from '../reusable/Card'; import { Card } from '../reusable/Card';
import { Spinner } from '../reusable/Spinner'; import { Spinner } from '../reusable/Spinner';
import { EmptyState } from '../reusable/EmptyState'; import { EmptyState } from '../reusable/EmptyState';
import { Store, User, Package, Calendar, Hash, Mail, Weight, List, MapPin } from 'lucide-react'; import { Store, User, Package, Calendar, Hash, Weight, MapPin, ShoppingBag } from 'lucide-react';
export interface WiredDetailViewProps { export interface WiredDetailViewProps {
instanceId: number | string; instanceId: number | string;
@ -116,163 +116,169 @@ export function OrderDetail({ instanceId }: WiredDetailViewProps) {
const totalKgs = extract('total_kgs_3', remainingData) || 0; const totalKgs = extract('total_kgs_3', remainingData) || 0;
return ( return (
<div className="flex flex-col gap-4 bg-[var(--tiles-card-bg)] p-1"> <div className="flex flex-col gap-6 bg-transparent">
{/* Top Section */} {/* Header */}
<div className="border border-slate-200 rounded-xl p-6 bg-[var(--tiles-card-bg)] shadow-sm flex flex-col md:flex-row md:items-start justify-between gap-4"> <div className="flex items-center gap-4">
<div> <div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Order ID</div> <h1 className="text-xl font-semibold tracking-tight text-slate-900">Order Detail</h1>
<div className="text-2xl font-black text-slate-900 mb-2">{String(orderId)}</div> <p className="text-sm text-slate-500">#{orderId}</p>
<div className="flex items-center gap-3 text-xs text-slate-500 font-medium"> </div>
<span className="flex items-center gap-1.5"><Calendar size={14} /> {String(dateOfOrder)}</span> </div>
<span className="flex items-center gap-1.5"><Hash size={14} /> Instance #{instanceId}</span>
{/* Status + Date ribbon */}
<div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-4 shadow-sm">
<div className="flex items-center gap-3">
<span className="inline-flex items-center gap-1.5 rounded-full bg-emerald-100 px-3 py-1 text-sm font-semibold text-emerald-800">
<span className="h-2 w-2 rounded-full bg-emerald-600" />
{String(stateName)}
</span>
<span className="text-sm text-slate-500 hidden sm:inline">Order ID</span>
<span className="font-mono text-sm font-semibold text-slate-800 hidden sm:inline">{String(orderId)}</span>
</div>
<div className="flex items-center gap-2 text-sm text-slate-500">
<Calendar className="h-4 w-4" />
<span>{String(dateOfOrder)}</span>
<span className="ml-2 pl-2 border-l border-slate-200 flex items-center gap-1.5 text-xs">
<Hash className="h-3 w-3" />
Instance #{instanceId}
</span>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{/* Main content */}
<div className="space-y-6 lg:col-span-2">
{/* Store summary */}
<div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
<div className="mb-4 flex items-center gap-2">
<div className="grid h-8 w-8 place-items-center rounded-full bg-emerald-100 text-emerald-600">
<Store className="h-4 w-4" />
</div>
<h2 className="text-sm font-semibold uppercase tracking-wider text-emerald-800">Store</h2>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<p className="text-xs text-slate-500">Business</p>
<p className="font-medium text-slate-900">{String(storeName)}</p>
</div>
<div>
<p className="text-xs text-slate-500">Distributor</p>
<p className="font-medium text-slate-900">{String(distributorName)}</p>
</div>
<div>
<p className="text-xs text-slate-500">Route</p>
<p className="font-medium text-slate-900">
{String(routeName)} ({String(routeCode)})
</p>
</div>
<div>
<p className="text-xs text-slate-500">Store Code</p>
<p className="font-mono font-medium text-slate-800">{(storeRaw as any)?.store_code_2 || (storeRaw as any)?.store_code || '-'}</p>
</div>
</div>
</div>
{/* Order items */}
<div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
<div className="mb-4 flex items-center gap-2">
<div className="grid h-8 w-8 place-items-center rounded-full bg-emerald-100 text-emerald-600">
<ShoppingBag className="h-4 w-4" />
</div>
<h2 className="text-sm font-semibold uppercase tracking-wider text-emerald-800">Order Items</h2>
<span className="ml-auto text-xs text-slate-500 font-semibold bg-slate-100 px-2 py-1 rounded-md">{itemsArray.length} items</span>
</div>
<div className="divide-y divide-slate-100">
{itemsArray.map((item: any, index: number) => {
const product = item.product_name_3 || item.product_name || '-';
const category = item.product_category_3 || item.product_category || '-';
const sku = item.sku_code_3 || item.sku_code || item.sku || '-';
const bags = item.bags_3 || item.bags || item.quantity || 0;
const weight = item.sku_3 || item.sku || '';
return (
<div key={index} className="group flex flex-col sm:flex-row sm:items-center justify-between py-4 first:pt-2 last:pb-2 gap-3">
<div className="flex items-start gap-4">
<div className="grid h-10 w-10 place-items-center rounded-xl bg-emerald-50 text-emerald-600 border border-emerald-100 shrink-0 mt-1 sm:mt-0">
<Package className="h-5 w-5" />
</div>
<div>
<p className="font-medium text-slate-900">{String(product)}</p>
<div className="mt-1 flex flex-wrap items-center gap-2 text-[10px] text-slate-500">
<span className="rounded text-slate-600 font-medium">
{String(category)}
</span>
<span className="w-1 h-1 rounded-full bg-slate-300"></span>
<span className="font-mono text-slate-500">{String(sku)}</span>
</div>
</div>
</div>
<div className="text-left sm:text-right pl-14 sm:pl-0">
<p className="font-bold text-slate-900 text-sm">{String(bags)} bag{Number(bags) === 1 ? "" : "s"}</p>
{weight && <p className="text-xs text-slate-500 mt-0.5">SKU {String(weight)}</p>}
</div>
</div>
)
})}
{itemsArray.length === 0 && (
<div className="py-6 text-center text-sm text-slate-500 italic">No items found in this order.</div>
)}
</div>
</div> </div>
</div> </div>
<div className="border border-slate-100 bg-slate-50 rounded-xl p-4 flex flex-col gap-1 min-w-[200px]">
<div className="flex items-center gap-1.5 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">
<Store size={12} /> Store
</div>
<div className="text-sm font-bold text-slate-900">{String(storeName)}</div>
<div className="flex items-center gap-2 text-[10px] text-slate-500 mt-1">
<span className="flex items-center gap-1"><MapPin size={12} /> {String(routeCode)} - {String(routeName)}</span>
</div>
<div className="flex items-center gap-2 text-[10px] text-slate-500 mt-0.5">
<span className="flex items-center gap-1"><User size={12} /> {String(distributorName)}</span>
</div>
</div>
</div>
{/* Middle Stats Section */} {/* Sidebar */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="space-y-6">
<div className="border border-slate-200 rounded-xl p-5 bg-[var(--tiles-card-bg)] shadow-sm flex flex-col justify-between"> {/* Totals */}
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3"> <div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
<Package size={14} className="text-emerald-500" /> Total Bags <div className="mb-4 flex items-center gap-2">
</div> <div className="grid h-8 w-8 place-items-center rounded-full bg-blue-100 text-blue-600">
<div className="text-2xl font-bold text-slate-900">{String(totalBags)}</div> <Weight className="h-4 w-4" />
</div> </div>
<div className="border border-slate-200 rounded-xl p-5 bg-[var(--tiles-card-bg)] shadow-sm flex flex-col justify-between"> <h2 className="text-sm font-semibold uppercase tracking-wider text-blue-800">Totals</h2>
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">
<Weight size={14} className="text-blue-500" /> Total Kilograms
</div>
<div className="text-2xl font-bold text-slate-900">{Number(totalKgs).toLocaleString()}</div>
</div>
<div className="border border-slate-200 rounded-xl p-5 bg-[var(--tiles-card-bg)] shadow-sm flex flex-col justify-between">
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">
<List size={14} className="text-slate-500" /> Line Items
</div>
<div className="text-2xl font-bold text-slate-900">{itemsArray.length}</div>
</div>
</div>
{/* Bottom Section */}
<div className="flex flex-col lg:flex-row gap-4">
{/* Left Table */}
<div className="flex-[2] border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden flex flex-col min-w-0">
<div className="p-4 border-b border-slate-100 flex items-center gap-2 bg-slate-50">
<Package size={14} className="text-slate-500" />
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider">Order Items</span>
</div> </div>
<div className="overflow-x-auto flex-1"> <div className="space-y-3">
<table className="w-full text-sm text-left"> <div className="flex items-center justify-between rounded-xl bg-slate-50 border border-slate-100 p-3.5">
<thead className="bg-slate-50/50 text-[10px] font-bold text-slate-400 uppercase tracking-wider"> <span className="text-sm font-medium text-slate-600">Total Bags</span>
<tr> <span className="font-mono text-lg font-bold text-slate-800">{String(totalBags)}</span>
<th className="px-5 py-3 border-b border-slate-100">Product</th> </div>
<th className="px-5 py-3 border-b border-slate-100">Category</th> <div className="flex items-center justify-between rounded-xl bg-slate-50 border border-slate-100 p-3.5">
<th className="px-5 py-3 border-b border-slate-100">SKU</th> <span className="text-sm font-medium text-slate-600">Total Weight</span>
<th className="px-5 py-3 border-b border-slate-100 text-right">Bags</th> <span className="font-mono text-lg font-bold text-slate-800">{Number(totalKgs).toLocaleString()} kg</span>
</tr> </div>
</thead>
<tbody className="divide-y divide-slate-100">
{itemsArray.map((item: any, idx: number) => {
const product = item.product_name_3 || '-';
const category = item.product_category_3 || '-';
const sku = item.sku_code_3 || '-';
const bags = item.bags_3 || '0';
const brCode = item.br_code_3 || '-';
const weight = item.sku_3 ? `${item.sku_3} kg` : '-';
return (
<tr key={idx} className="hover:bg-slate-50/50 transition-colors">
<td className="px-5 py-3">
<div className="font-bold text-slate-900 text-xs mb-0.5 whitespace-nowrap">{String(product)}</div>
<div className="text-[10px] text-slate-500">BR Code {String(brCode)}</div>
</td>
<td className="px-5 py-3 text-xs text-slate-600 whitespace-nowrap">{String(category)}</td>
<td className="px-5 py-3">
<div className="text-xs text-slate-600 mb-0.5 whitespace-nowrap">{String(sku)}</div>
<div className="text-[10px] text-slate-500 whitespace-nowrap">{String(weight)}</div>
</td>
<td className="px-5 py-3 text-right font-semibold text-slate-900 text-xs whitespace-nowrap">{String(bags)}</td>
</tr>
);
})}
<tr className="bg-slate-50 font-bold">
<td colSpan={3} className="px-5 py-3 text-slate-900 text-xs">Total</td>
<td className="px-5 py-3 text-right text-slate-900 text-xs">{String(totalBags)}</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
{/* Right Sidebar */} {/* Sales officer */}
<div className="flex-1 flex flex-col gap-4 min-w-[280px]"> <div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
{/* SO Card */} <div className="mb-4 flex items-center gap-2">
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5"> <div className="grid h-8 w-8 place-items-center rounded-full bg-purple-100 text-purple-600">
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4"> <User className="h-4 w-4" />
<User size={14} className="text-slate-500" /> Sales Officer </div>
</div> <h2 className="text-sm font-semibold uppercase tracking-wider text-purple-800">Sales Officer</h2>
<div className="flex items-center gap-3 mb-5">
<div className="w-10 h-10 rounded-full bg-slate-900 text-white flex items-center justify-center font-bold text-sm shrink-0">
{soInitials}
</div>
<div>
<div className="text-sm font-bold text-slate-900">{String(soName)}</div>
<div className="text-[10px] text-slate-500">Sales Officer - ID {String(soId)}</div>
</div>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0">
<Mail size={12} className="text-slate-500" />
</div>
<div className="overflow-hidden">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">Email</div>
<div className="text-xs font-semibold text-slate-900 truncate">{String(soEmail)}</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0">
<Hash size={12} className="text-slate-500" />
</div>
<div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-0.5">User ID</div>
<div className="text-xs font-semibold text-slate-900">{String(soId)}</div>
</div>
</div>
</div>
</div> </div>
<div className="flex items-center gap-3">
<div className="grid h-11 w-11 place-items-center rounded-full bg-slate-800 text-white text-sm font-bold shrink-0">
{soInitials}
</div>
<div className="min-w-0">
<p className="font-medium text-slate-900 truncate">{String(soName)}</p>
<p className="text-xs text-slate-500 truncate">{String(soEmail)}</p>
<p className="text-[10px] text-slate-400 mt-0.5">ID: {String(soId)}</p>
</div>
</div>
</div>
{/* Order Meta */} {/* Location hint */}
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5"> <div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4"> <div className="mb-3 flex items-center gap-2">
<Hash size={14} className="text-slate-500" /> Order Meta <div className="grid h-8 w-8 place-items-center rounded-full bg-amber-100 text-amber-600">
</div> <MapPin className="h-4 w-4" />
<div className="flex flex-col gap-3"> </div>
<div className="flex justify-between items-center text-xs"> <h2 className="text-sm font-semibold uppercase tracking-wider text-amber-800">Location</h2>
<span className="text-slate-500">Instance</span>
<span className="font-bold text-slate-900">#{instanceId}</span>
</div>
<div className="flex justify-between items-center text-xs">
<span className="text-slate-500">State</span>
<span className="font-bold text-slate-900">{String(stateName)}</span>
</div>
<div className="flex justify-between items-center text-xs">
<span className="text-slate-500">Date of Order</span>
<span className="font-bold text-slate-900">{String(dateOfOrder)}</span>
</div>
</div>
</div> </div>
</div> <p className="text-sm text-slate-500 leading-relaxed">Order placed in route <span className="font-semibold text-slate-800">{String(routeName)}</span>.</p>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@ -77,7 +77,7 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
const badgeClass = isSuccess ? 'bg-emerald-100 text-emerald-700' : 'bg-blue-100 text-blue-700'; const badgeClass = isSuccess ? 'bg-emerald-100 text-emerald-700' : 'bg-blue-100 text-blue-700';
// Store Overview // Store Overview
const storeCode = extract('store_code', remainingData) || '-'; const storeCode = extract('store_code_2', remainingData) || '-';
const businessName = extract('business_name', remainingData) || '-'; const businessName = extract('business_name', remainingData) || '-';
const area = extract('area', remainingData); const area = extract('area', remainingData);
const completeAddress = extract('complete_address', remainingData); const completeAddress = extract('complete_address', remainingData);
@ -301,16 +301,16 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider m-0">Route Assignment</h3> <h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider m-0">Route Assignment</h3>
</div> </div>
<div className="p-5 grid grid-cols-1 sm:grid-cols-3 gap-4 bg-[var(--tiles-card-bg)]"> <div className="p-5 grid grid-cols-1 sm:grid-cols-3 gap-4 bg-[var(--tiles-card-bg)]">
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50"> <div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Route</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Route</div>
<div className="text-base font-bold text-slate-900">{routeName ? String(routeName) : '-'}</div> <div className="text-base font-bold text-slate-900">{routeName ? String(routeName) : '-'}</div>
<div className="text-xs text-slate-500 mt-1">Code {routeCode ? String(routeCode) : '-'}</div> <div className="text-xs text-slate-500 mt-1">Code {routeCode ? String(routeCode) : '-'}</div>
</div> </div>
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50"> <div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Sub Route</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Sub Route</div>
<div className="text-base font-bold text-slate-900">{subRoute ? String(subRoute) : '-'}</div> <div className="text-base font-bold text-slate-900">{subRoute ? String(subRoute) : '-'}</div>
</div> </div>
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50"> <div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Area</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Area</div>
<div className="text-base font-bold text-slate-900">{area ? String(area) : '-'}</div> <div className="text-base font-bold text-slate-900">{area ? String(area) : '-'}</div>
<div className="text-xs text-slate-500 mt-1">PIN {pinCode ? String(pinCode) : '-'}</div> <div className="text-xs text-slate-500 mt-1">PIN {pinCode ? String(pinCode) : '-'}</div>
@ -360,7 +360,7 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider m-0">Distributor</h3> <h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider m-0">Distributor</h3>
</div> </div>
<div className="p-5 bg-[var(--tiles-card-bg)]"> <div className="p-5 bg-[var(--tiles-card-bg)]">
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50"> <div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
<div className="text-sm font-bold text-slate-900 mb-1">{distributorName ? String(distributorName) : '-'}</div> <div className="text-sm font-bold text-slate-900 mb-1">{distributorName ? String(distributorName) : '-'}</div>
<div className="text-xs text-slate-500 mb-4">Owner · {distributorOwnerName ? String(distributorOwnerName) : '-'}</div> <div className="text-xs text-slate-500 mb-4">Owner · {distributorOwnerName ? String(distributorOwnerName) : '-'}</div>

View File

@ -17,7 +17,7 @@ import {
WfLookupField, WfLookupField,
RadioField, RadioField,
} from './fields'; } from './fields';
import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE } from '../../api/config'; import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE, HIDDEN_FORM_FIELDS } from '../../api/config';
import { isCategoryColumn, isProductColumn } from './fields/gridUtils'; import { isCategoryColumn, isProductColumn } from './fields/gridUtils';
export interface DynamicFormProps { export interface DynamicFormProps {
@ -75,14 +75,8 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
} }
}); });
} }
const imageFieldIds = new Set(
res.fields.filter(f => f.data_type === 'image' || f.data_type === 'file').map(f => f.id)
);
const mapPrefillData = (sourceData: Record<string, unknown>) => { const mapPrefillData = (sourceData: Record<string, unknown>) => {
res.fields.forEach(f => { res.fields.forEach(f => {
if (imageFieldIds.has(f.id)) return;
const getBaseId = (id: string) => id.replace(/_\d+$/, ''); const getBaseId = (id: string) => id.replace(/_\d+$/, '');
if (sourceData[f.id] !== undefined) { if (sourceData[f.id] !== undefined) {
@ -132,13 +126,13 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
if (customPrefillData) { if (customPrefillData) {
Object.entries(customPrefillData).forEach(([k, v]) => { Object.entries(customPrefillData).forEach(([k, v]) => {
if (!imageFieldIds.has(k)) defaultValues[k] = v; defaultValues[k] = v;
}); });
} }
if (chainedPrefillData) { if (chainedPrefillData) {
Object.entries(chainedPrefillData).forEach(([k, v]) => { Object.entries(chainedPrefillData).forEach(([k, v]) => {
if (!imageFieldIds.has(k)) defaultValues[k] = v; defaultValues[k] = v;
}); });
} }
@ -577,6 +571,13 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
for (const f of fields) { for (const f of fields) {
const val = finalValues[f.id]; const val = finalValues[f.id];
const isRemark = (f.name || '').toLowerCase().includes('remark') || (f.name || '').toLowerCase().includes('note');
if (isRemark && (val == null || val === '')) {
payload[f.id] = '';
continue;
}
if (val == null) continue; if (val == null) continue;
if (f.data_type === 'phone' && typeof val === 'string') { if (f.data_type === 'phone' && typeof val === 'string') {
@ -586,16 +587,22 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
phone: phoneNum, phone: phoneNum,
phone_with_dial_code: `+91${phoneNum}` phone_with_dial_code: `+91${phoneNum}`
}; };
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val) && val.length > 0 && val[0] instanceof File) { } else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val)) {
const uploadedFiles = []; const uploadedFiles = [];
for (const file of val) { for (const file of val) {
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId }); if (file instanceof File) {
uploadedFiles.push(fileMeta); const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
uploadedFiles.push(fileMeta);
} else {
uploadedFiles.push(file);
}
} }
payload[f.id] = uploadedFiles; payload[f.id] = uploadedFiles;
} else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) { } else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) {
const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId }); const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
payload[f.id] = [fileMeta]; payload[f.id] = [fileMeta];
} else if ((f.data_type === 'image' || f.data_type === 'file') && val && typeof val === 'object') {
payload[f.id] = [val];
} else if (f.data_type.startsWith('grid') || f.data_type === 'smart_grid') { } else if (f.data_type.startsWith('grid') || f.data_type === 'smart_grid') {
const gridRows = Array.isArray(val) ? val : []; const gridRows = Array.isArray(val) ? val : [];
const getFormattedGridVal = (colKey: string, rawVal: unknown, colDef?: FormScreenField) => { const getFormattedGridVal = (colKey: string, rawVal: unknown, colDef?: FormScreenField) => {
@ -853,6 +860,9 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
f.id === 'total_kgs'; f.id === 'total_kgs';
if (isTotalField) return false; if (isTotalField) return false;
} }
if (HIDDEN_FORM_FIELDS.includes(f.id) || HIDDEN_FORM_FIELDS.includes(f.name)) {
return false;
}
return true; return true;
}); });

View File

@ -176,7 +176,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
try { try {
const formDataToSend = cleanFormData(formDataOverride || valuesRef.current); const formDataToSend = cleanFormData(formDataOverride || valuesRef.current);
console.log('[LogVisitForm] Executing select_store lookup with prefilled formData:', formDataToSend);
const lookupRes = await client.wfLookupRecords({ const lookupRes = await client.wfLookupRecords({
activityId, activityId,
@ -239,7 +239,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
time_of_visit: timeVal, time_of_visit: timeVal,
}); });
console.log('[LogVisitForm] Fetching initial daily_log lookup with payload:', payload);
const dailyLogLookupRes = await client.wfLookupRecords({ const dailyLogLookupRes = await client.wfLookupRecords({
activityId, activityId,
@ -254,7 +254,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
if (arr.length > 0) { if (arr.length > 0) {
const firstLog = arr[0]; const firstLog = arr[0];
console.log('[LogVisitForm] Successfully fetched daily log record:', firstLog);
const dailyLogInstanceId = String(firstLog.instance_id || firstLog.id || ''); const dailyLogInstanceId = String(firstLog.instance_id || firstLog.id || '');
const routeCode = String( const routeCode = String(
@ -291,7 +291,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
valuesRef.current = updated; valuesRef.current = updated;
setValues(updated); setValues(updated);
console.log('[LogVisitForm] Daily log prefill complete:', updated);
} else { } else {
console.warn('[LogVisitForm] No daily log records found for date/time:', dateVal, timeVal); console.warn('[LogVisitForm] No daily log records found for date/time:', dateVal, timeVal);
} }
@ -343,7 +343,11 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
const payload: Record<string, any> = {}; const payload: Record<string, any> = {};
validFieldIds.forEach(fieldId => { validFieldIds.forEach(fieldId => {
const val = valuesRef.current[fieldId]; const val = valuesRef.current[fieldId];
if (val !== undefined && val !== null && val !== '') { const isRemark = fieldId.toLowerCase().includes('remark') || fieldId.toLowerCase().includes('note');
if (isRemark && (val === undefined || val === null || val === '')) {
payload[fieldId] = '';
} else if (val !== undefined && val !== null && val !== '') {
payload[fieldId] = val; payload[fieldId] = val;
} }
}); });
@ -366,14 +370,14 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
} }
payload['upload_image'] = uploadedFiles; payload['upload_image'] = uploadedFiles;
console.log('[LogVisitForm] Submitting clean startInstance payload:', payload);
const res: any = await client.startInstance(activityId, payload); const res: any = await client.startInstance(activityId, payload);
const chainSource = res?.activity_chain || schema?.activity_chain || []; const chainSource = res?.activity_chain || schema?.activity_chain || [];
if (chainSource && chainSource.length > 0) { if (chainSource && chainSource.length > 0) {
const nextAct = chainSource[0]; const nextAct = chainSource[0];
console.log('[LogVisitForm] Activity chain detected, transitioning to DynamicForm:', nextAct);
onActivityChange?.(nextAct.activity_name); onActivityChange?.(nextAct.activity_name);
setChainedActivity({ setChainedActivity({

View File

@ -1,12 +1,17 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { BASE_URL, APP_ID } from '../../../api/config';
export function ImagePreview({ file }: { file: File }) { export function ImagePreview({ file }: { file: File | any }) {
const [url, setUrl] = useState<string | null>(null); const [url, setUrl] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const objectUrl = URL.createObjectURL(file); if (file instanceof File) {
setUrl(objectUrl); const objectUrl = URL.createObjectURL(file);
return () => URL.revokeObjectURL(objectUrl); setUrl(objectUrl);
return () => URL.revokeObjectURL(objectUrl);
} else if (file && file.uuid) {
setUrl(`${BASE_URL}/app/${APP_ID}/view/files/${file.uuid}/preview`);
}
}, [file]); }, [file]);
if (!url) return null; if (!url) return null;
@ -23,10 +28,10 @@ export function FileInput({
label: string; label: string;
type: 'image' | 'file' | string; type: 'image' | 'file' | string;
required?: boolean; required?: boolean;
value: File[] | undefined | null | unknown; value: File[] | any[] | undefined | null | unknown;
onChange: (files: File[]) => void; onChange: (files: (File | any)[]) => void;
}) { }) {
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []); const files = Array.isArray(value) ? value : (value ? [value] : []);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) { if (e.target.files) {
@ -68,7 +73,7 @@ export function FileInput({
<ImagePreview file={file} /> <ImagePreview file={file} />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted"> <div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted">
{file.name} {file.name || file.original_name || 'File'}
</div> </div>
)} )}
<button <button

View File

@ -1,9 +1,12 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { Modal } from '../../reusable/Modal'; import { Modal } from '../../reusable/Modal';
import { Button } from '../../buttons/Button'; import { Button } from '../../buttons/Button';
import { useJsApiLoader, GoogleMap, Marker } from '@react-google-maps/api'; import { useJsApiLoader, GoogleMap } from '@react-google-maps/api';
import { CustomAdvancedMarker } from '../../maps/CustomAdvancedMarker';
import { MapPin, Crosshair, Map as MapIcon, Loader2, X } from 'lucide-react'; import { MapPin, Crosshair, Map as MapIcon, Loader2, X } from 'lucide-react';
const libraries: ("marker")[] = ["marker"];
const mapContainerStyle = { const mapContainerStyle = {
width: '100%', width: '100%',
height: '400px', height: '400px',
@ -27,9 +30,12 @@ export function GeolocationInput({
const [isMapModalOpen, setIsMapModalOpen] = useState(false); const [isMapModalOpen, setIsMapModalOpen] = useState(false);
const [mapMarkerPos, setMapMarkerPos] = useState<{ lat: number; lng: number } | null>(null); const [mapMarkerPos, setMapMarkerPos] = useState<{ lat: number; lng: number } | null>(null);
const isLocalhost = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
const { isLoaded } = useJsApiLoader({ const { isLoaded } = useJsApiLoader({
id: 'google-map-script', id: 'google-map-script',
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '' googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''),
libraries
}); });
// Extract lat, lng & accuracy from value (supporting both latitude/longitude and lat/lng) // Extract lat, lng & accuracy from value (supporting both latitude/longitude and lat/lng)
@ -179,11 +185,12 @@ export function GeolocationInput({
options={{ options={{
streetViewControl: false, streetViewControl: false,
mapTypeControl: false, mapTypeControl: false,
fullscreenControl: false fullscreenControl: false,
mapId: "DEMO_MAP_ID"
}} }}
> >
{mapMarkerPos && ( {mapMarkerPos && (
<Marker position={mapMarkerPos} /> <CustomAdvancedMarker position={mapMarkerPos} />
)} )}
</GoogleMap> </GoogleMap>
)} )}

View File

@ -345,10 +345,10 @@ export function OrderGrid({
const computedRowKgs = skuNum * bagsNum; const computedRowKgs = skuNum * bagsNum;
const isMissingProd = isCategorySelected && !currentProdVal.trim(); const isMissingProd = isCategorySelected && !currentProdVal.trim();
const prodError = showErrors && isMissingProd ? 'Required' : undefined; const prodError = showErrors && isMissingProd ? true : undefined;
const isMissingBags = isCategorySelected && (!currentBagsVal || Number(currentBagsVal) <= 0); const isMissingBags = isCategorySelected && (!currentBagsVal || Number(currentBagsVal) <= 0);
const bagsError = showErrors && isMissingBags ? 'Required' : undefined; const bagsError = showErrors && isMissingBags ? true : undefined;
return ( return (
<div key={rowIdx}> <div key={rowIdx}>
@ -391,7 +391,7 @@ export function OrderGrid({
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)} onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
disabled={!isCategorySelected && categoriesList.length > 0} disabled={!isCategorySelected && categoriesList.length > 0}
options={[ options={[
{ value: '', label: isCategorySelected || categoriesList.length === 0 ? 'Select product' : 'Select category first' }, { value: '', label: isCategorySelected || categoriesList.length === 0 ? (prodError ? 'Select product *' : 'Select product') : 'Select category first' },
...prodOptions ...prodOptions
]} ]}
error={prodError} error={prodError}
@ -410,7 +410,7 @@ export function OrderGrid({
min={isMainBagsCol ? "1" : undefined} min={isMainBagsCol ? "1" : undefined}
value={numVal !== undefined && numVal !== null ? String(numVal) : ''} value={numVal !== undefined && numVal !== null ? String(numVal) : ''}
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)} onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
placeholder={isMainBagsCol && isCategorySelected ? 'Bags' : '0'} placeholder={isMainBagsCol ? (bagsError ? 'Bags *' : (isCategorySelected ? 'Bags' : '0')) : '0'}
error={isMainBagsCol ? bagsError : undefined} error={isMainBagsCol ? bagsError : undefined}
className="w-full" className="w-full"
/> />

View File

@ -0,0 +1,53 @@
import { useEffect, useRef } from 'react';
import { useGoogleMap } from '@react-google-maps/api';
export function CustomAdvancedMarker({
position,
title,
iconUrl,
onClick
}: {
position: google.maps.LatLngLiteral;
title?: string;
iconUrl?: string;
onClick?: () => void;
}) {
const map = useGoogleMap();
const markerRef = useRef<any>(null);
useEffect(() => {
if (!map || !window.google?.maps?.marker?.AdvancedMarkerElement) return;
let content: HTMLElement | undefined;
if (iconUrl) {
const img = document.createElement('img');
img.src = iconUrl;
img.style.width = '32px';
img.style.height = '32px';
content = img;
}
const options: google.maps.marker.AdvancedMarkerElementOptions = {
map,
position,
};
if (title) options.title = title;
if (content) options.content = content;
markerRef.current = new window.google.maps.marker.AdvancedMarkerElement(options);
if (onClick && markerRef.current) {
markerRef.current.addListener('gmp-click', onClick);
}
return () => {
if (markerRef.current) {
google.maps.event.clearInstanceListeners(markerRef.current);
markerRef.current.map = null;
}
};
}, [map, position.lat, position.lng, title, iconUrl, onClick]);
return null;
}

View File

@ -1,8 +1,11 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { useJsApiLoader, GoogleMap, Marker, InfoWindow } from '@react-google-maps/api'; import { useJsApiLoader, GoogleMap, InfoWindow } from '@react-google-maps/api';
import { Loader2, MapPin } from 'lucide-react'; import { Loader2, MapPin } from 'lucide-react';
import { PIPELINE } from '../../api/config'; import { PIPELINE } from '../../api/config';
import { dailyReportsClient } from '../../api/clients'; import { dailyReportsClient } from '../../api/clients';
import { CustomAdvancedMarker } from './CustomAdvancedMarker';
const libraries: ("marker")[] = ["marker"];
interface StoreLocation { interface StoreLocation {
latitude: number; latitude: number;
@ -50,9 +53,12 @@ export function DailyLogMap({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const mapRef = useRef<google.maps.Map | null>(null); const mapRef = useRef<google.maps.Map | null>(null);
const isLocalhost = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
const { isLoaded } = useJsApiLoader({ const { isLoaded } = useJsApiLoader({
id: 'google-map-script', id: 'google-map-script',
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '' googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''),
libraries
}); });
const fetchNearestStores = async (lat: number, lng: number) => { const fetchNearestStores = async (lat: number, lng: number) => {
@ -171,30 +177,29 @@ export function DailyLogMap({
options={{ options={{
streetViewControl: false, streetViewControl: false,
mapTypeControl: false, mapTypeControl: false,
fullscreenControl: false fullscreenControl: false,
mapId: "DEMO_MAP_ID"
}} }}
> >
{/* User's Current Location Marker */} {/* User's Current Location Marker */}
{userLocation && ( {userLocation && (
<Marker <CustomAdvancedMarker
position={userLocation} position={userLocation}
icon={{ iconUrl="https://maps.google.com/mapfiles/ms/icons/blue-dot.png"
url: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
}}
title="Location" title="Location"
/> />
)} )}
{/* Stores Markers */} {/* Stores Markers */}
{stores.map((store) => { {stores.map((store, index) => {
if (!store.location) return null; if (!store.location) return null;
const lat = Number(store.location.latitude); const lat = Number(store.location.latitude);
const lng = Number(store.location.longitude); const lng = Number(store.location.longitude);
if (isNaN(lat) || isNaN(lng)) return null; if (isNaN(lat) || isNaN(lng)) return null;
return ( return (
<Marker <CustomAdvancedMarker
key={store.store_code} key={store.store_code || index}
position={{ lat, lng }} position={{ lat, lng }}
onClick={() => setSelectedStore(store)} onClick={() => setSelectedStore(store)}
title={store.business_name} title={store.business_name}

View File

@ -203,9 +203,9 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
</table> </table>
</div> </div>
) : ( ) : (
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
{chartType === 3 || chartType === 4 ? ( {chartType === 3 || chartType === 4 ? (
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}> <PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
<Tooltip <Tooltip
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }} contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
itemStyle={{ color: '#0F172A', fontWeight: '500' }} itemStyle={{ color: '#0F172A', fontWeight: '500' }}
@ -219,22 +219,25 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
cy="50%" cy="50%"
outerRadius={100} outerRadius={100}
innerRadius={chartType === 4 ? 65 : 0} innerRadius={chartType === 4 ? 65 : 0}
style={{ outline: 'none' }}
activeShape={false}
className="focus:outline-none outline-none"
> >
{finalData.map((_, index) => ( {finalData.map((_, index) => (
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} /> <Cell key={`cell-${index}`} fill={colors[index % colors.length]} style={{ outline: 'none' }} className="focus:outline-none outline-none" />
))} ))}
</Pie> </Pie>
</PieChart> </PieChart>
) : chartType === 1 ? ( ) : chartType === 1 ? (
<LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}> <LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</LineChart> </LineChart>
) : chartType === 2 ? ( ) : chartType === 2 ? (
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}> <AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</AreaChart> </AreaChart>
) : ( ) : (
<BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}> <BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
{renderChartContent()} {renderChartContent()}
</BarChart> </BarChart>
)} )}

View File

@ -4,7 +4,7 @@ import { cn } from '../../lib/cn';
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'prefix'> { export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
label?: string; label?: string;
hint?: string; hint?: string;
error?: string; error?: string | boolean;
/** Leading adornment, e.g. "₹". */ /** Leading adornment, e.g. "₹". */
prefix?: ReactNode; prefix?: ReactNode;
/** Trailing adornment, e.g. "/ year". */ /** Trailing adornment, e.g. "/ year". */
@ -52,8 +52,10 @@ export function Input({
/> />
{suffix && <span className="text-faint text-sm">{suffix}</span>} {suffix && <span className="text-faint text-sm">{suffix}</span>}
</div> </div>
{(hint || error) && ( {(hint || (typeof error === 'string' && error)) && (
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>{error || hint}</span> <span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>
{typeof error === 'string' ? error : hint}
</span>
)} )}
</label> </label>
); );

View File

@ -12,7 +12,7 @@ export interface SelectOption {
export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> { export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
label?: string; label?: string;
hint?: string; hint?: string;
error?: string; error?: string | boolean;
placeholder?: string; placeholder?: string;
/** Either strings or {value,label} objects. */ /** Either strings or {value,label} objects. */
options?: Array<string | SelectOption>; options?: Array<string | SelectOption>;
@ -247,8 +247,10 @@ export function Select({
))} ))}
</select> </select>
{(hint || error) && ( {(hint || (typeof error === 'string' && error)) && (
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>{error || hint}</span> <span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>
{typeof error === 'string' ? error : hint}
</span>
)} )}
</div> </div>
); );

View File

@ -135,7 +135,7 @@ export function RecordView({
sortDir, sortDir,
presetAlias presetAlias
}); });
console.log("RECORD VIEW RESP:", r);
if (live) setResp(r); if (live) setResp(r);
} catch (e) { } catch (e) {
if (live) setError((e as { message?: string })?.message ?? 'Failed to load'); if (live) setError((e as { message?: string })?.message ?? 'Failed to load');

View File

@ -109,3 +109,9 @@ code {
padding: 4px 8px; padding: 4px 8px;
background: var(--code-bg); background: var(--code-bg);
} }
/* Remove black border on recharts when clicking */
.recharts-wrapper,
.recharts-wrapper * {
outline: none !important;
}

View File

@ -23,11 +23,11 @@ export function DailyLogsPage() {
const mapComponent = ( const mapComponent = (
<div className="!bg-[var(--tiles-card-bg)] rounded-lg shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full min-h-[320px]"> <div className="!bg-[var(--tiles-card-bg)] rounded-lg shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full min-h-[320px]">
<div className="px-5 py-4 border-b border-[var(--z-block-border)] flex items-center gap-2"> <div className="px-5 py-4 border-b border-[var(--z-block-border)] flex items-center gap-2">
<MapPin size={18} className="text-slate-400" /> <MapPin size={18} className="text-slate-400" />
<h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3> <h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3>
</div> </div>
<div className="flex-1 relative bg-slate-50"> <div className="flex-1 relative bg-slate-50">
<DailyLogMap /> <DailyLogMap />
</div> </div>
</div> </div>
); );
@ -38,7 +38,7 @@ export function DailyLogsPage() {
<DailyLogsView <DailyLogsView
mapComponent={mapComponent} mapComponent={mapComponent}
refreshKey={refreshKey} refreshKey={refreshKey}
presetAlias="my_logs" presetAlias=""
onRowClick={(row) => { onRowClick={(row) => {
const id = row.instance_id as number | string | undefined; const id = row.instance_id as number | string | undefined;
if (id != null) navigate(`/daily/${id}`); if (id != null) navigate(`/daily/${id}`);
@ -112,10 +112,10 @@ export function DailyLogsPage() {
{instanceId != null && ( {instanceId != null && (
<div className="w-full"> <div className="w-full">
<DailyLogDetail <DailyLogDetail
instanceId={instanceId} instanceId={instanceId}
refreshKey={refreshKey} refreshKey={refreshKey}
onPunchOut={() => setPunchOutInstanceId(instanceId)} onPunchOut={() => setPunchOutInstanceId(instanceId)}
onBack={() => navigate('/daily')} onBack={() => navigate('/daily')}
/> />
</div> </div>
)} )}

View File

@ -36,18 +36,18 @@ export function LoginPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center p-4 bg-app"> <div className="min-h-screen flex items-center justify-center p-4 bg-[#E8FBF0]">
<Card className="w-full max-w-[400px]"> <Card className="w-full max-w-[400px]">
<div className="flex flex-col gap-1 mb-6 items-center text-center"> <div className="flex flex-col gap-1 mb-6 items-center text-center">
<img src={logo} alt="Logo" className="h-20 w-auto object-contain mb-2" /> <img src={logo} alt="Logo" className="h-20 w-auto object-contain mb-2" />
<h1 className="m-0 text-2xl font-extrabold text-strong tracking-[-0.02em]">Krishna Sales</h1> <h1 className="m-0 text-2xl font-extrabold text-strong tracking-[-0.02em]">Krishna Sales</h1>
</div> </div>
<form onSubmit={submit} className="flex flex-col gap-4"> <form onSubmit={submit} className="flex flex-col gap-4">
<Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required autoFocus /> <Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required autoFocus className="[&>div]:rounded-full [&>div:focus-within]:!border-emerald-500 [&>div:focus-within]:!shadow-[0_0_0_3px_rgba(16,185,129,0.3)]" />
<Input label="Password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required /> <Input label="Password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required className="[&>div]:rounded-full [&>div:focus-within]:!border-emerald-500 [&>div:focus-within]:!shadow-[0_0_0_3px_rgba(16,185,129,0.3)]" />
{error && <div className="text-xs text-ruby-600 font-medium">{error}</div>} {error && <div className="text-xs text-ruby-600 font-medium">{error}</div>}
<Button type="submit" full disabled={busy}> <Button type="submit" full disabled={busy} className="rounded-full bg-emerald-600 hover:bg-emerald-700 text-white border-transparent">
{busy ? 'Signing in…' : 'Sign in'} {busy ? 'Signing in…' : 'Sign in'}
</Button> </Button>
</form> </form>

View File

@ -3,15 +3,15 @@ import { RecordView } from '../components/rv/RecordView';
import { ORDER_BOOKING } from '../api/config'; import { ORDER_BOOKING } from '../api/config';
import { orderBookingClient } from '../api/clients'; import { orderBookingClient } from '../api/clients';
export const REPORT_MAP: Record<string, { uid: string; title: string }> = { export const REPORT_MAP: Record<string, { uid: string; title: string; sortBy?: string; sortDir?: 'asc' | 'desc' }> = {
'product-wise-orders': { uid: ORDER_BOOKING.recordViews.PRODUCT_WISE_ORDERS, title: 'Product Wise Orders' }, 'product-wise-orders': { uid: ORDER_BOOKING.recordViews.PRODUCT_WISE_ORDERS, title: 'Product Wise Orders', sortBy: 'order_date', sortDir: 'desc' },
'route-wise-orders': { uid: ORDER_BOOKING.recordViews.ROUTE_WISE_ORDERS, title: 'Route Wise Orders' }, 'route-wise-orders': { uid: ORDER_BOOKING.recordViews.ROUTE_WISE_ORDERS, title: 'Route Wise Orders', sortBy: 'order_date', sortDir: 'desc' },
'so-wise-orders': { uid: ORDER_BOOKING.recordViews.SO_WISE_ORDERS, title: 'SO Wise Orders' }, 'so-wise-orders': { uid: ORDER_BOOKING.recordViews.SO_WISE_ORDERS, title: 'SO Wise Orders', sortBy: 'order_date', sortDir: 'desc' },
'so-wise-visits': { uid: ORDER_BOOKING.recordViews.SO_WISE_VISITS, title: 'SO Wise Visits' }, 'so-wise-visits': { uid: ORDER_BOOKING.recordViews.SO_WISE_VISITS, title: 'SO Wise Visits' },
'yearly-orders': { uid: ORDER_BOOKING.recordViews.YEARLY_ORDERS, title: 'Yearly Orders' }, 'yearly-orders': { uid: ORDER_BOOKING.recordViews.YEARLY_ORDERS, title: 'Yearly Orders' },
'monthly-orders': { uid: ORDER_BOOKING.recordViews.MONTHLY_ORDERS, title: 'Monthly Orders' }, 'monthly-orders': { uid: ORDER_BOOKING.recordViews.MONTHLY_ORDERS, title: 'Monthly Orders' },
'weekly-orders': { uid: ORDER_BOOKING.recordViews.WEEKLY_ORDERS, title: 'Weekly Orders' }, 'weekly-orders': { uid: ORDER_BOOKING.recordViews.WEEKLY_ORDERS, title: 'Weekly Orders' },
'store-wise-products': { uid: ORDER_BOOKING.recordViews.STORE_WISE_PRODUCTS, title: 'Store Wise Products' }, 'store-wise-products': { uid: ORDER_BOOKING.recordViews.STORE_WISE_PRODUCTS, title: 'Store Wise Products', sortBy: 'order_date', sortDir: 'desc' },
}; };
export function ReportPage() { export function ReportPage() {
@ -28,6 +28,8 @@ export function ReportPage() {
client={orderBookingClient} client={orderBookingClient}
rvUid={report.uid} rvUid={report.uid}
title={report.title} title={report.title}
sortBy={report.sortBy}
sortDir={report.sortDir}
/> />
); );
} }