This commit is contained in:
@@ -93,6 +93,29 @@ const uploadPhysicalFile = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletePhysicalFile = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { objectId, fileType, model, fileName } = req.body || {};
|
||||||
|
if (!objectId || !fileType || !model || !fileName) {
|
||||||
|
return res.status(400).send({ message: 'Required fields missed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`;
|
||||||
|
const targetFilePath = path.join(BASE_UPLOAD_DIR, model, objectId, fileType, fileName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(targetFilePath)) {
|
||||||
|
return res.status(200).send({ message: 'File already removed.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.unlink(targetFilePath);
|
||||||
|
return res.status(200).send({ message: 'File deleted successfully.' });
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).send({
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getFilesByType = async (req, res) => {
|
const getFilesByType = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {objectId, fileType, name, model} = req.params;
|
const {objectId, fileType, name, model} = req.params;
|
||||||
@@ -158,5 +181,6 @@ module.exports = {
|
|||||||
getFile,
|
getFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
uploadPhysicalFile,
|
uploadPhysicalFile,
|
||||||
|
deletePhysicalFile,
|
||||||
getFilesByType
|
getFilesByType
|
||||||
};
|
};
|
||||||
@@ -17,6 +17,7 @@ module.exports = app => {
|
|||||||
router.post("/upload/:filename", upload.uploadFiles);
|
router.post("/upload/:filename", upload.uploadFiles);
|
||||||
router.post("/upload-physical", handleUploadMiddleware, upload.uploadPhysicalFile);
|
router.post("/upload-physical", handleUploadMiddleware, upload.uploadPhysicalFile);
|
||||||
router.post("/delete", upload.deleteFile);
|
router.post("/delete", upload.deleteFile);
|
||||||
|
router.post("/delete-physical", upload.deletePhysicalFile);
|
||||||
router.get("/uploadedDocs/:model/:objectId/type/:fileType/name/:name", upload.getFilesByType);
|
router.get("/uploadedDocs/:model/:objectId/type/:fileType/name/:name", upload.getFilesByType);
|
||||||
app.use('/api/files', router);
|
app.use('/api/files', router);
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS, PERSONAL_ROUTE_STATUS_TEXT, PICKUP_STATUS, PICKUP_STATUS_TEXT, REPORT_TYPE } from "../../shared";
|
import { CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS, PERSONAL_ROUTE_STATUS_TEXT, PICKUP_STATUS, PICKUP_STATUS_TEXT, REPORT_TYPE, PROGRAM_TYPE_TEXT, PAY_SOURCE_TEXT } from "../../shared";
|
||||||
import { Modal, Button } from "react-bootstrap";
|
import { Modal, Button } from "react-bootstrap";
|
||||||
import { transRoutesSlice } from "./../../store";
|
import { transRoutesSlice } from "./../../store";
|
||||||
import { CSVLink } from "react-csv";
|
import { CSVLink } from "react-csv";
|
||||||
@@ -153,6 +153,23 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
return dateObj;
|
return dateObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDisplayNameWithProgramAndPaySource = (customer) => {
|
||||||
|
const baseName = customer?.customer_name || '';
|
||||||
|
const programTypeRaw = (customer?.customer_program_type || customer?.program_type || '').toString().trim();
|
||||||
|
const paySourceRaw = (customer?.customer_pay_source || customer?.pay_source || '').toString().trim();
|
||||||
|
|
||||||
|
const qualifiers = [];
|
||||||
|
if (programTypeRaw && programTypeRaw.toLowerCase() !== 'amdc') {
|
||||||
|
qualifiers.push(PROGRAM_TYPE_TEXT[programTypeRaw] || programTypeRaw);
|
||||||
|
}
|
||||||
|
if (paySourceRaw && paySourceRaw.toLowerCase() !== 'medicaid') {
|
||||||
|
qualifiers.push(PAY_SOURCE_TEXT[paySourceRaw] || paySourceRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualifiers.length === 0) return baseName;
|
||||||
|
return `${baseName} (${qualifiers.join(', ')})`;
|
||||||
|
};
|
||||||
|
|
||||||
const saveRouteCustomerInfo = () => {
|
const saveRouteCustomerInfo = () => {
|
||||||
const routeId = customerInEdit.routeId;
|
const routeId = customerInEdit.routeId;
|
||||||
let removeSignature = false
|
let removeSignature = false
|
||||||
@@ -752,8 +769,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
<th>Leave Center Time</th>
|
<th>Leave Center Time</th>
|
||||||
<th>Drop Off Time</th>
|
<th>Drop Off Time</th>
|
||||||
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
||||||
{showCompletedInfo && (<th>Schedule Absent Note</th>)}
|
{showCompletedInfo && (<th>Note</th>)}
|
||||||
{showCompletedInfo && (<th>Special Needs</th>)}
|
|
||||||
{showCompletedInfo && (<th>Pickup Order</th>)}
|
{showCompletedInfo && (<th>Pickup Order</th>)}
|
||||||
{showCompletedInfo && (<th>Estimated Pickup Time</th>)}
|
{showCompletedInfo && (<th>Estimated Pickup Time</th>)}
|
||||||
{!showCompletedInfo && (<th>Vehicle Number</th>)}
|
{!showCompletedInfo && (<th>Vehicle Number</th>)}
|
||||||
@@ -802,7 +818,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
return (<tr key={index}>
|
return (<tr key={index}>
|
||||||
<td className="td-index"> {index + 1}</td>
|
<td className="td-index"> {index + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
{ customer.customer_name}
|
{ getDisplayNameWithProgramAndPaySource(customer)}
|
||||||
</td>
|
</td>
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_address_override || customer.customer_address }
|
{ customer.customer_address_override || customer.customer_address }
|
||||||
@@ -826,9 +842,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||||
</td>)}
|
</td>)}
|
||||||
{showCompletedInfo && (<td>
|
|
||||||
{ customer.customer_note }
|
|
||||||
</td>)}
|
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_special_needs }
|
{ customer.customer_special_needs }
|
||||||
</td>)}
|
</td>)}
|
||||||
@@ -857,7 +870,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
return (<tr key={index} >
|
return (<tr key={index} >
|
||||||
<td className="td-index">{stopNo}</td>
|
<td className="td-index">{stopNo}</td>
|
||||||
<td>
|
<td>
|
||||||
{ customerItem.customer_name}
|
{ getDisplayNameWithProgramAndPaySource(customerItem)}
|
||||||
</td>
|
</td>
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customerItem.customer_address_override || customerItem.customer_address }
|
{ customerItem.customer_address_override || customerItem.customer_address }
|
||||||
@@ -881,9 +894,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||||
</td>)}
|
</td>)}
|
||||||
{showCompletedInfo && (<td>
|
|
||||||
{ customerItem.customer_note }
|
|
||||||
</td>)}
|
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customerItem.customer_special_needs }
|
{ customerItem.customer_special_needs }
|
||||||
</td>)}
|
</td>)}
|
||||||
@@ -916,7 +926,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
customerItem.customers?.map((customer) => (<tr key={customer.customer_id} style={groupedRowsStyle} onClick={() => openForceEditModal(customer)}>
|
customerItem.customers?.map((customer) => (<tr key={customer.customer_id} style={groupedRowsStyle} onClick={() => openForceEditModal(customer)}>
|
||||||
<td className="td-index"></td>
|
<td className="td-index"></td>
|
||||||
<td className="children">
|
<td className="children">
|
||||||
{ customer.customer_name}
|
{ getDisplayNameWithProgramAndPaySource(customer)}
|
||||||
</td>
|
</td>
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_address_override || customer.customer_address }
|
{ customer.customer_address_override || customer.customer_address }
|
||||||
@@ -940,9 +950,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||||
</td>)}
|
</td>)}
|
||||||
{showCompletedInfo && (<td>
|
|
||||||
{ customer.customer_note }
|
|
||||||
</td>)}
|
|
||||||
{showCompletedInfo && (<td>
|
{showCompletedInfo && (<td>
|
||||||
{ customer.customer_special_needs }
|
{ customer.customer_special_needs }
|
||||||
</td>)}
|
</td>)}
|
||||||
@@ -1044,13 +1051,15 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="app-main-content-fields-section">
|
{false && (
|
||||||
<div className="me-4">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="field-label">Note
|
<div className="me-4">
|
||||||
</div>
|
<div className="field-label">Note
|
||||||
<textarea value={customerNote} onChange={(e) => {setCustomerNote(e.target.value)}}></textarea>
|
</div>
|
||||||
</div>
|
<textarea value={customerNote} onChange={(e) => {setCustomerNote(e.target.value)}}></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
|
|||||||
@@ -416,8 +416,20 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addPersonnel = () => {
|
const addPersonnel = () => {
|
||||||
const result = [].concat(customers).concat(newRouteCustomerList);
|
const merged = [...customers];
|
||||||
setCustomers(result.filter((item, pos) => result.indexOf(item) === pos));
|
newRouteCustomerList.forEach((newCustomer) => {
|
||||||
|
const existingIndex = merged.findIndex((item) => item?.customer_id === newCustomer?.customer_id);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// If the customer already exists, overwrite with latest selection (including address).
|
||||||
|
merged[existingIndex] = {
|
||||||
|
...merged[existingIndex],
|
||||||
|
...newCustomer,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
merged.push(newCustomer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setCustomers(merged);
|
||||||
setShowAddPersonnelModal(false);
|
setShowAddPersonnelModal(false);
|
||||||
setNewRouteCustomerList([]);
|
setNewRouteCustomerList([]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router-dom";
|
|||||||
import { selectAllRoutes, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "../../store";
|
import { selectAllRoutes, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "../../store";
|
||||||
import { CustomerService, SignatureRequestService, EventsService } from "../../services";
|
import { CustomerService, SignatureRequestService, EventsService } from "../../services";
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS } from "../../shared";
|
import { CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS, PROGRAM_TYPE_TEXT, PAY_SOURCE_TEXT } from "../../shared";
|
||||||
|
|
||||||
const RouteReportWithSignature = () => {
|
const RouteReportWithSignature = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@@ -121,6 +121,23 @@ const RouteReportWithSignature = () => {
|
|||||||
return safeA - safeB;
|
return safeA - safeB;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getDisplayNameWithProgramAndPaySource = (customer) => {
|
||||||
|
const baseName = customer?.customer_name || '';
|
||||||
|
const programTypeRaw = (customer?.customer_program_type || customer?.program_type || '').toString().trim();
|
||||||
|
const paySourceRaw = (customer?.customer_pay_source || customer?.pay_source || '').toString().trim();
|
||||||
|
|
||||||
|
const qualifiers = [];
|
||||||
|
if (programTypeRaw && programTypeRaw.toLowerCase() !== 'amdc') {
|
||||||
|
qualifiers.push(PROGRAM_TYPE_TEXT[programTypeRaw] || programTypeRaw);
|
||||||
|
}
|
||||||
|
if (paySourceRaw && paySourceRaw.toLowerCase() !== 'medicaid') {
|
||||||
|
qualifiers.push(PAY_SOURCE_TEXT[paySourceRaw] || paySourceRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualifiers.length === 0) return baseName;
|
||||||
|
return `${baseName} (${qualifiers.join(', ')})`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>
|
<style>
|
||||||
@@ -253,7 +270,7 @@ const RouteReportWithSignature = () => {
|
|||||||
return (
|
return (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td style={{ textAlign: 'center' }}>{index + 1}</td>
|
<td style={{ textAlign: 'center' }}>{index + 1}</td>
|
||||||
<td>{customer?.customer_name}</td>
|
<td>{getDisplayNameWithProgramAndPaySource(customer)}</td>
|
||||||
<td>{customer?.customer_phone}</td>
|
<td>{customer?.customer_phone}</td>
|
||||||
<td>{customer?.customer_address_override || customer?.customer_address}</td>
|
<td>{customer?.customer_address_override || customer?.customer_address}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|||||||
@@ -86,8 +86,9 @@ const RoutesDashboard = () => {
|
|||||||
const [scheduleImportProgress, setScheduleImportProgress] = useState(0);
|
const [scheduleImportProgress, setScheduleImportProgress] = useState(0);
|
||||||
const [scheduleImportLabel, setScheduleImportLabel] = useState('');
|
const [scheduleImportLabel, setScheduleImportLabel] = useState('');
|
||||||
const [showCheckRoutesModal, setShowCheckRoutesModal] = useState(false);
|
const [showCheckRoutesModal, setShowCheckRoutesModal] = useState(false);
|
||||||
const [checkRoutesResult, setCheckRoutesResult] = useState({ inbound: [], outbound: [], attendance: [], addressMismatch: [], customerTypeMismatch: [], dischargedOnRoute: [] });
|
const [checkRoutesResult, setCheckRoutesResult] = useState({ inbound: [], outbound: [], attendance: [], addressMismatch: [], customerTypeMismatch: [], customerSpecialNeedsMismatch: [], dischargedOnRoute: [] });
|
||||||
const [customerTypeFixing, setCustomerTypeFixing] = useState({});
|
const [customerTypeFixing, setCustomerTypeFixing] = useState({});
|
||||||
|
const [customerSpecialNeedsFixing, setCustomerSpecialNeedsFixing] = useState({});
|
||||||
const scheduleImportProgressTimerRef = useRef(null);
|
const scheduleImportProgressTimerRef = useRef(null);
|
||||||
|
|
||||||
|
|
||||||
@@ -424,6 +425,40 @@ const RoutesDashboard = () => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildCustomerSpecialNeedsMismatchIssues = (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_special_needs || ''}`.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) => {
|
const syncCustomerTypeForMismatch = async (issue) => {
|
||||||
if (!issue?.customerId || !issue?.dbType || !issue?.mismatchedRoutes?.length) return;
|
if (!issue?.customerId || !issue?.dbType || !issue?.mismatchedRoutes?.length) return;
|
||||||
setCustomerTypeFixing((prev) => Object.assign({}, prev, { [issue.customerId]: true }));
|
setCustomerTypeFixing((prev) => Object.assign({}, prev, { [issue.customerId]: true }));
|
||||||
@@ -452,11 +487,40 @@ const RoutesDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const syncCustomerSpecialNeedsForMismatch = async (issue) => {
|
||||||
|
if (!issue?.customerId || !issue?.mismatchedRoutes?.length) return;
|
||||||
|
setCustomerSpecialNeedsFixing((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_special_needs: issue.dbNoteToDriver || '' });
|
||||||
|
});
|
||||||
|
return TransRoutesService.updateRoute(route.id, Object.assign({}, route, { route_customer_list: nextCustomerList }));
|
||||||
|
});
|
||||||
|
await Promise.all(updatePromises);
|
||||||
|
setSuccessMessage(`Synced note to driver for ${issue.customerName}.`);
|
||||||
|
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||||
|
dispatch(fetchAllTomorrowRoutes({dateText: moment(dateSelected).format('MM/DD/YYYY')}));
|
||||||
|
await runCheckRoutes();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing note to driver mismatch:', error);
|
||||||
|
setErrorMessage(`Failed to sync note to driver for ${issue.customerName}.`);
|
||||||
|
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||||
|
} finally {
|
||||||
|
setCustomerSpecialNeedsFixing((prev) => Object.assign({}, prev, { [issue.customerId]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const runCheckRoutes = async () => {
|
const runCheckRoutes = async () => {
|
||||||
const inboundIssues = buildRouteConflictsByDirection(tmrInboundRoutes || []);
|
const inboundIssues = buildRouteConflictsByDirection(tmrInboundRoutes || []);
|
||||||
const outboundIssues = buildRouteConflictsByDirection(tmrOutboundRoutes || []);
|
const outboundIssues = buildRouteConflictsByDirection(tmrOutboundRoutes || []);
|
||||||
const addressMismatchIssues = buildAddressMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
const addressMismatchIssues = buildAddressMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||||
const customerTypeMismatchIssues = buildCustomerTypeMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
const customerTypeMismatchIssues = buildCustomerTypeMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||||
|
const customerSpecialNeedsMismatchIssues = buildCustomerSpecialNeedsMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||||
const dischargedOnRouteIssues = buildDischargedCustomerRouteIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
const dischargedOnRouteIssues = buildDischargedCustomerRouteIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||||
let attendanceIssues = [];
|
let attendanceIssues = [];
|
||||||
try {
|
try {
|
||||||
@@ -464,7 +528,7 @@ const RoutesDashboard = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking attendance notes against routes:', error);
|
console.error('Error checking attendance notes against routes:', error);
|
||||||
}
|
}
|
||||||
setCheckRoutesResult({ inbound: inboundIssues, outbound: outboundIssues, attendance: attendanceIssues, addressMismatch: addressMismatchIssues, customerTypeMismatch: customerTypeMismatchIssues, dischargedOnRoute: dischargedOnRouteIssues });
|
setCheckRoutesResult({ inbound: inboundIssues, outbound: outboundIssues, attendance: attendanceIssues, addressMismatch: addressMismatchIssues, customerTypeMismatch: customerTypeMismatchIssues, customerSpecialNeedsMismatch: customerSpecialNeedsMismatchIssues, dischargedOnRoute: dischargedOnRouteIssues });
|
||||||
setShowCheckRoutesModal(true);
|
setShowCheckRoutesModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -749,6 +813,15 @@ const RoutesDashboard = () => {
|
|||||||
const goToCreateRoute = (type) => {
|
const goToCreateRoute = (type) => {
|
||||||
navigate(`/trans-routes/create?type=${type}&date=${dateSelected? moment(dateSelected).format('YYYY-MM-DD'): 'tomorrow'}`);
|
navigate(`/trans-routes/create?type=${type}&date=${dateSelected? moment(dateSelected).format('YYYY-MM-DD'): 'tomorrow'}`);
|
||||||
}
|
}
|
||||||
|
const goToRouteDetails = (routeId) => {
|
||||||
|
if (!routeId) return;
|
||||||
|
const selectedDateString = getDateString(dateSelected);
|
||||||
|
if (selectedDateString === getDateString(new Date())) {
|
||||||
|
navigate(`/trans-routes/${routeId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigate(`/trans-routes/${routeId}?dateSchedule=${moment(dateSelected).format('YYYY-MM-DD')}`);
|
||||||
|
}
|
||||||
const changeTab = (k) => {
|
const changeTab = (k) => {
|
||||||
setCurrentTab(k);
|
setCurrentTab(k);
|
||||||
setKeyword('');
|
setKeyword('');
|
||||||
@@ -1760,7 +1833,16 @@ const RoutesDashboard = () => {
|
|||||||
}).filter((route) => route.name?.toLowerCase().includes(keyword?.toLowerCase()) || drivers.find((d) => d.id === route?.driver)?.name?.toLowerCase().includes(keyword?.toLowerCase()))?.map(({id, name, end_time, driver, type, signature}, index) => {
|
}).filter((route) => route.name?.toLowerCase().includes(keyword?.toLowerCase()) || drivers.find((d) => d.id === route?.driver)?.name?.toLowerCase().includes(keyword?.toLowerCase()))?.map(({id, name, end_time, driver, type, signature}, index) => {
|
||||||
return (<tr key={index}>
|
return (<tr key={index}>
|
||||||
<td className="td-index">{index + 1}</td>
|
<td className="td-index">{index + 1}</td>
|
||||||
<td>{name}</td>
|
<td>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-link p-0"
|
||||||
|
style={{ fontSize: 'inherit', verticalAlign: 'baseline' }}
|
||||||
|
onClick={() => goToRouteDetails(id)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td>{drivers.find((d) => d.id === driver)?.name}</td>
|
<td>{drivers.find((d) => d.id === driver)?.name}</td>
|
||||||
<td>{end_time? moment(end_time).format('HH:mm'): ''}</td>
|
<td>{end_time? moment(end_time).format('HH:mm'): ''}</td>
|
||||||
<td>{type}</td>
|
<td>{type}</td>
|
||||||
@@ -1968,7 +2050,7 @@ const RoutesDashboard = () => {
|
|||||||
<Modal.Title>Check Routes Result</Modal.Title>
|
<Modal.Title>Check Routes Result</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{(checkRoutesResult.inbound.length === 0 && checkRoutesResult.outbound.length === 0 && checkRoutesResult.attendance.length === 0 && checkRoutesResult.addressMismatch.length === 0 && checkRoutesResult.customerTypeMismatch.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.dischargedOnRoute.length === 0) ? (
|
||||||
<div className="text-success d-flex align-items-center">
|
<div className="text-success d-flex align-items-center">
|
||||||
<Check size={18} className="me-2"></Check>
|
<Check size={18} className="me-2"></Check>
|
||||||
<span>No issue found.</span>
|
<span>No issue found.</span>
|
||||||
@@ -2035,6 +2117,26 @@ const RoutesDashboard = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{checkRoutesResult.customerSpecialNeedsMismatch.length > 0 && (
|
||||||
|
<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>
|
||||||
|
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'}.
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline-primary"
|
||||||
|
size="sm"
|
||||||
|
disabled={!!customerSpecialNeedsFixing[issue.customerId]}
|
||||||
|
onClick={() => syncCustomerSpecialNeedsForMismatch(issue)}
|
||||||
|
>
|
||||||
|
{customerSpecialNeedsFixing[issue.customerId] ? 'Fixing...' : 'Fix'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{checkRoutesResult.dischargedOnRoute.length > 0 && (
|
{checkRoutesResult.dischargedOnRoute.length > 0 && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="fw-bold mb-2">Discharged Customer Issues</div>
|
<div className="fw-bold mb-2">Discharged Customer Issues</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
|
|||||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
||||||
import { Download, PencilSquare, Archive } from "react-bootstrap-icons";
|
import { Download, PencilSquare, Archive, Trash } from "react-bootstrap-icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { Export } from "../../shared/components";
|
import { Export } from "../../shared/components";
|
||||||
import {
|
import {
|
||||||
@@ -55,6 +55,37 @@ const ViewVehicle = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshVehicleDocuments = async () => {
|
||||||
|
if (!currentVehicle?.id || !currentVehicle?.vehicle_number) return;
|
||||||
|
const getInspectionDate = (name) => {
|
||||||
|
const arr1 = name.split('.');
|
||||||
|
const prefix = arr1[0];
|
||||||
|
if (prefix) {
|
||||||
|
const arr2 = prefix.split('_');
|
||||||
|
const dateNumber = arr2[arr2.length - 1];
|
||||||
|
return dateNumber ? new Date(parseInt(dateNumber)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}) : moment().format('MM/DD/YYYY');
|
||||||
|
} else {
|
||||||
|
return moment().format('MM/DD/YYYY');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const monthlyInspectionDocs = (await VehicleService.getAllVechileFiles(currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection'))?.data?.data?.files || [];
|
||||||
|
const yearlyInspectionDocs = (await VehicleService.getAllVechileFiles(currentVehicle.id, currentVehicle.vehicle_number, 'yearlyInspection'))?.data?.data?.files || [];
|
||||||
|
setMonthlyDocs(monthlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
|
||||||
|
setYearlyDocs(yearlyInspectionDocs?.map(item => ({ ...item, inspectionDate: getInspectionDate(item?.name) })));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInspectionFile = async (fileType, fileName) => {
|
||||||
|
if (!currentVehicle?.id || !fileName) return;
|
||||||
|
if (!window.confirm('Are you sure you want to delete this inspection file?')) return;
|
||||||
|
try {
|
||||||
|
await VehicleService.deleteVechileFile(currentVehicle.id, fileType, fileName);
|
||||||
|
await refreshVehicleDocuments();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete inspection file:', error);
|
||||||
|
window.alert('Failed to delete inspection file.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const deactivateVehicle = () => {
|
const deactivateVehicle = () => {
|
||||||
const data = {
|
const data = {
|
||||||
status: 'inactive'
|
status: 'inactive'
|
||||||
@@ -310,6 +341,7 @@ const ViewVehicle = () => {
|
|||||||
<td className="td-index">{index + 1}</td>
|
<td className="td-index">{index + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/monthly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/monthly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
||||||
|
<Trash size={14} className="clickable me-2 text-danger" onClick={() => deleteInspectionFile('monthlyInspection', doc?.name)} />
|
||||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{doc?.inspectionDate}</td>
|
<td>{doc?.inspectionDate}</td>
|
||||||
@@ -342,6 +374,7 @@ const ViewVehicle = () => {
|
|||||||
<td className="td-index">{index + 1}</td>
|
<td className="td-index">{index + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/yearly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/yearly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
||||||
|
<Trash size={14} className="clickable me-2 text-danger" onClick={() => deleteInspectionFile('yearlyInspection', doc?.name)} />
|
||||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{doc?.inspectionDate}</td>
|
<td>{doc?.inspectionDate}</td>
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ const getAllVechileFiles = (vehicleId, name, fileType) => {
|
|||||||
return http.get(`/files/uploadedDocs/vehicle/${vehicleId}/type/${fileType}/name/${name}`)
|
return http.get(`/files/uploadedDocs/vehicle/${vehicleId}/type/${fileType}/name/${name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteVechileFile = (vehicleId, fileType, fileName) => {
|
||||||
|
return http.post('/files/delete-physical', {
|
||||||
|
model: 'vehicle',
|
||||||
|
objectId: vehicleId,
|
||||||
|
fileType,
|
||||||
|
fileName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const VehicleService = {
|
export const VehicleService = {
|
||||||
getAll,
|
getAll,
|
||||||
getAllActiveVehicles,
|
getAllActiveVehicles,
|
||||||
@@ -40,5 +49,6 @@ export const VehicleService = {
|
|||||||
getVehicle,
|
getVehicle,
|
||||||
convertToDate,
|
convertToDate,
|
||||||
uploadVechileFile,
|
uploadVechileFile,
|
||||||
getAllVechileFiles
|
getAllVechileFiles,
|
||||||
|
deleteVechileFile
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user