updates on the changes

This commit is contained in:
2026-08-27 16:49:09 +05:30
parent 8855b62b39
commit c9a80cbd20
5 changed files with 271 additions and 119 deletions

View File

@@ -163,6 +163,9 @@ export default function CreateOrder() {
const applyLocationToPickup = (loc) => {
if (!loc) return;
const lat = loc.latitude ?? loc.lat ?? loc.pickuplatitude ?? loc.locationlat;
const lng = loc.longitude ?? loc.lng ?? loc.long ?? loc.pickuplongitude ?? loc.locationlong;
setPickCust((prev) => ({
...prev,
firstname: loc.contactname || loc.contactperson || prev.firstname || loc.locationname || 'Pickup Point',
@@ -174,24 +177,9 @@ export default function CreateOrder() {
suburb: loc.locationname || loc.city || '',
city: loc.city || '',
postcode: loc.pincode || loc.postcode || '',
latitude: loc.latitude ? Number(loc.latitude) : '',
longitude: loc.longitude ? Number(loc.longitude) : ''
latitude: lat != null && lat !== '' ? Number(lat) : prev.latitude,
longitude: lng != null && lng !== '' ? Number(lng) : prev.longitude
}));
if (!loc.latitude || !loc.longitude) {
const q = [loc.address, loc.city, loc.pincode].filter(Boolean).join(', ');
if (q) {
geocodeAddress(q).then((place) => {
if (place?.geometry?.location) {
setPickCust((prev) => ({
...prev,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
}));
}
});
}
}
};
const handleSelectLocation = (locId) => {
@@ -207,8 +195,14 @@ export default function CreateOrder() {
// Tenant Pricing
const clientRate = useMemo(() => {
if (!selectedTenantId) return null;
return (pricingList || []).find((p) => String(p.tenantid) === String(selectedTenantId));
const list = Array.isArray(pricingList) ? pricingList : [];
if (selectedTenantId) {
const match = list.find((p) => String(p.tenantid) === String(selectedTenantId));
if (match) return match;
}
// Fallback to default pricing rule or standard system rates
const defaultRule = list.find((p) => p.isdefault || !p.tenantid || p.tenantid === 0);
return defaultRule || list[0] || { baseprice: 40, priceperkm: 10, basedistance: 2 };
}, [pricingList, selectedTenantId]);
// Route calculation
@@ -227,7 +221,7 @@ export default function CreateOrder() {
calculateDrivingDistance({ latitude: pLat, longitude: pLng }, { latitude: dLat, longitude: dLng })
.then((distance) => {
if (!active) return;
setRoute({ distance, minutes: getLastRouteDurationMin(), resolved: true });
setRoute({ distance: Math.max(1, distance || 1), minutes: getLastRouteDurationMin() || 15, resolved: true });
})
.catch(() => {
if (active) setRoute({ distance: 0, minutes: null, resolved: false });
@@ -240,13 +234,16 @@ export default function CreateOrder() {
// Total Charge calculation
const totalCharge = useMemo(() => {
if (!route.resolved || !clientRate) return 0;
return calculateTotalCharge(
route.distance,
Number(clientRate.baseprice) || 0,
Number(clientRate.priceperkm) || 0,
Number(clientRate.basedistance) || 0
);
const base = Number(clientRate?.baseprice) || 40;
const perKm = Number(clientRate?.priceperkm) || 10;
const minKm = Number(clientRate?.basedistance) || 2;
if (!route.resolved || !route.distance) {
return base;
}
const calculated = calculateTotalCharge(route.distance, base, perKm, minKm);
return Math.max(base, calculated);
}, [route, clientRate]);
// Place selection helpers

View File

@@ -138,13 +138,12 @@ export default function MultipleOrders() {
// Pricing synchronization
useEffect(() => {
if (!selectedTenantId || !pricingList?.length) return;
const match = pricingList.find((p) => String(p.tenantid) === String(selectedTenantId));
if (match) {
setBasePrice(Number(match.baseprice) || 0);
setPricePerKm(Number(match.priceperkm) || 0);
setMinKm(Number(match.basedistance) || 0);
}
const list = Array.isArray(pricingList) ? pricingList : [];
const match = list.find((p) => String(p.tenantid) === String(selectedTenantId));
const rate = match || list.find((p) => p.isdefault || !p.tenantid || p.tenantid === 0) || list[0] || { baseprice: 40, priceperkm: 10, basedistance: 2 };
setBasePrice(Number(rate.baseprice) || 40);
setPricePerKm(Number(rate.priceperkm) || 10);
setMinKm(Number(rate.basedistance) || 2);
}, [selectedTenantId, pricingList]);
// Auto-select single location

View File

@@ -2,11 +2,12 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { motion } from 'framer-motion';
import dayjs from 'dayjs';
import { Ban, FileSpreadsheet, MapPin, Package, Plus, Truck, Upload } from 'lucide-react';
import { Button, ConfirmModal, DataTable, EmptyState, PageHeader, SearchInput, Stack, StatusBadge, Tabs, ZoneSelector } from '@/components/ds';
import { useBulkCancelBookings, useCustomers } from '@/lib/doormileHooks';
import { Ban, Bike, FileSpreadsheet, MapPin, Package, Plus, Sparkles, Truck, Upload, UserCheck } from 'lucide-react';
import { Button, ConfirmModal, DataTable, EmptyState, Modal, PageHeader, SearchInput, Stack, StatusBadge, Tabs, ZoneSelector } from '@/components/ds';
import { useBulkCancelBookings, useCustomers, useRiderOptions } from '@/lib/doormileHooks';
import { useZone } from '@/lib/ZoneContext';
import { createAutomationDeliveries } from '@/api/doormile';
import { assignMilerToBooking, notifyMiler } from '@/api/doormile/endpoints';
import { drainBookings } from '@/lib/assistant/scan';
import { OpenToast, messageOf } from '@/api/doormile/notify';
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
@@ -56,7 +57,18 @@ const haversineKm = (lat1, lon1, lat2, lon2) => {
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
const chargeOf = (booking) => booking?.serviceoptions?.[0]?.estimatedprice;
const chargeOf = (booking) => {
const serviceOpt = booking?.serviceoptions?.[0];
const val =
serviceOpt?.estimatedprice ??
serviceOpt?.price ??
booking?.finalprice ??
booking?.orderamount ??
booking?.deliverycharges ??
booking?.deliverycharge ??
booking?.estimatedprice;
return Number.isFinite(Number(val)) ? Number(val) : undefined;
};
export default function Orders() {
const navigate = useNavigate();
@@ -72,6 +84,11 @@ export default function Orders() {
const { selectedZone, selectedZoneId, isAllZones, matchesZone } = useZone();
const { data: customers = [] } = useCustomers();
const { data: riders = [] } = useRiderOptions();
const [manualAssignOpen, setManualAssignOpen] = useState(false);
const [selectedRiderId, setSelectedRiderId] = useState('');
const [isAssigningManual, setIsAssigningManual] = useState(false);
const bulkCancel = useBulkCancelBookings();
/* Polled: an operator watching the pending queue needs new bookings to
@@ -213,6 +230,39 @@ export default function Orders() {
});
};
const handleManualAssign = async () => {
if (!selectedRiderId || !selectedIds.length) {
OpenToast('Please select a rider first', 'warning');
return;
}
const targetRider = riders.find((r) => String(r.userid || r.mileruserid || r.id) === String(selectedRiderId));
if (!targetRider) {
OpenToast('Please select a valid rider', 'warning');
return;
}
const riderUserId = targetRider.userid || targetRider.mileruserid || targetRider.id;
setIsAssigningManual(true);
try {
await Promise.all(
selectedIds.map((bookingId) =>
assignMilerToBooking(bookingId, { mileruserid: riderUserId })
)
);
if (targetRider.milerprofileid) {
notifyMiler(targetRider.milerprofileid, 'DoormileXpress', `${selectedIds.length} orders assigned to you`).catch(() => {});
}
OpenToast(`${selectedIds.length} order${selectedIds.length === 1 ? '' : 's'} assigned to ${targetRider.displayname || targetRider.name || 'rider'}`, 'success');
refetchOrders();
setSelectedIds([]);
setManualAssignOpen(false);
setSelectedRiderId('');
} catch (err) {
OpenToast(messageOf(err, 'Failed to assign orders'), 'error', 4000);
} finally {
setIsAssigningManual(false);
}
};
const columns = useMemo(
() => [
{
@@ -445,14 +495,91 @@ export default function Orders() {
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.18, ease: 'easeOut' }}
className="fixed bottom-6 right-6 z-50 flex items-center gap-2 rounded-full border border-border bg-surface p-2 shadow-xl"
className="fixed bottom-6 right-6 z-50 flex flex-wrap items-center gap-2.5 rounded-2xl border border-slate-200 bg-white/95 p-2.5 shadow-2xl backdrop-blur-md"
>
<Button onClick={assignOrders} loading={optimise.isPending}>
<Truck className="mr-1.5 h-4 w-4" /> Assign orders ({selectedOrders.length})
<div className="px-2 text-xs font-bold text-slate-700">
{selectedOrders.length} selected
</div>
<Button
variant="default"
className="bg-purple-600 hover:bg-purple-700 text-white shadow-xs"
onClick={assignOrders}
loading={optimise.isPending}
>
<Sparkles className="mr-1.5 h-4 w-4" /> AI Assign ({selectedOrders.length})
</Button>
<Button
variant="outline"
className="border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100"
onClick={() => {
if (isAllZones) {
OpenToast('Please select a specific Zone / Hub first', 'warning');
return;
}
setManualAssignOpen(true);
}}
>
<Bike className="mr-1.5 h-4 w-4 text-emerald-600" /> Manual Assign
</Button>
<Button
variant="ghost"
className="text-red-600 hover:bg-red-50 hover:text-red-700"
onClick={() => setCancelOpen(true)}
>
<Ban className="mr-1.5 h-4 w-4" /> Cancel
</Button>
</motion.div>
)}
<Modal
open={manualAssignOpen}
onOpenChange={setManualAssignOpen}
title={`Manual Assign ${selectedIds.length} Order${selectedIds.length === 1 ? '' : 's'}`}
description="Choose an active rider to assign the selected orders directly."
>
<div className="space-y-4 pt-2">
<div>
<label className="block text-xs font-semibold text-slate-800 mb-1.5">
Select Rider <span className="text-red-500">*</span>
</label>
<select
value={selectedRiderId}
onChange={(e) => setSelectedRiderId(e.target.value)}
className="w-full h-10 px-3 bg-white border border-slate-200 rounded-lg text-xs font-medium text-slate-900 focus:outline-none focus:ring-2 focus:ring-black/10 focus:border-black"
>
<option value="">-- Choose an available rider --</option>
{riders.map((r) => {
const rId = String(r.userid || r.mileruserid || r.id);
const rName = r.displayname || r.name || `Rider #${rId}`;
const rPhone = r.mobile || r.phone ? ` · ${r.mobile || r.phone}` : '';
const rStatus = r.status ? ` [${r.status}]` : '';
return (
<option key={rId} value={rId}>
{rName} {rPhone} {rStatus}
</option>
);
})}
</select>
</div>
<div className="flex items-center justify-end gap-2 pt-3 border-t border-slate-100">
<Button variant="outline" onClick={() => setManualAssignOpen(false)} disabled={isAssigningManual}>
Close
</Button>
<Button
onClick={handleManualAssign}
disabled={!selectedRiderId || isAssigningManual}
loading={isAssigningManual}
>
<UserCheck className="mr-1.5 h-4 w-4" /> Assign {selectedIds.length} Orders
</Button>
</div>
</div>
</Modal>
<ConfirmModal
open={cancelOpen}
onOpenChange={setCancelOpen}