This commit is contained in:
2026-02-12 15:15:21 -05:00
parent f6c47d8acc
commit 24b03dc190
16 changed files with 107 additions and 109 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@@ -1,16 +1,16 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.3cda7e3a.css", "main.css": "/static/css/main.3cda7e3a.css",
"main.js": "/static/js/main.171aba0b.js", "main.js": "/static/js/main.ed5fc57f.js",
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js", "static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png", "static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
"index.html": "/index.html", "index.html": "/index.html",
"main.3cda7e3a.css.map": "/static/css/main.3cda7e3a.css.map", "main.3cda7e3a.css.map": "/static/css/main.3cda7e3a.css.map",
"main.171aba0b.js.map": "/static/js/main.171aba0b.js.map", "main.ed5fc57f.js.map": "/static/js/main.ed5fc57f.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map" "787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.3cda7e3a.css", "static/css/main.3cda7e3a.css",
"static/js/main.171aba0b.js" "static/js/main.ed5fc57f.js"
] ]
} }

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.171aba0b.js"></script><link href="/static/css/main.3cda7e3a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.ed5fc57f.js"></script><link href="/static/css/main.3cda7e3a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
client/.DS_Store vendored

Binary file not shown.

BIN
client/build/.DS_Store vendored

Binary file not shown.

View File

@@ -519,7 +519,7 @@ const EventsList = () => {
<Button className="me-2" variant="outline-primary" size="sm" onClick={() => goToPreviousDay()} > {'<'} </Button> <Button className="me-2" variant="outline-primary" size="sm" onClick={() => goToPreviousDay()} > {'<'} </Button>
<DatePicker className="me-2" selected={selectedDate} onChange={(v) => setSelectedDate(v)} /> <DatePicker className="me-2" selected={selectedDate} onChange={(v) => setSelectedDate(v)} />
<Button className="me-2 ms-2" variant="outline-primary" size="sm" onClick={() => goToNextDay()}> {'>'} </Button> <Button className="me-2 ms-2" variant="outline-primary" size="sm" onClick={() => goToNextDay()}> {'>'} </Button>
<Button className="me-2" variant="primary" size="sm" disabled={selectedItems.length === 0} onClick={() => setShowTransportationModal(true)}> + Show Assign Transportation Panel</Button> <Button className="me-2" variant="primary" size="sm" disabled={selectedItems.length === 0} onClick={() => { setTransportSelected('create_new'); setShowTransportationModal(true); }}> + Show Assign Transportation Panel</Button>
</div> </div>
{table('active')} {table('active')}
</Tab> </Tab>
@@ -551,15 +551,6 @@ const EventsList = () => {
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<> <>
<div className="app-main-content-fields-section">
<div className="mb-4 me-4"></div>
<div className="field-label">Select Transportation</div>
<select value={transportSelected} onChange={(e)=>{setTransportSelected(e.currentTarget.value)}}>
<option value=""></option>
<option value="create_new">Create A New Transportation</option>
{transportOptionsList.map((item) => <option key={item.id} value={item.id}>{item.title}</option>)}
</select>
</div>
{transportSelected === 'create_new' && ( {transportSelected === 'create_new' && (
<> <>
<hr/> <hr/>

View File

@@ -9,7 +9,7 @@ const RouteCustomerTable = ({transRoutes, sectionName, vehicles}) => {
useEffect(() => { useEffect(() => {
setCustomers(TransRoutesService.getAllCustomersFromRoutes([...transRoutes], vehicles)); setCustomers(TransRoutesService.getAllCustomersFromRoutes([...transRoutes], vehicles));
}) }, [transRoutes, vehicles])
const generateCustomerTableVechileReportData = () => { const generateCustomerTableVechileReportData = () => {
const head = ['No.', 'Name', 'Table', 'Vehicle Number']; const head = ['No.', 'Name', 'Table', 'Vehicle Number'];

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef, useCallback } from "react";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { selectInboundRoutes, selectTomorrowAllRoutes, selectTomorrowInboundRoutes, selectTomorrowOutboundRoutes, selectHistoryInboundRoutes, selectHistoryRoutes, selectHistoryOutboundRoutes, selectOutboundRoutes, selectAllRoutes, selectAllActiveVehicles, selectAllActiveDrivers, transRoutesSlice } from "./../../store"; import { selectInboundRoutes, selectTomorrowAllRoutes, selectTomorrowInboundRoutes, selectTomorrowOutboundRoutes, selectHistoryInboundRoutes, selectHistoryRoutes, selectHistoryOutboundRoutes, selectOutboundRoutes, selectAllRoutes, selectAllActiveVehicles, selectAllActiveDrivers, transRoutesSlice } from "./../../store";
@@ -48,7 +48,8 @@ const RoutesDashboard = () => {
const [routesForShowing, setRoutesForShowing] = useState(allRoutes); const [routesForShowing, setRoutesForShowing] = useState(allRoutes);
const [routesInboundForShowing, setRoutesInboundForShowing] = useState(inboundRoutes); const [routesInboundForShowing, setRoutesInboundForShowing] = useState(inboundRoutes);
const [routesOutboundForShowing, setRoutesOutboundForShowing] = useState(outboundRoutes); const [routesOutboundForShowing, setRoutesOutboundForShowing] = useState(outboundRoutes);
const [previousAbsentCustomerIds, setPreviousAbsentCustomerIds] = useState(new Set()); const previousAbsentCustomerIdsRef = useRef(new Set());
const isUpdatingAbsentRef = useRef(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState(undefined); const [errorMessage, setErrorMessage] = useState(undefined);
@@ -89,7 +90,7 @@ const RoutesDashboard = () => {
return ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear() return ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear()
} }
const processRoutesForAbsentCustomers = (inboundRoutes, outboundRoutes) => { const processRoutesForAbsentCustomers = useCallback((inboundRoutes, outboundRoutes) => {
// Get customers who are absent in inbound routes // Get customers who are absent in inbound routes
const absentCustomerIds = new Set(); const absentCustomerIds = new Set();
@@ -102,31 +103,48 @@ const RoutesDashboard = () => {
}); });
}); });
// Only update backend if absent customer list has changed // Only update backend if absent customer list has changed and not already updating
const previousIds = previousAbsentCustomerIdsRef.current;
const hasAbsentCustomersChanged = const hasAbsentCustomersChanged =
absentCustomerIds.size !== previousAbsentCustomerIds.size || absentCustomerIds.size !== previousIds.size ||
[...absentCustomerIds].some(id => !previousAbsentCustomerIds.has(id)) || [...absentCustomerIds].some(id => !previousIds.has(id)) ||
[...previousAbsentCustomerIds].some(id => !absentCustomerIds.has(id)); [...previousIds].some(id => !absentCustomerIds.has(id));
if (hasAbsentCustomersChanged) { if (hasAbsentCustomersChanged && !isUpdatingAbsentRef.current) {
// Update outbound routes in backend if customers need to be removed // Update ref immediately (synchronous) to prevent re-entry
previousAbsentCustomerIdsRef.current = absentCustomerIds;
isUpdatingAbsentRef.current = true;
// Collect routes that need updating
const routesToUpdate = [];
outboundRoutes.forEach(outboundRoute => { outboundRoutes.forEach(outboundRoute => {
const filteredCustomerList = outboundRoute.route_customer_list?.filter(customer => const filteredCustomerList = outboundRoute.route_customer_list?.filter(customer =>
!absentCustomerIds.has(customer.customer_id) !absentCustomerIds.has(customer.customer_id)
) || []; ) || [];
// If customers were removed, update the route in backend
if (filteredCustomerList.length !== outboundRoute.route_customer_list?.length) { if (filteredCustomerList.length !== outboundRoute.route_customer_list?.length) {
const updatedRoute = { routesToUpdate.push({
...outboundRoute, id: outboundRoute.id,
route_customer_list: filteredCustomerList data: { ...outboundRoute, route_customer_list: filteredCustomerList }
}; });
dispatch(updateRoute({ id: outboundRoute.id, data: updatedRoute }));
} }
}); });
// Update the previous absent customer IDs // Batch update via direct API calls, then refetch once
setPreviousAbsentCustomerIds(absentCustomerIds); if (routesToUpdate.length > 0) {
Promise.all(routesToUpdate.map(r => TransRoutesService.updateRoute(r.id, r.data)))
.then(() => {
dispatch(fetchAllRoutes());
})
.catch(err => {
console.error('Error batch-updating outbound routes:', err);
})
.finally(() => {
isUpdatingAbsentRef.current = false;
});
} else {
isUpdatingAbsentRef.current = false;
}
} }
// Remove absent customers from outbound routes for display // Remove absent customers from outbound routes for display
@@ -138,7 +156,7 @@ const RoutesDashboard = () => {
})); }));
return processedOutboundRoutes; return processedOutboundRoutes;
} }, [dispatch, fetchAllRoutes])
useEffect(() => { useEffect(() => {
if (scheduleDate) { if (scheduleDate) {
@@ -535,7 +553,7 @@ const RoutesDashboard = () => {
} }
const cleanAllRoutesStatus = () => { const cleanAllRoutesStatus = () => {
for (const route of allRoutes) { const updatePromises = allRoutes.map(route => {
let cleanRoute = Object.assign({}, route, { status: [ROUTE_STATUS.NOT_STARTED] }); let cleanRoute = Object.assign({}, route, { status: [ROUTE_STATUS.NOT_STARTED] });
let newCustomers = []; let newCustomers = [];
for (let i=0; i< cleanRoute.route_customer_list.length; i++) { for (let i=0; i< cleanRoute.route_customer_list.length; i++) {
@@ -552,9 +570,15 @@ const RoutesDashboard = () => {
newCustomers.push(newCustomerListObject); newCustomers.push(newCustomerListObject);
} }
cleanRoute = Object.assign({}, cleanRoute, {route_customer_list: newCustomers}); cleanRoute = Object.assign({}, cleanRoute, {route_customer_list: newCustomers});
TransRoutesService.updateRoute(route.id, cleanRoute); return TransRoutesService.updateRoute(route.id, cleanRoute);
} });
setTimeout(dispatch(fetchAllRoutes()), 10000); Promise.all(updatePromises)
.then(() => {
dispatch(fetchAllRoutes());
})
.catch(err => {
console.error('Error cleaning route statuses:', err);
});
} }
const handleCleanAllRoutesStatus = () => { const handleCleanAllRoutesStatus = () => {
@@ -579,13 +603,23 @@ const RoutesDashboard = () => {
customer_language: customer.language customer_language: customer.language
}) })
}; };
for (const route of allRoutes) { // Batch all route updates, then refetch once after all complete
const updatePromises = allRoutes.map(route => {
const customerList = route.route_customer_list; const customerList = route.route_customer_list;
const newCustomerList = customerList.map((customerItem) => Object.assign({}, customerItem, customersMap.get(customerItem.customer_id))); const newCustomerList = customerList.map((customerItem) => Object.assign({}, customerItem, customersMap.get(customerItem.customer_id)));
const newRouteObject = Object.assign({}, route, {route_customer_list: newCustomerList}); const newRouteObject = Object.assign({}, route, {route_customer_list: newCustomerList});
TransRoutesService.updateRoute(route.id, newRouteObject); return TransRoutesService.updateRoute(route.id, newRouteObject);
} });
setTimeout(() => { dispatch(fetchAllRoutes()); setShowSyncCustomersLoading(false);}, 5000); Promise.all(updatePromises)
.then(() => {
dispatch(fetchAllRoutes());
})
.catch(err => {
console.error('Error syncing customer info:', err);
})
.finally(() => {
setShowSyncCustomersLoading(false);
});
}) })
} }

View File

@@ -5,7 +5,7 @@ import { DailyRoutesTemplateService } from "../../services";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store"; import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
import { Breadcrumb, Spinner, Modal, Button } from "react-bootstrap"; import { Breadcrumb, Spinner, Modal, Button } from "react-bootstrap";
import { Pencil, Trash } from "react-bootstrap-icons"; import { Pencil, Trash } from "react-bootstrap-icons";
import RouteCustomerTable from "./RouteCustomerTable"; import RouteCustomerEditor from "./RouteCustomerEditor";
const ViewDailyTemplateRoute = () => { const ViewDailyTemplateRoute = () => {
const params = useParams(); const params = useParams();
@@ -136,19 +136,11 @@ const ViewDailyTemplateRoute = () => {
</div> </div>
</div> </div>
{currentRoute.route_customer_list && currentRoute.route_customer_list.length > 0 && ( <RouteCustomerEditor
<div className="list row mt-4"> currentRoute={currentRoute}
<div className="col-md-12"> viewMode={true}
<h5>Customer List</h5> editFun={() => goToEdit()}
<RouteCustomerTable />
transRoutes={[currentRoute]}
sectionName={'Customers'}
vehicles={[]}
isTemplate={true}
/>
</div>
</div>
)}
</div> </div>
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}> <Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>

View File

@@ -62,60 +62,44 @@ function* updateCustomerSaga(action) {
route.route_customer_list.some(customer => customer.customer_id === customerId) route.route_customer_list.some(customer => customer.customer_id === customerId)
); );
// Update each route with the customer's latest info if (routesToUpdate.length > 0) {
for (const route of routesToUpdate) { // Build all updated route objects
const updatedCustomerList = route.route_customer_list.map(customer => { const updatePayloads = routesToUpdate.map(route => {
if (customer.customer_id === customerId) { const updatedCustomerList = route.route_customer_list.map(customer => {
return { if (customer.customer_id === customerId) {
...customer, return {
customer_name: updatedCustomerData.name || customer.customer_name, ...customer,
customer_address: updatedCustomerData.address1 || updatedCustomerData.address2 || updatedCustomerData.address3 || updatedCustomerData.address4 || updatedCustomerData.address5 || customer.customer_address, customer_name: updatedCustomerData.name || customer.customer_name,
customer_phone: updatedCustomerData.mobile_phone || updatedCustomerData.phone || customer.customer_phone, customer_address: updatedCustomerData.address1 || updatedCustomerData.address2 || updatedCustomerData.address3 || updatedCustomerData.address4 || updatedCustomerData.address5 || customer.customer_address,
customer_special_needs: updatedCustomerData.special_needs || customer.customer_special_needs, customer_phone: updatedCustomerData.mobile_phone || updatedCustomerData.phone || customer.customer_phone,
customer_note: updatedCustomerData.note || customer.customer_note, customer_special_needs: updatedCustomerData.special_needs || customer.customer_special_needs,
customer_language: updatedCustomerData.language || customer.customer_language, customer_note: updatedCustomerData.note || customer.customer_note,
customer_type: updatedCustomerData.type || customer.customer_type, customer_language: updatedCustomerData.language || customer.customer_language,
customer_table_id: updatedCustomerData.table_id || customer.customer_table_id, customer_type: updatedCustomerData.type || customer.customer_type,
customer_group: updatedCustomerData.groups ? updatedCustomerData.groups[0] : customer.customer_group customer_table_id: updatedCustomerData.table_id || customer.customer_table_id,
}; customer_group: updatedCustomerData.groups ? updatedCustomerData.groups[0] : customer.customer_group
} };
return customer; }
return customer;
});
return {
id: route.id,
data: { ...route, route_customer_list: updatedCustomerList, updatedAt: new Date(), updatedBy: 'admin' }
};
}); });
const updatedRoute = { // Batch update all routes in parallel instead of sequentially
...route, yield call(() => Promise.all(updatePayloads.map(p => TransRoutesService.updateRoute(p.id, p.data))));
route_customer_list: updatedCustomerList,
updatedAt: new Date(),
updatedBy: 'admin'
};
yield call(TransRoutesService.updateRoute, route.id, updatedRoute); // Refetch only today's routes to update the store
}
// Refetch routes to update the store with latest data
if (routesToUpdate.length > 0) {
try { try {
// Refetch today's routes
const date = new Date(); const date = new Date();
const dateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear(); const dateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const todayRoutes = (yield call(TransRoutesService.getAll, dateText)).data; const todayRoutes = (yield call(TransRoutesService.getAll, dateText)).data;
yield put(fetchAllRoutesSuccess(todayRoutes)); yield put(fetchAllRoutesSuccess(todayRoutes));
// Refetch tomorrow's routes
date.setDate(date.getDate() + 1);
const tmrDateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const tomorrowRoutes = (yield call(TransRoutesService.getAll, tmrDateText)).data;
yield put(fetchAllTomorrowRoutesSuccess(tomorrowRoutes));
// Refetch history routes (last 7 days)
const historyDate = new Date();
historyDate.setDate(historyDate.getDate() - 7);
const historyDateText = moment(historyDate).format('MM/DD/YYYY');
const historyRoutes = (yield call(TransRoutesService.getAll, historyDateText)).data;
yield put(fetchAllHisotryRoutesSuccess(historyRoutes));
} catch (refetchError) { } catch (refetchError) {
console.error('Error refetching routes:', refetchError); console.error('Error refetching routes:', refetchError);
// Don't fail the customer update if refetch fails
} }
} }
} catch (routeUpdateError) { } catch (routeUpdateError) {

View File

@@ -101,15 +101,12 @@ function* updateRouteSaga(action) {
yield put(fetchAllHisotryRoutesSuccess(routes)); yield put(fetchAllHisotryRoutesSuccess(routes));
} }
} else { } else if (!action.payload.skipRefetch) {
// Only refetch today's routes by default (not tomorrow's too)
const date = new Date(); const date = new Date();
const dateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear(); const dateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const routes = (yield call(TransRoutesService.getAll, dateText)).data; const routes = (yield call(TransRoutesService.getAll, dateText)).data;
yield put(fetchAllRoutesSuccess(routes)); yield put(fetchAllRoutesSuccess(routes));
date.setDate(date.getDate() + 1);
const tmrDateText = ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear();
const routesTmr = (yield call(TransRoutesService.getAll, tmrDateText)).data;
yield put(fetchAllTomorrowRoutesSuccess(routesTmr));
} }
if (action.payload.callback) { if (action.payload.callback) {
action.payload.callback(); action.payload.callback();