This commit is contained in:
@@ -328,7 +328,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
customer_avatar: customer.avatar,
|
||||
customer_type: customer.type,
|
||||
customer_pickup_status: customer.pickup_status,
|
||||
customer_note: customer.note,
|
||||
customer_note: customer.notes_for_driver || '',
|
||||
customer_special_needs: customer.notes_for_driver || '',
|
||||
customer_phone: customer.phone || customer.mobile_phone || customer.home_phone,
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
|
||||
@@ -353,7 +353,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
customer_group_address: newGroupAddress,
|
||||
customer_type: customer.type,
|
||||
customer_pickup_status: customer.pickup_status,
|
||||
customer_note: customer.note,
|
||||
customer_note: customer.notes_for_driver || '',
|
||||
customer_special_needs: customer.notes_for_driver || '',
|
||||
customer_phone: customer.phone || customer.mobile_phone || customer.home_phone,
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
|
||||
@@ -462,7 +462,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
customer_avatar: customerData.avatar,
|
||||
customer_type: customerData.type,
|
||||
customer_pickup_status: customerData.pickup_status,
|
||||
customer_note: customerData.note,
|
||||
customer_note: customerData.notes_for_driver || '',
|
||||
customer_special_needs: customerData.notes_for_driver || '',
|
||||
customer_phone: customerData.phone || customerData.mobile_phone || customerData.home_phone,
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
|
||||
|
||||
@@ -86,9 +86,10 @@ const RoutesDashboard = () => {
|
||||
const [scheduleImportProgress, setScheduleImportProgress] = useState(0);
|
||||
const [scheduleImportLabel, setScheduleImportLabel] = useState('');
|
||||
const [showCheckRoutesModal, setShowCheckRoutesModal] = useState(false);
|
||||
const [checkRoutesResult, setCheckRoutesResult] = useState({ inbound: [], outbound: [], attendance: [], addressMismatch: [], customerTypeMismatch: [], customerSpecialNeedsMismatch: [], dischargedOnRoute: [] });
|
||||
const [checkRoutesResult, setCheckRoutesResult] = useState({ inbound: [], outbound: [], attendance: [], addressMismatch: [], customerTypeMismatch: [], customerSpecialNeedsMismatch: [], customerNoteMismatch: [], dischargedOnRoute: [] });
|
||||
const [customerTypeFixing, setCustomerTypeFixing] = useState({});
|
||||
const [customerSpecialNeedsFixing, setCustomerSpecialNeedsFixing] = useState({});
|
||||
const [customerNoteFixing, setCustomerNoteFixing] = useState({});
|
||||
const scheduleImportProgressTimerRef = useRef(null);
|
||||
|
||||
|
||||
@@ -459,6 +460,40 @@ const RoutesDashboard = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const buildCustomerNoteMismatchIssues = (routes = []) => {
|
||||
const customerProfileById = new Map((customers || []).map((customer) => [customer.id, customer]));
|
||||
const mismatchMap = new Map();
|
||||
(routes || []).forEach((route) => {
|
||||
(route?.route_customer_list || []).forEach((customerInRoute) => {
|
||||
const customerId = customerInRoute?.customer_id;
|
||||
if (!customerId) return;
|
||||
const customerProfile = customerProfileById.get(customerId);
|
||||
if (!customerProfile) return;
|
||||
|
||||
const routeNote = `${customerInRoute?.customer_note || ''}`.trim();
|
||||
const dbNoteToDriver = `${customerProfile?.notes_for_driver || ''}`.trim();
|
||||
if (routeNote === dbNoteToDriver) return;
|
||||
|
||||
const existing = mismatchMap.get(customerId) || {
|
||||
customerId,
|
||||
customerName: customerInRoute?.customer_name || customerProfile?.name || 'Unknown',
|
||||
dbNoteToDriver,
|
||||
mismatchedRoutes: []
|
||||
};
|
||||
existing.mismatchedRoutes.push({
|
||||
routeId: route?.id,
|
||||
routeName: route?.name || 'Unnamed Route',
|
||||
routeNote: routeNote || ''
|
||||
});
|
||||
mismatchMap.set(customerId, existing);
|
||||
});
|
||||
});
|
||||
return Array.from(mismatchMap.values()).map((item) => ({
|
||||
...item,
|
||||
mismatchedRoutes: item.mismatchedRoutes.filter((route, idx, arr) => arr.findIndex((r) => r.routeId === route.routeId) === idx)
|
||||
}));
|
||||
};
|
||||
|
||||
const syncCustomerTypeForMismatch = async (issue) => {
|
||||
if (!issue?.customerId || !issue?.dbType || !issue?.mismatchedRoutes?.length) return;
|
||||
setCustomerTypeFixing((prev) => Object.assign({}, prev, { [issue.customerId]: true }));
|
||||
@@ -515,12 +550,41 @@ const RoutesDashboard = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncCustomerNoteForMismatch = async (issue) => {
|
||||
if (!issue?.customerId || !issue?.mismatchedRoutes?.length) return;
|
||||
setCustomerNoteFixing((prev) => Object.assign({}, prev, { [issue.customerId]: true }));
|
||||
try {
|
||||
const routeMap = new Map([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])].map((route) => [route.id, route]));
|
||||
const updatePromises = issue.mismatchedRoutes.map((routeMeta) => {
|
||||
const route = routeMap.get(routeMeta.routeId);
|
||||
if (!route) return Promise.resolve();
|
||||
const nextCustomerList = (route.route_customer_list || []).map((customer) => {
|
||||
if (customer?.customer_id !== issue.customerId) return customer;
|
||||
return Object.assign({}, customer, { customer_note: issue.dbNoteToDriver || '' });
|
||||
});
|
||||
return TransRoutesService.updateRoute(route.id, Object.assign({}, route, { route_customer_list: nextCustomerList }));
|
||||
});
|
||||
await Promise.all(updatePromises);
|
||||
setSuccessMessage(`Synced route note for ${issue.customerName}.`);
|
||||
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||
dispatch(fetchAllTomorrowRoutes({dateText: moment(dateSelected).format('MM/DD/YYYY')}));
|
||||
await runCheckRoutes();
|
||||
} catch (error) {
|
||||
console.error('Error syncing customer note mismatch:', error);
|
||||
setErrorMessage(`Failed to sync route note for ${issue.customerName}.`);
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
} finally {
|
||||
setCustomerNoteFixing((prev) => Object.assign({}, prev, { [issue.customerId]: false }));
|
||||
}
|
||||
};
|
||||
|
||||
const runCheckRoutes = async () => {
|
||||
const inboundIssues = buildRouteConflictsByDirection(tmrInboundRoutes || []);
|
||||
const outboundIssues = buildRouteConflictsByDirection(tmrOutboundRoutes || []);
|
||||
const addressMismatchIssues = buildAddressMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const customerTypeMismatchIssues = buildCustomerTypeMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const customerSpecialNeedsMismatchIssues = buildCustomerSpecialNeedsMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const customerNoteMismatchIssues = buildCustomerNoteMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const dischargedOnRouteIssues = buildDischargedCustomerRouteIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
let attendanceIssues = [];
|
||||
try {
|
||||
@@ -528,7 +592,7 @@ const RoutesDashboard = () => {
|
||||
} catch (error) {
|
||||
console.error('Error checking attendance notes against routes:', error);
|
||||
}
|
||||
setCheckRoutesResult({ inbound: inboundIssues, outbound: outboundIssues, attendance: attendanceIssues, addressMismatch: addressMismatchIssues, customerTypeMismatch: customerTypeMismatchIssues, customerSpecialNeedsMismatch: customerSpecialNeedsMismatchIssues, dischargedOnRoute: dischargedOnRouteIssues });
|
||||
setCheckRoutesResult({ inbound: inboundIssues, outbound: outboundIssues, attendance: attendanceIssues, addressMismatch: addressMismatchIssues, customerTypeMismatch: customerTypeMismatchIssues, customerSpecialNeedsMismatch: customerSpecialNeedsMismatchIssues, customerNoteMismatch: customerNoteMismatchIssues, dischargedOnRoute: dischargedOnRouteIssues });
|
||||
setShowCheckRoutesModal(true);
|
||||
};
|
||||
|
||||
@@ -2050,7 +2114,7 @@ const RoutesDashboard = () => {
|
||||
<Modal.Title>Check Routes Result</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{(checkRoutesResult.inbound.length === 0 && checkRoutesResult.outbound.length === 0 && checkRoutesResult.attendance.length === 0 && checkRoutesResult.addressMismatch.length === 0 && checkRoutesResult.customerTypeMismatch.length === 0 && checkRoutesResult.customerSpecialNeedsMismatch.length === 0 && checkRoutesResult.dischargedOnRoute.length === 0) ? (
|
||||
{(checkRoutesResult.inbound.length === 0 && checkRoutesResult.outbound.length === 0 && checkRoutesResult.attendance.length === 0 && checkRoutesResult.addressMismatch.length === 0 && checkRoutesResult.customerTypeMismatch.length === 0 && checkRoutesResult.customerSpecialNeedsMismatch.length === 0 && checkRoutesResult.customerNoteMismatch.length === 0 && checkRoutesResult.dischargedOnRoute.length === 0) ? (
|
||||
<div className="text-success d-flex align-items-center">
|
||||
<Check size={18} className="me-2"></Check>
|
||||
<span>No issue found.</span>
|
||||
@@ -2121,18 +2185,54 @@ const RoutesDashboard = () => {
|
||||
<div className="mt-3">
|
||||
<div className="fw-bold mb-2">Note to Driver Mismatch Issues</div>
|
||||
{checkRoutesResult.customerSpecialNeedsMismatch.map((issue, idx) => (
|
||||
<div key={`customer-special-needs-mismatch-issue-${idx}`} className="mb-2 d-flex align-items-center justify-content-between" style={{ gap: '12px' }}>
|
||||
<div key={`customer-special-needs-mismatch-issue-${idx}`} className="mb-2" style={{ whiteSpace: 'normal', overflowWrap: 'anywhere' }}>
|
||||
<div>
|
||||
Customer {issue.customerName}'s note to driver is mismatched on route {joinRouteNames(issue.mismatchedRoutes.map((route) => route.routeName))}. Latest note in system is {issue.dbNoteToDriver || 'empty'}.
|
||||
Customer {issue.customerName}'s note to driver is mismatched on route {joinRouteNames(issue.mismatchedRoutes.map((route) => route.routeName))}.
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
Latest note in system:
|
||||
<div style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>
|
||||
{issue.dbNoteToDriver || 'empty'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
disabled={!!customerSpecialNeedsFixing[issue.customerId]}
|
||||
onClick={() => syncCustomerSpecialNeedsForMismatch(issue)}
|
||||
>
|
||||
{customerSpecialNeedsFixing[issue.customerId] ? 'Fixing...' : 'Fix'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{checkRoutesResult.customerNoteMismatch.length > 0 && (
|
||||
<div className="mt-3">
|
||||
<div className="fw-bold mb-2">Route Note Mismatch Issues</div>
|
||||
{checkRoutesResult.customerNoteMismatch.map((issue, idx) => (
|
||||
<div key={`customer-note-mismatch-issue-${idx}`} className="mb-2" style={{ whiteSpace: 'normal', overflowWrap: 'anywhere' }}>
|
||||
<div>
|
||||
Customer {issue.customerName}'s route note is mismatched on route {joinRouteNames(issue.mismatchedRoutes.map((route) => route.routeName))}.
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
Latest note in system:
|
||||
<div style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>
|
||||
{issue.dbNoteToDriver || 'empty'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
disabled={!!customerNoteFixing[issue.customerId]}
|
||||
onClick={() => syncCustomerNoteForMismatch(issue)}
|
||||
>
|
||||
{customerNoteFixing[issue.customerId] ? 'Fixing...' : 'Fix'}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
disabled={!!customerSpecialNeedsFixing[issue.customerId]}
|
||||
onClick={() => syncCustomerSpecialNeedsForMismatch(issue)}
|
||||
>
|
||||
{customerSpecialNeedsFixing[issue.customerId] ? 'Fixing...' : 'Fix'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user