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) => {
|
||||
try {
|
||||
const {objectId, fileType, name, model} = req.params;
|
||||
@@ -158,5 +181,6 @@ module.exports = {
|
||||
getFile,
|
||||
deleteFile,
|
||||
uploadPhysicalFile,
|
||||
deletePhysicalFile,
|
||||
getFilesByType
|
||||
};
|
||||
@@ -17,6 +17,7 @@ module.exports = app => {
|
||||
router.post("/upload/:filename", upload.uploadFiles);
|
||||
router.post("/upload-physical", handleUploadMiddleware, upload.uploadPhysicalFile);
|
||||
router.post("/delete", upload.deleteFile);
|
||||
router.post("/delete-physical", upload.deletePhysicalFile);
|
||||
router.get("/uploadedDocs/:model/:objectId/type/:fileType/name/:name", upload.getFilesByType);
|
||||
app.use('/api/files', router);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useState} from "react";
|
||||
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 { transRoutesSlice } from "./../../store";
|
||||
import { CSVLink } from "react-csv";
|
||||
@@ -153,6 +153,23 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
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 routeId = customerInEdit.routeId;
|
||||
let removeSignature = false
|
||||
@@ -752,8 +769,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
<th>Leave Center Time</th>
|
||||
<th>Drop Off Time</th>
|
||||
{showCompletedInfo && (<th>Schedule Absent</th>)}
|
||||
{showCompletedInfo && (<th>Schedule Absent Note</th>)}
|
||||
{showCompletedInfo && (<th>Special Needs</th>)}
|
||||
{showCompletedInfo && (<th>Note</th>)}
|
||||
{showCompletedInfo && (<th>Pickup Order</th>)}
|
||||
{showCompletedInfo && (<th>Estimated Pickup Time</th>)}
|
||||
{!showCompletedInfo && (<th>Vehicle Number</th>)}
|
||||
@@ -802,7 +818,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
return (<tr key={index}>
|
||||
<td className="td-index"> {index + 1}</td>
|
||||
<td>
|
||||
{ customer.customer_name}
|
||||
{ getDisplayNameWithProgramAndPaySource(customer)}
|
||||
</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_address_override || customer.customer_address }
|
||||
@@ -826,9 +842,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_note }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_special_needs }
|
||||
</td>)}
|
||||
@@ -857,7 +870,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
return (<tr key={index} >
|
||||
<td className="td-index">{stopNo}</td>
|
||||
<td>
|
||||
{ customerItem.customer_name}
|
||||
{ getDisplayNameWithProgramAndPaySource(customerItem)}
|
||||
</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customerItem.customer_address_override || customerItem.customer_address }
|
||||
@@ -881,9 +894,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{showCompletedInfo && (<td>
|
||||
{ customerItem.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customerItem.customer_note }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customerItem.customer_special_needs }
|
||||
</td>)}
|
||||
@@ -916,7 +926,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
customerItem.customers?.map((customer) => (<tr key={customer.customer_id} style={groupedRowsStyle} onClick={() => openForceEditModal(customer)}>
|
||||
<td className="td-index"></td>
|
||||
<td className="children">
|
||||
{ customer.customer_name}
|
||||
{ getDisplayNameWithProgramAndPaySource(customer)}
|
||||
</td>
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_address_override || customer.customer_address }
|
||||
@@ -940,9 +950,6 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_pickup_status === PICKUP_STATUS.SCHEDULE_ABSENT ? 'Yes' : "No" }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_note }
|
||||
</td>)}
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_special_needs }
|
||||
</td>)}
|
||||
@@ -1044,6 +1051,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{false && (
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Note
|
||||
@@ -1051,6 +1059,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
<textarea value={customerNote} onChange={(e) => {setCustomerNote(e.target.value)}}></textarea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
|
||||
@@ -416,8 +416,20 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
}
|
||||
|
||||
const addPersonnel = () => {
|
||||
const result = [].concat(customers).concat(newRouteCustomerList);
|
||||
setCustomers(result.filter((item, pos) => result.indexOf(item) === pos));
|
||||
const merged = [...customers];
|
||||
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);
|
||||
setNewRouteCustomerList([]);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router-dom";
|
||||
import { selectAllRoutes, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "../../store";
|
||||
import { CustomerService, SignatureRequestService, EventsService } from "../../services";
|
||||
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 params = useParams();
|
||||
@@ -121,6 +121,23 @@ const RouteReportWithSignature = () => {
|
||||
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 (
|
||||
<>
|
||||
<style>
|
||||
@@ -253,7 +270,7 @@ const RouteReportWithSignature = () => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td style={{ textAlign: 'center' }}>{index + 1}</td>
|
||||
<td>{customer?.customer_name}</td>
|
||||
<td>{getDisplayNameWithProgramAndPaySource(customer)}</td>
|
||||
<td>{customer?.customer_phone}</td>
|
||||
<td>{customer?.customer_address_override || customer?.customer_address}</td>
|
||||
<td></td>
|
||||
|
||||
@@ -86,8 +86,9 @@ 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: [], dischargedOnRoute: [] });
|
||||
const [checkRoutesResult, setCheckRoutesResult] = useState({ inbound: [], outbound: [], attendance: [], addressMismatch: [], customerTypeMismatch: [], customerSpecialNeedsMismatch: [], dischargedOnRoute: [] });
|
||||
const [customerTypeFixing, setCustomerTypeFixing] = useState({});
|
||||
const [customerSpecialNeedsFixing, setCustomerSpecialNeedsFixing] = useState({});
|
||||
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) => {
|
||||
if (!issue?.customerId || !issue?.dbType || !issue?.mismatchedRoutes?.length) return;
|
||||
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 inboundIssues = buildRouteConflictsByDirection(tmrInboundRoutes || []);
|
||||
const outboundIssues = buildRouteConflictsByDirection(tmrOutboundRoutes || []);
|
||||
const addressMismatchIssues = buildAddressMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const customerTypeMismatchIssues = buildCustomerTypeMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const customerSpecialNeedsMismatchIssues = buildCustomerSpecialNeedsMismatchIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
const dischargedOnRouteIssues = buildDischargedCustomerRouteIssues([...(tmrInboundRoutes || []), ...(tmrOutboundRoutes || [])]);
|
||||
let attendanceIssues = [];
|
||||
try {
|
||||
@@ -464,7 +528,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, dischargedOnRoute: dischargedOnRouteIssues });
|
||||
setCheckRoutesResult({ inbound: inboundIssues, outbound: outboundIssues, attendance: attendanceIssues, addressMismatch: addressMismatchIssues, customerTypeMismatch: customerTypeMismatchIssues, customerSpecialNeedsMismatch: customerSpecialNeedsMismatchIssues, dischargedOnRoute: dischargedOnRouteIssues });
|
||||
setShowCheckRoutesModal(true);
|
||||
};
|
||||
|
||||
@@ -749,6 +813,15 @@ const RoutesDashboard = () => {
|
||||
const goToCreateRoute = (type) => {
|
||||
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) => {
|
||||
setCurrentTab(k);
|
||||
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) => {
|
||||
return (<tr key={index}>
|
||||
<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>{end_time? moment(end_time).format('HH:mm'): ''}</td>
|
||||
<td>{type}</td>
|
||||
@@ -1968,7 +2050,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.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">
|
||||
<Check size={18} className="me-2"></Check>
|
||||
<span>No issue found.</span>
|
||||
@@ -2035,6 +2117,26 @@ const RoutesDashboard = () => {
|
||||
))}
|
||||
</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 && (
|
||||
<div className="mt-3">
|
||||
<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 { vehicleSlice, selectVehicleError } from "./../../store";
|
||||
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 { Export } from "../../shared/components";
|
||||
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 data = {
|
||||
status: 'inactive'
|
||||
@@ -310,6 +341,7 @@ const ViewVehicle = () => {
|
||||
<td className="td-index">{index + 1}</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 || '')}`)} />
|
||||
<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>
|
||||
</td>
|
||||
<td>{doc?.inspectionDate}</td>
|
||||
@@ -342,6 +374,7 @@ const ViewVehicle = () => {
|
||||
<td className="td-index">{index + 1}</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 || '')}`)} />
|
||||
<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>
|
||||
</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}`)
|
||||
}
|
||||
|
||||
const deleteVechileFile = (vehicleId, fileType, fileName) => {
|
||||
return http.post('/files/delete-physical', {
|
||||
model: 'vehicle',
|
||||
objectId: vehicleId,
|
||||
fileType,
|
||||
fileName
|
||||
});
|
||||
}
|
||||
|
||||
export const VehicleService = {
|
||||
getAll,
|
||||
getAllActiveVehicles,
|
||||
@@ -40,5 +49,6 @@ export const VehicleService = {
|
||||
getVehicle,
|
||||
convertToDate,
|
||||
uploadVechileFile,
|
||||
getAllVechileFiles
|
||||
getAllVechileFiles,
|
||||
deleteVechileFile
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user