Compare commits
No commits in common. "2f159af60b0ca2b347a9dd6b6c11bf502762a1b7" and "c8c251f2d19b8b91c2dd7a91b7c147e8fea143fb" have entirely different histories.
2f159af60b
...
c8c251f2d1
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/src/assets/logo.png" />
|
||||
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Krishna Sales</title>
|
||||
</head>
|
||||
|
||||
@ -249,7 +249,3 @@ export const PIPELINE = {
|
||||
salesOfficers: '/api/papi2/sales-officers'
|
||||
}
|
||||
};
|
||||
|
||||
export const HIDDEN_FORM_FIELDS = [
|
||||
'store_code_3'
|
||||
];
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -45,7 +45,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setUserEmail(null);
|
||||
setIsAdmin(false);
|
||||
setRoles([]);
|
||||
localStorage.clear();
|
||||
localStorage.removeItem('krishna_sales_user_email');
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -21,38 +21,6 @@ import {
|
||||
ArrowLeft,
|
||||
} 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 {
|
||||
instanceId: number | string;
|
||||
selectedRow?: Record<string, any>;
|
||||
@ -130,7 +98,7 @@ export function CallDetail({
|
||||
// Extract store info with fallbacks matching reference
|
||||
const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record<string, any>;
|
||||
const storeName = selectStore.business_name || selectStore.store_name || rowSrc.store_name || 'No data';
|
||||
const storeCode = selectStore.store_code_2 || selectStore.code || 'No data';
|
||||
const storeCode = selectStore.store_code || selectStore.code || 'No data';
|
||||
const ownerName = selectStore.owner_name || selectStore.contact_person || 'No data';
|
||||
const phone = selectStore.phone_number?.phone || selectStore.phone_number?.phone_with_dial_code || selectStore.phone || 'No data';
|
||||
const email = selectStore.email || 'No data';
|
||||
@ -321,28 +289,28 @@ export function CallDetail({
|
||||
|
||||
// Extract order line items
|
||||
let orderItems: any[] = [];
|
||||
const rawOrderItems = rowSrc.order_details_3 ?? data?.order_details_3;
|
||||
|
||||
const rawOrderItems = rowSrc.order_details || data?.order_details || rowSrc.order_details_3 || data?.order_details_3;
|
||||
if (Array.isArray(rawOrderItems) && rawOrderItems.length > 0) {
|
||||
orderItems = rawOrderItems.map((item: any) => {
|
||||
const bags = Number(item.bags_3 ?? 0);
|
||||
const skuVal = Number(item.sku_3 ?? 30);
|
||||
|
||||
const bags = Number(item.bags || item.bags_3 || item.quantity || 0);
|
||||
const skuVal = Number(item.sku || item.sku_3 || '30');
|
||||
return {
|
||||
name: item.product_name_3 ?? 'Unknown Product',
|
||||
category: item.product_category_3 ?? 'Unknown Category',
|
||||
sku: item.sku_3 ?? '30',
|
||||
skuCode: item.sku_code_3 ?? 'Unknown SKU Code',
|
||||
brCode: item.br_code_3 ?? 'Unknown BR Code',
|
||||
name: item.product_name || item.product_name_3 || item.product_category || 'MAIDA PUFF 30 kgs',
|
||||
category: item.product_category || item.product_category_3 || item.category || 'MAIDA PUFF',
|
||||
sku: item.sku || item.sku_3 || '30',
|
||||
skuCode: item.sku_code || item.sku_code_3 || 'MP30',
|
||||
brCode: item.br_code || item.br_code_3 || item.br || 'PM',
|
||||
bags,
|
||||
totalKgs: bags * skuVal,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
orderItems = [];
|
||||
}
|
||||
|
||||
// Calculate totals
|
||||
const totalBags = Number(rowSrc.total_bags_3 ?? data?.total_bags_3 ?? 0);
|
||||
const totalKgs = Number(rowSrc.total_kgs_3 ?? data?.total_kgs_3 ?? 0);
|
||||
const totalBags = Number(rowSrc.total_bags_3 || rowSrc.total_bags || data?.total_bags_3 || data?.total_bags || orderItems.reduce((acc, i) => acc + i.bags, 0));
|
||||
const totalKgs = Number(rowSrc.total_kgs_3 || rowSrc.total_kgs || data?.total_kgs_3 || data?.total_kgs || orderItems.reduce((acc, i) => acc + (i.bags * (Number(i.sku) || 1)), 0));
|
||||
const lineItemsCount = orderItems.length;
|
||||
|
||||
const orderId = String(rowSrc.order_id || data?.order_id || rowSrc.order_number || `No data`);
|
||||
@ -379,7 +347,6 @@ export function CallDetail({
|
||||
const channel = String(rowSrc.order_received_channel
|
||||
|| rowSrc.channel || data?.order_received_channel || 'No data');
|
||||
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');
|
||||
|
||||
return (
|
||||
@ -439,19 +406,19 @@ export function CallDetail({
|
||||
|
||||
{/* 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="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[100px] flex-1">
|
||||
<div className="bg-slate-50 border border-slate-200/60 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-xl font-bold text-slate-900">{totalBags.toLocaleString()}</div>
|
||||
</div>
|
||||
<div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[100px] flex-1">
|
||||
<div className="bg-slate-50 border border-slate-200/60 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-xl font-bold text-slate-900">{totalKgs.toLocaleString()}</div>
|
||||
</div>
|
||||
<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">ORDER ITEMS</div>
|
||||
<div className="bg-slate-50 border border-slate-200/60 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-xl font-bold text-slate-900">{lineItemsCount}</div>
|
||||
</div>
|
||||
<div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3 text-center min-w-[110px] flex-1">
|
||||
<div className="bg-slate-50 border border-slate-200/60 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-sm font-bold text-slate-900 truncate mt-1">{salesOfficer}</div>
|
||||
</div>
|
||||
@ -761,47 +728,37 @@ export function CallDetail({
|
||||
</div>
|
||||
|
||||
{/* Right Store Proof Photo */}
|
||||
<div className="space-y-4">
|
||||
{visitNotes && visitNotes.trim() !== '' && visitNotes.toLowerCase() !== 'undefined' && visitNotes.toLowerCase() !== 'null' && (
|
||||
<div className="space-y-2">
|
||||
<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
|
||||
</div>
|
||||
<div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3.5 text-xs text-slate-700 leading-relaxed font-medium">
|
||||
{visitNotes}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-[11px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1">
|
||||
<FileText size={12} /> UPLOADED PROOF
|
||||
</div>
|
||||
|
||||
{uploadedImages.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{uploadedImages.map((file: any, idx: number) => {
|
||||
const previewUrl = `${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${file.uuid}/preview`;
|
||||
return (
|
||||
<div key={file.uuid || idx} className="relative w-full rounded-xl overflow-hidden border border-slate-200 bg-slate-100 flex items-center justify-center group shadow-sm">
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt={file.original_name || 'Uploaded Proof'}
|
||||
className="w-full h-auto max-h-[300px] object-cover transition-transform group-hover:scale-[1.01]"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = 'none';
|
||||
(e.target as HTMLImageElement).parentElement!.innerHTML = `<div class="p-6 text-center text-xs text-slate-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 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>
|
||||
@ -815,20 +772,21 @@ export function CallDetail({
|
||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 overflow-hidden shadow-sm">
|
||||
{storeImages.length > 0 ? (
|
||||
<div className="relative w-full h-44 bg-slate-100 flex items-center justify-center overflow-hidden">
|
||||
<SafeImage
|
||||
<img
|
||||
src={`${orderBookingClient.baseUrl}/app/${APP_ID}/view/files/${storeImages[0].uuid}/preview`}
|
||||
alt="Store Front"
|
||||
type="store"
|
||||
text={storeName}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<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-emerald-500">
|
||||
<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="w-12 h-12 rounded-full bg-[var(--tiles-card-bg)] shadow-sm flex items-center justify-center text-slate-500">
|
||||
<ImageIcon size={22} />
|
||||
</div>
|
||||
<span className="text-xs font-bold text-emerald-700 uppercase tracking-wider">{storeName}</span>
|
||||
<span className="text-xs font-bold text-slate-600 uppercase tracking-wider">{storeName}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -887,9 +845,9 @@ export function CallDetail({
|
||||
</div>
|
||||
|
||||
{/* Notes Container */}
|
||||
<div className="bg-[#E8FBF0] border border-emerald-200 rounded-xl p-3.5 space-y-1.5 text-xs">
|
||||
<div className="font-bold text-emerald-600 flex items-center gap-1.5 text-[11px] uppercase tracking-wider">
|
||||
<FileText size={12} className="text-emerald-500" /> NOTES
|
||||
<div className="bg-slate-50/80 border border-slate-200/80 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">
|
||||
<FileText size={12} /> NOTES
|
||||
</div>
|
||||
<p className="text-slate-700 leading-relaxed font-medium">
|
||||
{storeNotes}
|
||||
|
||||
@ -284,12 +284,12 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
|
||||
<Map size={14} className="text-slate-500" /> Route Assignment
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="border border-emerald-200 rounded-xl p-4 bg-[#E8FBF0] flex flex-col justify-between">
|
||||
<div className="text-[10px] font-bold text-emerald-600 uppercase tracking-wider mb-3">Route Code</div>
|
||||
<div className="border border-slate-100 rounded-xl p-4 bg-slate-50 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-lg font-bold text-slate-900">{String(routeCode)}</div>
|
||||
</div>
|
||||
<div className="border border-emerald-200 rounded-xl p-4 bg-[#E8FBF0] flex flex-col justify-between">
|
||||
<div className="text-[10px] font-bold text-emerald-600 uppercase tracking-wider mb-3">Sub Route</div>
|
||||
<div className="border border-slate-100 rounded-xl p-4 bg-slate-50 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-lg font-bold text-slate-900">{String(subRoute)}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -312,9 +312,9 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
|
||||
</div>
|
||||
);
|
||||
}) : (
|
||||
<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 text-emerald-500" />
|
||||
<span className="text-xs font-semibold">No image uploaded</span>
|
||||
<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">
|
||||
<Camera size={24} className="mb-2 opacity-50" />
|
||||
<span className="text-xs">No image uploaded</span>
|
||||
</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 items-center gap-3">
|
||||
<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-emerald-500" />
|
||||
<div className="w-8 h-8 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center shrink-0">
|
||||
<User size={12} className="text-slate-500" />
|
||||
</div>
|
||||
<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 className="flex items-center gap-3">
|
||||
<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-emerald-500" />
|
||||
<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>
|
||||
@ -347,8 +347,8 @@ export function DailyLogDetail({ instanceId, refreshKey, onPunchOut, onBack }: D
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<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-emerald-500" />
|
||||
<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>
|
||||
|
||||
@ -4,7 +4,7 @@ import { ORDER_BOOKING } from '../../api/config';
|
||||
import { Card } from '../reusable/Card';
|
||||
import { Spinner } from '../reusable/Spinner';
|
||||
import { EmptyState } from '../reusable/EmptyState';
|
||||
import { Store, User, Package, Calendar, Hash, Weight, MapPin, ShoppingBag } from 'lucide-react';
|
||||
import { Store, User, Package, Calendar, Hash, Mail, Weight, List, MapPin } from 'lucide-react';
|
||||
|
||||
export interface WiredDetailViewProps {
|
||||
instanceId: number | string;
|
||||
@ -116,169 +116,163 @@ export function OrderDetail({ instanceId }: WiredDetailViewProps) {
|
||||
const totalKgs = extract('total_kgs_3', remainingData) || 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 bg-transparent">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col gap-4 bg-[var(--tiles-card-bg)] p-1">
|
||||
{/* Top Section */}
|
||||
<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>
|
||||
<h1 className="text-xl font-semibold tracking-tight text-slate-900">Order Detail</h1>
|
||||
<p className="text-sm text-slate-500">#{orderId}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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 className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">Order ID</div>
|
||||
<div className="text-2xl font-black text-slate-900 mb-2">{String(orderId)}</div>
|
||||
<div className="flex items-center gap-3 text-xs text-slate-500 font-medium">
|
||||
<span className="flex items-center gap-1.5"><Calendar size={14} /> {String(dateOfOrder)}</span>
|
||||
<span className="flex items-center gap-1.5"><Hash size={14} /> Instance #{instanceId}</span>
|
||||
</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>
|
||||
|
||||
{/* 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>
|
||||
{/* Middle Stats Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<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">
|
||||
<Package size={14} className="text-emerald-500" /> Total Bags
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-slate-900">{String(totalBags)}</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">
|
||||
<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 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 || '';
|
||||
<div className="overflow-x-auto flex-1">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-slate-50/50 text-[10px] font-bold text-slate-400 uppercase tracking-wider">
|
||||
<tr>
|
||||
<th className="px-5 py-3 border-b border-slate-100">Product</th>
|
||||
<th className="px-5 py-3 border-b border-slate-100">Category</th>
|
||||
<th className="px-5 py-3 border-b border-slate-100">SKU</th>
|
||||
<th className="px-5 py-3 border-b border-slate-100 text-right">Bags</th>
|
||||
</tr>
|
||||
</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 (
|
||||
<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>
|
||||
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>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div className="flex-1 flex flex-col gap-4 min-w-[280px]">
|
||||
{/* SO Card */}
|
||||
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5">
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4">
|
||||
<User size={14} className="text-slate-500" /> Sales Officer
|
||||
</div>
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
{itemsArray.length === 0 && (
|
||||
<div className="py-6 text-center text-sm text-slate-500 italic">No items found in this order.</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>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
{/* Totals */}
|
||||
<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-blue-100 text-blue-600">
|
||||
<Weight className="h-4 w-4" />
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-blue-800">Totals</h2>
|
||||
{/* Order Meta */}
|
||||
<div className="border border-slate-200 rounded-xl bg-[var(--tiles-card-bg)] shadow-sm overflow-hidden p-5">
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4">
|
||||
<Hash size={14} className="text-slate-500" /> Order Meta
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<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 className="space-y-3">
|
||||
<div className="flex items-center justify-between rounded-xl bg-slate-50 border border-slate-100 p-3.5">
|
||||
<span className="text-sm font-medium text-slate-600">Total Bags</span>
|
||||
<span className="font-mono text-lg font-bold text-slate-800">{String(totalBags)}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-xl bg-slate-50 border border-slate-100 p-3.5">
|
||||
<span className="text-sm font-medium text-slate-600">Total Weight</span>
|
||||
<span className="font-mono text-lg font-bold text-slate-800">{Number(totalKgs).toLocaleString()} kg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sales officer */}
|
||||
<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-purple-100 text-purple-600">
|
||||
<User className="h-4 w-4" />
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-purple-800">Sales Officer</h2>
|
||||
</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>
|
||||
|
||||
{/* Location hint */}
|
||||
<div className="rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-5 shadow-sm">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<div className="grid h-8 w-8 place-items-center rounded-full bg-amber-100 text-amber-600">
|
||||
<MapPin className="h-4 w-4" />
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-amber-800">Location</h2>
|
||||
</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>
|
||||
);
|
||||
|
||||
@ -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';
|
||||
|
||||
// Store Overview
|
||||
const storeCode = extract('store_code_2', remainingData) || '-';
|
||||
const storeCode = extract('store_code', remainingData) || '-';
|
||||
const businessName = extract('business_name', remainingData) || '-';
|
||||
const area = extract('area', 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>
|
||||
</div>
|
||||
<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-emerald-100 bg-emerald-50">
|
||||
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50">
|
||||
<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-xs text-slate-500 mt-1">Code {routeCode ? String(routeCode) : '-'}</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
|
||||
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50">
|
||||
<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>
|
||||
<div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
|
||||
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50">
|
||||
<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-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>
|
||||
</div>
|
||||
<div className="p-5 bg-[var(--tiles-card-bg)]">
|
||||
<div className="p-4 rounded-xl border border-emerald-100 bg-emerald-50">
|
||||
<div className="p-4 rounded-xl border border-slate-100 bg-slate-50">
|
||||
<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>
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
WfLookupField,
|
||||
RadioField,
|
||||
} from './fields';
|
||||
import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE, HIDDEN_FORM_FIELDS } from '../../api/config';
|
||||
import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE } from '../../api/config';
|
||||
import { isCategoryColumn, isProductColumn } from './fields/gridUtils';
|
||||
|
||||
export interface DynamicFormProps {
|
||||
@ -75,8 +75,14 @@ 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>) => {
|
||||
res.fields.forEach(f => {
|
||||
if (imageFieldIds.has(f.id)) return;
|
||||
|
||||
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
||||
|
||||
if (sourceData[f.id] !== undefined) {
|
||||
@ -126,13 +132,13 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
|
||||
if (customPrefillData) {
|
||||
Object.entries(customPrefillData).forEach(([k, v]) => {
|
||||
defaultValues[k] = v;
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
if (chainedPrefillData) {
|
||||
Object.entries(chainedPrefillData).forEach(([k, v]) => {
|
||||
defaultValues[k] = v;
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
@ -571,13 +577,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
|
||||
for (const f of fields) {
|
||||
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 (f.data_type === 'phone' && typeof val === 'string') {
|
||||
@ -587,22 +586,16 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
phone: phoneNum,
|
||||
phone_with_dial_code: `+91${phoneNum}`
|
||||
};
|
||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val)) {
|
||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val) && val.length > 0 && val[0] instanceof File) {
|
||||
const uploadedFiles = [];
|
||||
for (const file of val) {
|
||||
if (file instanceof File) {
|
||||
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||
uploadedFiles.push(fileMeta);
|
||||
} else {
|
||||
uploadedFiles.push(file);
|
||||
}
|
||||
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||
uploadedFiles.push(fileMeta);
|
||||
}
|
||||
payload[f.id] = uploadedFiles;
|
||||
} 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 });
|
||||
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') {
|
||||
const gridRows = Array.isArray(val) ? val : [];
|
||||
const getFormattedGridVal = (colKey: string, rawVal: unknown, colDef?: FormScreenField) => {
|
||||
@ -860,9 +853,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
f.id === 'total_kgs';
|
||||
if (isTotalField) return false;
|
||||
}
|
||||
if (HIDDEN_FORM_FIELDS.includes(f.id) || HIDDEN_FORM_FIELDS.includes(f.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
@ -176,7 +176,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
|
||||
try {
|
||||
const formDataToSend = cleanFormData(formDataOverride || valuesRef.current);
|
||||
|
||||
console.log('[LogVisitForm] Executing select_store lookup with prefilled formData:', formDataToSend);
|
||||
|
||||
const lookupRes = await client.wfLookupRecords({
|
||||
activityId,
|
||||
@ -239,7 +239,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
time_of_visit: timeVal,
|
||||
});
|
||||
|
||||
|
||||
console.log('[LogVisitForm] Fetching initial daily_log lookup with payload:', payload);
|
||||
|
||||
const dailyLogLookupRes = await client.wfLookupRecords({
|
||||
activityId,
|
||||
@ -254,7 +254,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
|
||||
if (arr.length > 0) {
|
||||
const firstLog = arr[0];
|
||||
|
||||
console.log('[LogVisitForm] Successfully fetched daily log record:', firstLog);
|
||||
|
||||
const dailyLogInstanceId = String(firstLog.instance_id || firstLog.id || '');
|
||||
const routeCode = String(
|
||||
@ -291,7 +291,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
|
||||
valuesRef.current = updated;
|
||||
setValues(updated);
|
||||
|
||||
console.log('[LogVisitForm] Daily log prefill complete:', updated);
|
||||
} else {
|
||||
console.warn('[LogVisitForm] No daily log records found for date/time:', dateVal, timeVal);
|
||||
}
|
||||
@ -343,11 +343,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
const payload: Record<string, any> = {};
|
||||
validFieldIds.forEach(fieldId => {
|
||||
const val = valuesRef.current[fieldId];
|
||||
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 !== '') {
|
||||
if (val !== undefined && val !== null && val !== '') {
|
||||
payload[fieldId] = val;
|
||||
}
|
||||
});
|
||||
@ -370,14 +366,14 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
||||
}
|
||||
payload['upload_image'] = uploadedFiles;
|
||||
|
||||
|
||||
console.log('[LogVisitForm] Submitting clean startInstance payload:', payload);
|
||||
|
||||
const res: any = await client.startInstance(activityId, payload);
|
||||
|
||||
const chainSource = res?.activity_chain || schema?.activity_chain || [];
|
||||
if (chainSource && chainSource.length > 0) {
|
||||
const nextAct = chainSource[0];
|
||||
|
||||
console.log('[LogVisitForm] Activity chain detected, transitioning to DynamicForm:', nextAct);
|
||||
onActivityChange?.(nextAct.activity_name);
|
||||
|
||||
setChainedActivity({
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { BASE_URL, APP_ID } from '../../../api/config';
|
||||
|
||||
export function ImagePreview({ file }: { file: File | any }) {
|
||||
export function ImagePreview({ file }: { file: File }) {
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (file instanceof File) {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
setUrl(objectUrl);
|
||||
return () => URL.revokeObjectURL(objectUrl);
|
||||
} else if (file && file.uuid) {
|
||||
setUrl(`${BASE_URL}/app/${APP_ID}/view/files/${file.uuid}/preview`);
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
setUrl(objectUrl);
|
||||
return () => URL.revokeObjectURL(objectUrl);
|
||||
}, [file]);
|
||||
|
||||
if (!url) return null;
|
||||
@ -28,10 +23,10 @@ export function FileInput({
|
||||
label: string;
|
||||
type: 'image' | 'file' | string;
|
||||
required?: boolean;
|
||||
value: File[] | any[] | undefined | null | unknown;
|
||||
onChange: (files: (File | any)[]) => void;
|
||||
value: File[] | undefined | null | unknown;
|
||||
onChange: (files: File[]) => void;
|
||||
}) {
|
||||
const files = Array.isArray(value) ? value : (value ? [value] : []);
|
||||
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
@ -73,7 +68,7 @@ export function FileInput({
|
||||
<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">
|
||||
{file.name || file.original_name || 'File'}
|
||||
{file.name}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Modal } from '../../reusable/Modal';
|
||||
import { Button } from '../../buttons/Button';
|
||||
import { useJsApiLoader, GoogleMap } from '@react-google-maps/api';
|
||||
import { CustomAdvancedMarker } from '../../maps/CustomAdvancedMarker';
|
||||
import { useJsApiLoader, GoogleMap, Marker } from '@react-google-maps/api';
|
||||
import { MapPin, Crosshair, Map as MapIcon, Loader2, X } from 'lucide-react';
|
||||
|
||||
const libraries: ("marker")[] = ["marker"];
|
||||
|
||||
const mapContainerStyle = {
|
||||
width: '100%',
|
||||
height: '400px',
|
||||
@ -30,12 +27,9 @@ export function GeolocationInput({
|
||||
const [isMapModalOpen, setIsMapModalOpen] = useState(false);
|
||||
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({
|
||||
id: 'google-map-script',
|
||||
googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''),
|
||||
libraries
|
||||
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''
|
||||
});
|
||||
|
||||
// Extract lat, lng & accuracy from value (supporting both latitude/longitude and lat/lng)
|
||||
@ -185,12 +179,11 @@ export function GeolocationInput({
|
||||
options={{
|
||||
streetViewControl: false,
|
||||
mapTypeControl: false,
|
||||
fullscreenControl: false,
|
||||
mapId: "DEMO_MAP_ID"
|
||||
fullscreenControl: false
|
||||
}}
|
||||
>
|
||||
{mapMarkerPos && (
|
||||
<CustomAdvancedMarker position={mapMarkerPos} />
|
||||
<Marker position={mapMarkerPos} />
|
||||
)}
|
||||
</GoogleMap>
|
||||
)}
|
||||
|
||||
@ -345,10 +345,10 @@ export function OrderGrid({
|
||||
const computedRowKgs = skuNum * bagsNum;
|
||||
|
||||
const isMissingProd = isCategorySelected && !currentProdVal.trim();
|
||||
const prodError = showErrors && isMissingProd ? true : undefined;
|
||||
const prodError = showErrors && isMissingProd ? 'Required' : undefined;
|
||||
|
||||
const isMissingBags = isCategorySelected && (!currentBagsVal || Number(currentBagsVal) <= 0);
|
||||
const bagsError = showErrors && isMissingBags ? true : undefined;
|
||||
const bagsError = showErrors && isMissingBags ? 'Required' : undefined;
|
||||
|
||||
return (
|
||||
<div key={rowIdx}>
|
||||
@ -391,7 +391,7 @@ export function OrderGrid({
|
||||
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
||||
disabled={!isCategorySelected && categoriesList.length > 0}
|
||||
options={[
|
||||
{ value: '', label: isCategorySelected || categoriesList.length === 0 ? (prodError ? 'Select product *' : 'Select product') : 'Select category first' },
|
||||
{ value: '', label: isCategorySelected || categoriesList.length === 0 ? 'Select product' : 'Select category first' },
|
||||
...prodOptions
|
||||
]}
|
||||
error={prodError}
|
||||
@ -410,7 +410,7 @@ export function OrderGrid({
|
||||
min={isMainBagsCol ? "1" : undefined}
|
||||
value={numVal !== undefined && numVal !== null ? String(numVal) : ''}
|
||||
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
||||
placeholder={isMainBagsCol ? (bagsError ? 'Bags *' : (isCategorySelected ? 'Bags' : '0')) : '0'}
|
||||
placeholder={isMainBagsCol && isCategorySelected ? 'Bags' : '0'}
|
||||
error={isMainBagsCol ? bagsError : undefined}
|
||||
className="w-full"
|
||||
/>
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,11 +1,8 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useJsApiLoader, GoogleMap, InfoWindow } from '@react-google-maps/api';
|
||||
import { useJsApiLoader, GoogleMap, Marker, InfoWindow } from '@react-google-maps/api';
|
||||
import { Loader2, MapPin } from 'lucide-react';
|
||||
import { PIPELINE } from '../../api/config';
|
||||
import { dailyReportsClient } from '../../api/clients';
|
||||
import { CustomAdvancedMarker } from './CustomAdvancedMarker';
|
||||
|
||||
const libraries: ("marker")[] = ["marker"];
|
||||
|
||||
interface StoreLocation {
|
||||
latitude: number;
|
||||
@ -53,12 +50,9 @@ export function DailyLogMap({
|
||||
const [error, setError] = useState<string | 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({
|
||||
id: 'google-map-script',
|
||||
googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''),
|
||||
libraries
|
||||
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''
|
||||
});
|
||||
|
||||
const fetchNearestStores = async (lat: number, lng: number) => {
|
||||
@ -177,29 +171,30 @@ export function DailyLogMap({
|
||||
options={{
|
||||
streetViewControl: false,
|
||||
mapTypeControl: false,
|
||||
fullscreenControl: false,
|
||||
mapId: "DEMO_MAP_ID"
|
||||
fullscreenControl: false
|
||||
}}
|
||||
>
|
||||
{/* User's Current Location Marker */}
|
||||
{userLocation && (
|
||||
<CustomAdvancedMarker
|
||||
<Marker
|
||||
position={userLocation}
|
||||
iconUrl="https://maps.google.com/mapfiles/ms/icons/blue-dot.png"
|
||||
icon={{
|
||||
url: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
|
||||
}}
|
||||
title="Location"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Stores Markers */}
|
||||
{stores.map((store, index) => {
|
||||
{stores.map((store) => {
|
||||
if (!store.location) return null;
|
||||
const lat = Number(store.location.latitude);
|
||||
const lng = Number(store.location.longitude);
|
||||
if (isNaN(lat) || isNaN(lng)) return null;
|
||||
|
||||
return (
|
||||
<CustomAdvancedMarker
|
||||
key={store.store_code || index}
|
||||
<Marker
|
||||
key={store.store_code}
|
||||
position={{ lat, lng }}
|
||||
onClick={() => setSelectedStore(store)}
|
||||
title={store.business_name}
|
||||
|
||||
@ -203,9 +203,9 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
{chartType === 3 || chartType === 4 ? (
|
||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
|
||||
<Tooltip
|
||||
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' }}
|
||||
@ -219,25 +219,22 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
||||
cy="50%"
|
||||
outerRadius={100}
|
||||
innerRadius={chartType === 4 ? 65 : 0}
|
||||
style={{ outline: 'none' }}
|
||||
activeShape={false}
|
||||
className="focus:outline-none outline-none"
|
||||
>
|
||||
{finalData.map((_, index) => (
|
||||
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} style={{ outline: 'none' }} className="focus:outline-none outline-none" />
|
||||
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
) : chartType === 1 ? (
|
||||
<LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
||||
<LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||
{renderChartContent()}
|
||||
</LineChart>
|
||||
) : chartType === 2 ? (
|
||||
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
||||
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||
{renderChartContent()}
|
||||
</AreaChart>
|
||||
) : (
|
||||
<BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
||||
<BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||
{renderChartContent()}
|
||||
</BarChart>
|
||||
)}
|
||||
|
||||
@ -4,7 +4,7 @@ import { cn } from '../../lib/cn';
|
||||
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
|
||||
label?: string;
|
||||
hint?: string;
|
||||
error?: string | boolean;
|
||||
error?: string;
|
||||
/** Leading adornment, e.g. "₹". */
|
||||
prefix?: ReactNode;
|
||||
/** Trailing adornment, e.g. "/ year". */
|
||||
@ -52,10 +52,8 @@ export function Input({
|
||||
/>
|
||||
{suffix && <span className="text-faint text-sm">{suffix}</span>}
|
||||
</div>
|
||||
{(hint || (typeof error === 'string' && error)) && (
|
||||
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>
|
||||
{typeof error === 'string' ? error : hint}
|
||||
</span>
|
||||
{(hint || error) && (
|
||||
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>{error || hint}</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
|
||||
@ -12,7 +12,7 @@ export interface SelectOption {
|
||||
export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
label?: string;
|
||||
hint?: string;
|
||||
error?: string | boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
/** Either strings or {value,label} objects. */
|
||||
options?: Array<string | SelectOption>;
|
||||
@ -247,10 +247,8 @@ export function Select({
|
||||
))}
|
||||
</select>
|
||||
|
||||
{(hint || (typeof error === 'string' && error)) && (
|
||||
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>
|
||||
{typeof error === 'string' ? error : hint}
|
||||
</span>
|
||||
{(hint || error) && (
|
||||
<span className={cn('text-xs', error ? 'text-ruby-600' : 'text-faint')}>{error || hint}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -135,7 +135,7 @@ export function RecordView({
|
||||
sortDir,
|
||||
presetAlias
|
||||
});
|
||||
|
||||
console.log("RECORD VIEW RESP:", r);
|
||||
if (live) setResp(r);
|
||||
} catch (e) {
|
||||
if (live) setError((e as { message?: string })?.message ?? 'Failed to load');
|
||||
|
||||
@ -109,9 +109,3 @@ code {
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
||||
/* Remove black border on recharts when clicking */
|
||||
.recharts-wrapper,
|
||||
.recharts-wrapper * {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@ -23,11 +23,11 @@ export function DailyLogsPage() {
|
||||
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="px-5 py-4 border-b border-[var(--z-block-border)] flex items-center gap-2">
|
||||
<MapPin size={18} className="text-slate-400" />
|
||||
<h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3>
|
||||
<MapPin size={18} className="text-slate-400" />
|
||||
<h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3>
|
||||
</div>
|
||||
<div className="flex-1 relative bg-slate-50">
|
||||
<DailyLogMap />
|
||||
<DailyLogMap />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -38,7 +38,7 @@ export function DailyLogsPage() {
|
||||
<DailyLogsView
|
||||
mapComponent={mapComponent}
|
||||
refreshKey={refreshKey}
|
||||
presetAlias=""
|
||||
presetAlias="my_logs"
|
||||
onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/daily/${id}`);
|
||||
@ -112,10 +112,10 @@ export function DailyLogsPage() {
|
||||
{instanceId != null && (
|
||||
<div className="w-full">
|
||||
<DailyLogDetail
|
||||
instanceId={instanceId}
|
||||
refreshKey={refreshKey}
|
||||
onPunchOut={() => setPunchOutInstanceId(instanceId)}
|
||||
onBack={() => navigate('/daily')}
|
||||
instanceId={instanceId}
|
||||
refreshKey={refreshKey}
|
||||
onPunchOut={() => setPunchOutInstanceId(instanceId)}
|
||||
onBack={() => navigate('/daily')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -36,18 +36,18 @@ export function LoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-[#E8FBF0]">
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-app">
|
||||
<Card className="w-full max-w-[400px]">
|
||||
<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" />
|
||||
<h1 className="m-0 text-2xl font-extrabold text-strong tracking-[-0.02em]">Krishna Sales</h1>
|
||||
</div>
|
||||
<form onSubmit={submit} className="flex flex-col gap-4">
|
||||
<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 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="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required autoFocus />
|
||||
<Input label="Password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
|
||||
|
||||
{error && <div className="text-xs text-ruby-600 font-medium">{error}</div>}
|
||||
<Button type="submit" full disabled={busy} className="rounded-full bg-emerald-600 hover:bg-emerald-700 text-white border-transparent">
|
||||
<Button type="submit" full disabled={busy}>
|
||||
{busy ? 'Signing in…' : 'Sign in'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@ -3,15 +3,15 @@ import { RecordView } from '../components/rv/RecordView';
|
||||
import { ORDER_BOOKING } from '../api/config';
|
||||
import { orderBookingClient } from '../api/clients';
|
||||
|
||||
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', sortBy: 'order_date', sortDir: 'desc' },
|
||||
'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', sortBy: 'order_date', sortDir: 'desc' },
|
||||
export const REPORT_MAP: Record<string, { uid: string; title: string }> = {
|
||||
'product-wise-orders': { uid: ORDER_BOOKING.recordViews.PRODUCT_WISE_ORDERS, title: 'Product Wise Orders' },
|
||||
'route-wise-orders': { uid: ORDER_BOOKING.recordViews.ROUTE_WISE_ORDERS, title: 'Route Wise Orders' },
|
||||
'so-wise-orders': { uid: ORDER_BOOKING.recordViews.SO_WISE_ORDERS, title: 'SO Wise Orders' },
|
||||
'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' },
|
||||
'monthly-orders': { uid: ORDER_BOOKING.recordViews.MONTHLY_ORDERS, title: 'Monthly 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', sortBy: 'order_date', sortDir: 'desc' },
|
||||
'store-wise-products': { uid: ORDER_BOOKING.recordViews.STORE_WISE_PRODUCTS, title: 'Store Wise Products' },
|
||||
};
|
||||
|
||||
export function ReportPage() {
|
||||
@ -28,8 +28,6 @@ export function ReportPage() {
|
||||
client={orderBookingClient}
|
||||
rvUid={report.uid}
|
||||
title={report.title}
|
||||
sortBy={report.sortBy}
|
||||
sortDir={report.sortDir}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user