Files
worldshine-redesign/client/src/components/trans-routes/RouteView.js
Lixian Zhou 1d38bac7fe
All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 31s
fix
2026-03-10 14:41:52 -04:00

374 lines
17 KiB
JavaScript

import React, {useState, useEffect} from "react";
import { useSelector } from "react-redux";
import { useParams, useNavigate } from "react-router-dom";
import { selectAllRoutes, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
import PersonnelSection from "./PersonnelSection";
import { AuthService, CustomerService, SignatureRequestService, EmployeeService, TransRoutesService } from "../../services";
import { ROUTE_STATUS } from "../../shared";
import moment from 'moment';
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button } from "react-bootstrap";
import { Download, Pencil } from "react-bootstrap-icons";
import RouteCustomerEditor from "./RouteCustomerEditor";
const RouteView = () => {
const params = useParams();
const allRoutes = useSelector(selectAllRoutes);
const tomorrowRoutes = useSelector(selectTomorrowAllRoutes);
const historyRoutes = useSelector(selectHistoryRoutes);
const drivers = useSelector(selectAllActiveDrivers);
const vehicles = useSelector(selectAllActiveVehicles);
const currentRoute = (allRoutes.find(item => item.id === params.id)) || (tomorrowRoutes.find(item => item.id === params.id)) || (historyRoutes.find(item => item.id === params.id));
const currentVehicle = vehicles.find(item => item.id === currentRoute?.vehicle );
const currentDriver = drivers.find(item => item.id === currentRoute?.driver);
const [fallbackDriver, setFallbackDriver] = useState(undefined);
const [showVehicleDetails, setShowVehicleDetails] = useState(false);
const [signature, setSignature] = useState(undefined);
const [signatureRequest, setSignatureRequest] = useState(undefined);
const [routeStatusValue, setRouteStatusValue] = useState('');
const [isSavingRouteStatus, setIsSavingRouteStatus] = useState(false);
const [latestRouteForStatus, setLatestRouteForStatus] = useState(undefined);
const [routeSnapshot, setRouteSnapshot] = useState(undefined);
const [customerMetaById, setCustomerMetaById] = useState(new Map());
const paramsQuery = new URLSearchParams(window.location.search);
const scheduleDate = paramsQuery.get('dateSchedule');
const navigate = useNavigate();
const resolvedDriverId = currentDriver?.id || fallbackDriver?.id || currentRoute?.driver;
const resolvedDriverName = currentDriver?.name || fallbackDriver?.name || '';
const routeStatusOptions = [
{ value: '', label: 'Not Started' },
{ value: ROUTE_STATUS.ENROUTE, label: 'En Route' },
{ value: ROUTE_STATUS.ENROUTE_TO_CENTER, label: 'En Route To Center' },
{ value: ROUTE_STATUS.DROPPED_OFF_ALL, label: 'Dropped Off All' },
{ value: ROUTE_STATUS.SIGN_OFF, label: 'Signed Off' },
{ value: ROUTE_STATUS.UNEXPECTED_ABSENT, label: 'Unexpected Absent' },
];
const mergeRouteCustomerMeta = (route) => {
if (!route || !Array.isArray(route?.route_customer_list) || customerMetaById.size === 0) {
return route;
}
return Object.assign({}, route, {
route_customer_list: route.route_customer_list.map((customerInRoute) => {
const customerMeta = customerMetaById.get(customerInRoute?.customer_id);
if (!customerMeta) return customerInRoute;
return Object.assign({}, customerInRoute, {
customer_program_type: customerMeta.program_type || '',
customer_pay_source: customerMeta.pay_source || ''
});
})
});
};
const routeForStatusView = mergeRouteCustomerMeta(latestRouteForStatus || routeSnapshot || currentRoute);
const routeForAssignmentView = routeSnapshot || currentRoute;
const closeModal = () => {
setShowVehicleDetails(false);
}
const openModal = () => {
setShowVehicleDetails(true);
}
const getRelatedOutboundRoutesForThisView = () => {
if (allRoutes.find(item => item.id === params.id)) {
return allRoutes.filter(item => item.type==='outbound');
}
if (tomorrowRoutes.find(item => item.id === params.id)) {
return tomorrowRoutes.filter(item => item.type==='outbound');
}
if (historyRoutes.find(item => item.id === params.id)) {
return historyRoutes.filter(item => item.type==='outbound');
}
}
const directToDashboad = () => {
navigate(`/trans-routes/dashboard?dateSchedule=${moment(currentRoute?.schedule_date).format('YYYY-MM-DD')}`);
}
const edit = (editSection) => {
if (scheduleDate) {
navigate(`/trans-routes/edit/${currentRoute?.id}?dateSchedule=${scheduleDate}&editSection=${editSection}`)
} else {
navigate(`/trans-routes/edit/${currentRoute?.id}?editSection=${editSection}`)
}
}
const deleteFile = () => {
if (signature) {
const dateArr = moment(currentRoute?.schedule_date)?.format('MM/DD/YYYY')?.split('/') || [];
CustomerService.deleteFile({name: `${currentRoute?.id}_${currentRoute?.driver}_${dateArr[0]}_${dateArr[1]}`}).then(data => {
setSignature(undefined);
})
}
}
const generateSignatureRequest = () => {
if (!resolvedDriverId) {
window.alert('Driver is not assigned for this route.');
return;
}
SignatureRequestService.createNewSignatureRequest({
driver_id: resolvedDriverId,
driver_name: resolvedDriverName,
route_id: currentRoute?.id,
route_date: currentRoute?.schedule_date,
route_name: currentRoute?.name,
status: 'active',
}).then((data) => {
setSignatureRequest(data.data);
})
}
const saveRouteStatus = async () => {
if (!currentRoute?.id || isSavingRouteStatus) return;
try {
setIsSavingRouteStatus(true);
const nextStatus = routeStatusValue ? [routeStatusValue] : [];
await TransRoutesService.updateRoute(currentRoute.id, { status: nextStatus });
await refreshRouteStatusData();
window.alert('Route status updated successfully.');
} catch (error) {
console.error('Error updating route status:', error);
window.alert('Failed to update route status.');
} finally {
setIsSavingRouteStatus(false);
}
};
const refreshRouteStatusData = async () => {
if (!currentRoute?.id) return;
try {
const response = await TransRoutesService.getRoute(currentRoute.id);
if (response?.data) {
setLatestRouteForStatus(response.data);
}
} catch (error) {
console.error('Error refreshing route status data:', error);
}
};
useEffect(() => {
const currentStatus = Array.isArray(currentRoute?.status) ? currentRoute.status[0] : '';
setRouteStatusValue(currentStatus || '');
}, [currentRoute?.id, currentRoute?.status]);
useEffect(() => {
setLatestRouteForStatus(undefined);
}, [currentRoute?.id]);
useEffect(() => {
const routeId = currentRoute?.id || params?.id;
if (!routeId) return;
TransRoutesService.getRoute(routeId)
.then((response) => {
if (response?.data) {
setRouteSnapshot(response.data);
}
})
.catch(() => {
setRouteSnapshot(undefined);
});
}, [currentRoute?.id, params?.id]);
useEffect(() => {
CustomerService.getAllCustomers()
.then((res) => {
const nextMap = new Map();
(res?.data || []).forEach((customer) => {
nextMap.set(customer?.id, {
program_type: customer?.program_type || '',
pay_source: customer?.pay_source || ''
});
});
setCustomerMetaById(nextMap);
})
.catch(() => {
setCustomerMetaById(new Map());
});
}, []);
useEffect(() => {
if (!currentRoute?.driver || currentDriver?.id) {
setFallbackDriver(undefined);
return;
}
EmployeeService.getEmployee(currentRoute?.driver)
.then((data) => {
setFallbackDriver(data?.data);
})
.catch(() => {
setFallbackDriver(undefined);
});
}, [currentRoute?.driver, currentDriver?.id]);
useEffect(() => {
const dateArr = moment(currentRoute?.schedule_date)?.format('MM/DD/YYYY')?.split('/') || [];
CustomerService.getAvatar(`${currentRoute?.id}_${currentRoute?.driver}_${dateArr[0]}_${dateArr[1]}`).then(data => {
setSignature(data.data);
});
SignatureRequestService.getAllSignatureRequests({driver_id: resolvedDriverId, route_id: currentRoute?.id, route_date: currentRoute?.schedule_date}).then((data) => {
if (data?.data?.length > 0) {
setSignatureRequest(data?.data[0]);
}
})
}, [currentRoute, resolvedDriverId]);
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item href="/trans-routes/dashboard">
Transportation Routes
</Breadcrumb.Item>
<Breadcrumb.Item active>
View Route
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
View Route Information <button className="btn btn-link btn-sm" onClick={() => {directToDashboad()}}>Back</button>
</h4>
</div>
</div>
<div className="app-main-content-list-container form-page">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
<Tab eventKey="routeOverview" title="Route Information">
<h6 className="text-primary">Route Details <button className="btn btn-sm btn-primary" onClick={() => edit('info')}><Pencil size={16} className="me-2"></Pencil>Edit </button></h6>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Route Name</div>
<div className="field-value">{currentRoute?.name}</div>
</div>
<div className="field-body">
<div className="field-label">Vehicle</div>
<div className="field-value">{currentVehicle?.vehicle_number}</div>
</div>
<div className="field-body">
<div className="field-label">Driver</div>
<div className="field-value">{resolvedDriverName || 'Not assigned'}</div>
</div>
<div className="field-body">
<div className="field-label">Route Type</div>
<div className="field-value">{currentRoute?.type}</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Route Start Time</div>
<div className="field-value">{currentRoute?.start_time && (new Date(currentRoute?.start_time))?.toLocaleTimeString()}</div>
</div>
<div className="field-body">
<div className="field-label">Route End Time</div>
<div className="field-value">{currentRoute?.end_time && (new Date(currentRoute?.end_time))?.toLocaleTimeString()}</div>
</div>
{currentRoute?.type === 'outbound' &&<div className="field-body">
<div className="field-label">Leave Center Time</div>
<div className="field-value">{currentRoute?.start_time && (new Date(currentRoute?.start_time))?.toLocaleTimeString()}</div>
</div>}
{currentRoute?.type === 'outbound' &&<div className="field-body">
<div className="field-label">Estimated Start Time</div>
<div className="field-value">{currentRoute?.estimated_start_time && (new Date(currentRoute?.estimated_start_time))?.toLocaleTimeString()}</div>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Start Mileage</div>
<div className="field-value">{ currentRoute?.start_mileage}</div>
</div>
<div className="field-body">
<div className="field-label">End Mileage</div>
<div className="field-value">{ currentRoute?.end_mileage}</div>
</div>
</div>
<div className="app-main-content-fields-section">
{signature &&<div className="field-body">
<div className="field-label">Driver Signature</div>
<div className="field-value">
{signature && <img width="100px" src={`data:image/jpg;base64, ${signature}`}/>}
</div>
</div>}
{!signature && !signatureRequest && <div className="field-body">
<div className="field-label">Signature Request</div>
<div className="field-value"><button className="btn btn-sm btn-primary" onClick={() => generateSignatureRequest()}>Generate Signature Link For Driver</button></div>
</div>}
{!signature && signatureRequest && <div className="alert alert-success fade show mb-2 mt-2" role="alert">
<div>Please send this to the driver to get signature:</div>
<div>{`${window.location.origin}/signature/${signatureRequest?.id}`}</div>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Checklist</div>
<div className="field-value">
{currentRoute && currentRoute?.checklist_result?.map(item => <div>{`${item?.item}: ${item?.result ? 'Yes': "No"}`}</div>)}
{currentRoute && currentRoute?.checklist_result.length === 0 && <>No Checklist found</>}</div>
</div>
</div>
<RouteCustomerEditor currentRoute={routeForAssignmentView} viewMode={true} editFun={edit}></RouteCustomerEditor>
</Tab>
<Tab eventKey="routeStatus" title="Route Status">
<div className="list row">
<div className="col-md-12 mb-3">
<div className="app-main-content-fields-section">
<div className="field-body">
<div className="field-label">Route Status</div>
<div className="field-value d-flex align-items-center gap-2">
<select
className="form-select"
style={{ width: '260px' }}
value={routeStatusValue}
onChange={(e) => setRouteStatusValue(e.target.value)}
disabled={isSavingRouteStatus}
>
{routeStatusOptions.map((option) => (
<option key={option.value || 'not-started'} value={option.value}>
{option.label}
</option>
))}
</select>
<button
className="btn btn-primary btn-sm"
onClick={saveRouteStatus}
disabled={isSavingRouteStatus}
>
{isSavingRouteStatus ? 'Saving...' : 'Save Status'}
</button>
</div>
</div>
</div>
</div>
<div className="col-md-12 mb-4">
{routeForStatusView && <PersonnelSection transRoutes={[routeForStatusView]} showCompletedInfo={true} showGroupInfo={true} isInbound={routeForStatusView?.type === 'inbound' } allowForceEdit={AuthService.canViewRoutes()} sectionName="Personnel Status (click on each user to edit)" relatedOutbound={getRelatedOutboundRoutesForThisView()} vehicle={currentVehicle} driverName={resolvedDriverName} deleteFile={deleteFile} onRouteUpdated={refreshRouteStatusData}/>}
</div>
</div>
</Tab>
</Tabs>
{/* <div className="list-func-panel">
<button className="btn btn-primary"><Download size={16} className="me-2"></Download>Export</button>
</div> */}
</div>
<Modal show={showVehicleDetails} onHide={() => closeModal()}>
<Modal.Header closeButton>
<Modal.Title>Vehicle Info</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
<div className="mb-2">Vehicle Number: {currentVehicle?.vehicle_number}</div>
<div className="mb-2">Tag: {currentVehicle?.tag}</div>
<div className="mb-2">EzPass: {currentVehicle?.ezpass}</div>
<div className="mb-2">GPS: {currentVehicle?.gps_tag}</div>
<div className="mb-2">Capacity: {currentVehicle?.capacity}</div>
<div className="mb-2">Status: {currentVehicle?.status}</div>
<div className="mb-2">Mileage: {currentVehicle?.mileage}</div>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => closeModal()}>
Close
</Button>
</Modal.Footer>
</Modal>
</div>
</>
);
};
export default RouteView;