fix
This commit is contained in:
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.46cc12be.css",
|
||||
"main.js": "/static/js/main.59349b38.js",
|
||||
"main.js": "/static/js/main.3066488d.js",
|
||||
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
||||
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
||||
"index.html": "/index.html",
|
||||
"main.46cc12be.css.map": "/static/css/main.46cc12be.css.map",
|
||||
"main.59349b38.js.map": "/static/js/main.59349b38.js.map",
|
||||
"main.3066488d.js.map": "/static/js/main.3066488d.js.map",
|
||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.46cc12be.css",
|
||||
"static/js/main.59349b38.js"
|
||||
"static/js/main.3066488d.js"
|
||||
]
|
||||
}
|
||||
@@ -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.59349b38.js"></script><link href="/static/css/main.46cc12be.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.3066488d.js"></script><link href="/static/css/main.46cc12be.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
3
app/views/static/js/main.3066488d.js
Normal file
3
app/views/static/js/main.3066488d.js
Normal file
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
BIN
client/.DS_Store
vendored
Binary file not shown.
@@ -1,9 +1,11 @@
|
||||
import React, {useState, useEffect} from "react";
|
||||
import React, {useState, useEffect, useRef} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthService, DailyRoutesTemplateService } from "../../services";
|
||||
import { Breadcrumb, Modal, Button, Tabs, Tab } from "react-bootstrap";
|
||||
import { AuthService, DailyRoutesTemplateService, TransRoutesService } from "../../services";
|
||||
import { Breadcrumb, Modal, Button, Tabs, Tab, Dropdown, ProgressBar } from "react-bootstrap";
|
||||
import { PencilSquare } from "react-bootstrap-icons";
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import DatePicker from "react-datepicker";
|
||||
import moment from "moment";
|
||||
|
||||
const DailyTemplatesList = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -18,6 +20,11 @@ const DailyTemplatesList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [templateToDelete, setTemplateToDelete] = useState(null);
|
||||
const [showCreateFromDateDropdown, setShowCreateFromDateDropdown] = useState(false);
|
||||
const [createFromDate, setCreateFromDate] = useState(new Date());
|
||||
const [isCreatingTemplate, setIsCreatingTemplate] = useState(false);
|
||||
const [createTemplateProgress, setCreateTemplateProgress] = useState(0);
|
||||
const createTemplateProgressTimerRef = useRef(null);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -160,6 +167,129 @@ const DailyTemplatesList = () => {
|
||||
return selectedItems?.length === currentItems?.length && selectedItems?.length > 0;
|
||||
}
|
||||
|
||||
const getDateString = (date) => {
|
||||
return ((date.getMonth() > 8) ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '/' + ((date.getDate() > 9) ? date.getDate() : ('0' + date.getDate())) + '/' + date.getFullYear()
|
||||
}
|
||||
|
||||
const startCreateTemplateProgress = () => {
|
||||
if (createTemplateProgressTimerRef.current) {
|
||||
clearInterval(createTemplateProgressTimerRef.current);
|
||||
}
|
||||
setCreateTemplateProgress(10);
|
||||
setIsCreatingTemplate(true);
|
||||
createTemplateProgressTimerRef.current = setInterval(() => {
|
||||
setCreateTemplateProgress((prev) => (prev >= 90 ? 90 : prev + 5));
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const finishCreateTemplateProgress = () => {
|
||||
if (createTemplateProgressTimerRef.current) {
|
||||
clearInterval(createTemplateProgressTimerRef.current);
|
||||
createTemplateProgressTimerRef.current = null;
|
||||
}
|
||||
setCreateTemplateProgress(100);
|
||||
setTimeout(() => {
|
||||
setIsCreatingTemplate(false);
|
||||
setCreateTemplateProgress(0);
|
||||
}, 350);
|
||||
};
|
||||
|
||||
const createTemplateFromSelectedDate = async () => {
|
||||
if (!createFromDate) {
|
||||
window.alert('Please select a date.');
|
||||
return;
|
||||
}
|
||||
|
||||
startCreateTemplateProgress();
|
||||
try {
|
||||
const targetDate = getDateString(createFromDate);
|
||||
const { data: routes } = await TransRoutesService.getAll(targetDate);
|
||||
if (!routes || routes.length === 0) {
|
||||
window.alert(`No schedule routes found on ${targetDate}.`);
|
||||
finishCreateTemplateProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanedRoutes = routes.map(route => {
|
||||
const cleanedCustomerList = route.route_customer_list?.map(customer => ({
|
||||
...customer,
|
||||
customer_enter_center_time: null,
|
||||
customer_leave_center_time: null,
|
||||
customer_pickup_time: null,
|
||||
customer_dropoff_time: null,
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
name: route.name,
|
||||
schedule_date: route.schedule_date,
|
||||
vehicle: route.vehicle,
|
||||
status: [],
|
||||
driver: route.driver,
|
||||
type: route.type,
|
||||
start_mileage: route.start_mileage,
|
||||
end_mileage: route.end_mileage,
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
estimated_start_time: null,
|
||||
route_customer_list: cleanedCustomerList,
|
||||
checklist_result: route.checklist_result || []
|
||||
};
|
||||
});
|
||||
|
||||
const currentUser = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
|
||||
const payload = {
|
||||
name: `Template ${moment(createFromDate).format('MM/DD/YYYY')}`,
|
||||
template_date: targetDate,
|
||||
routes: cleanedRoutes,
|
||||
create_by: currentUser
|
||||
};
|
||||
|
||||
await DailyRoutesTemplateService.createNewDailyRoutesTemplate(payload);
|
||||
await fetchTemplates();
|
||||
setShowCreateFromDateDropdown(false);
|
||||
window.alert(`Template created from ${targetDate}.`);
|
||||
} catch (error) {
|
||||
console.error('Failed to create template from date:', error);
|
||||
window.alert(error?.message || 'Failed to create template from selected date.');
|
||||
} finally {
|
||||
finishCreateTemplateProgress();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (createTemplateProgressTimerRef.current) {
|
||||
clearInterval(createTemplateProgressTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const customMenuCreateFromDate = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Select Date</div>
|
||||
<DatePicker selected={createFromDate} onChange={(v) => setCreateFromDate(v)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => setShowCreateFromDateDropdown(false)}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={createTemplateFromSelectedDate}> Create </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const table = (
|
||||
<div className="list row mb-4">
|
||||
<div className="col-md-12">
|
||||
@@ -233,6 +363,12 @@ const DailyTemplatesList = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCreatingTemplate && <div className="spinner-overlay" style={{ flexDirection: 'column', gap: '12px', zIndex: 1060 }}>
|
||||
<div style={{ width: '360px', maxWidth: '80vw', textAlign: 'center' }}>
|
||||
<div style={{ marginBottom: '8px', color: '#333' }}>Creating template from date...</div>
|
||||
<ProgressBar now={createTemplateProgress} animated label={`${Math.round(createTemplateProgress)}%`} />
|
||||
</div>
|
||||
</div>}
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
@@ -255,6 +391,19 @@ const DailyTemplatesList = () => {
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
<Dropdown
|
||||
key={'create-template-from-date'}
|
||||
id="create-template-from-date"
|
||||
className="me-2"
|
||||
show={showCreateFromDateDropdown}
|
||||
onToggle={() => setShowCreateFromDateDropdown(!showCreateFromDateDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="outline-secondary" disabled={isCreatingTemplate}>
|
||||
Create template from date
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuCreateFromDate}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -877,6 +877,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
</td>}
|
||||
</tr>);
|
||||
} else {
|
||||
const groupedChildrenRowStyle = { backgroundColor: '#fff' };
|
||||
return (<React.Fragment key={index}>
|
||||
<tr className="group">
|
||||
<td className="td-index"></td>
|
||||
@@ -887,7 +888,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
</td>}
|
||||
</tr>
|
||||
{
|
||||
customerItem.customers?.map((customer) => (<tr key={customer.customer_id} onClick={() => openForceEditModal(customer)}>
|
||||
customerItem.customers?.map((customer) => (<tr key={customer.customer_id} style={groupedChildrenRowStyle} onClick={() => openForceEditModal(customer)}>
|
||||
<td className="td-index">{customer.index + 1}</td>
|
||||
<td className="children">
|
||||
{ customer.customer_name}
|
||||
@@ -898,8 +899,8 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
||||
{showCompletedInfo && (<td>
|
||||
{ customer.customer_phone }
|
||||
</td>)}
|
||||
<td> <div className={`${getTextAndClassName(customerItem).className} status-tag`}>
|
||||
{ getTextAndClassName(customerItem).text } </div>
|
||||
<td> <div className={`${getTextAndClassName(customer).className} status-tag`}>
|
||||
{ getTextAndClassName(customer).text } </div>
|
||||
</td>
|
||||
<td>
|
||||
{ CUSTOMER_TYPE_TEXT[customer.customer_type]}
|
||||
|
||||
@@ -146,6 +146,41 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
return addresses.filter(address => (address || '').trim() !== '');
|
||||
};
|
||||
|
||||
const getCustomerSearchAddresses = (customer) => {
|
||||
if (!customer) return [];
|
||||
const addresses = [
|
||||
formatStructuredAddress(customer.address_line_1, customer.address_line_2, customer.city, customer.state, customer.zip_code),
|
||||
formatStructuredAddress(customer.address2_line_1, customer.address2_line_2, customer.city2, customer.state2, customer.zip_code2),
|
||||
formatStructuredAddress(customer.address3_line_1, customer.address3_line_2, customer.city3, customer.state3, customer.zip_code3),
|
||||
formatStructuredAddress(customer.address4_line_1, customer.address4_line_2, customer.city4, customer.state4, customer.zip_code4),
|
||||
];
|
||||
return addresses.filter(address => (address || '').trim() !== '');
|
||||
};
|
||||
|
||||
const getCustomerSearchText = (customer) => {
|
||||
const allAddressText = getCustomerSearchAddresses(customer).join(' ');
|
||||
return [
|
||||
customer?.name,
|
||||
customer?.id,
|
||||
customer?.apartment,
|
||||
allAddressText,
|
||||
// Keep legacy fields searchable for older records.
|
||||
customer?.address1,
|
||||
customer?.address2,
|
||||
customer?.address3,
|
||||
customer?.address4,
|
||||
customer?.address5,
|
||||
]
|
||||
.map(item => (item || '').toString().toLowerCase())
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const matchesCustomerSearch = (customer, keyword) => {
|
||||
const normalizedKeyword = (keyword || '').toLowerCase().trim();
|
||||
if (!normalizedKeyword) return true;
|
||||
return getCustomerSearchText(customer).includes(normalizedKeyword);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch items from another resources.
|
||||
const endOffset = itemOffset + itemsPerPage;
|
||||
@@ -154,16 +189,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
|
||||
const filteredCustomers = availableCustomers
|
||||
?.filter(customer => (lastNameFilter && (customer.lastname?.toLowerCase().indexOf(lastNameFilter) === 0)) || !lastNameFilter)
|
||||
?.filter((customer) =>
|
||||
customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) ||
|
||||
customer.id.toLowerCase().includes(customerFilter?.toLowerCase()) ||
|
||||
customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) ||
|
||||
customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) ||
|
||||
customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) ||
|
||||
customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) ||
|
||||
customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) ||
|
||||
customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase())
|
||||
) || [];
|
||||
?.filter((customer) => matchesCustomerSearch(customer, customerFilter)) || [];
|
||||
if (hasActiveFilters) {
|
||||
setCurrentItems(filteredCustomers.slice(itemOffset, endOffset));
|
||||
setPageCount(Math.ceil(filteredCustomers.length / itemsPerPage));
|
||||
@@ -179,7 +205,8 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
const handlePageClick = (event) => {
|
||||
const assignedIds = getAssignedCustomerIds();
|
||||
const availableCustomers = customerOptions?.filter(customer => !assignedIds.has(customer.id));
|
||||
const newOffset = (event.selected * itemsPerPage) % availableCustomers?.filter((customer) => customer.name?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.id?.toLowerCase().includes(customerFilter?.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).length;
|
||||
const matchedCount = availableCustomers?.filter((customer) => matchesCustomerSearch(customer, customerFilter)).length || 0;
|
||||
const newOffset = matchedCount > 0 ? (event.selected * itemsPerPage) % matchedCount : 0;
|
||||
console.log(
|
||||
`User requested page number ${event.selected}, which is offset ${newOffset}`
|
||||
);
|
||||
@@ -551,7 +578,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
||||
|
||||
const ItemsGroup = ({ currentItems }) => {
|
||||
const assignedIds = getAssignedCustomerIds();
|
||||
return currentItems?.filter(customer => !assignedIds.has(customer.id)).filter((customer) => customer.name.toLowerCase().includes(customerFilter.toLowerCase()) || customer.id.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address1?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address2?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address3?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address4?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.address5?.toLowerCase().includes(customerFilter.toLowerCase()) || customer.apartment?.toLowerCase().includes(customerFilter.toLocaleLowerCase()) ).map(
|
||||
return currentItems?.filter(customer => !assignedIds.has(customer.id)).filter((customer) => matchesCustomerSearch(customer, customerFilter)).map(
|
||||
(customer) => {
|
||||
const addressOptions = getCustomerAddressOptions(customer);
|
||||
const customerDisplayName = customer?.name || '';
|
||||
|
||||
@@ -76,11 +76,31 @@ const RouteReportWithSignature = () => {
|
||||
}
|
||||
});
|
||||
}, [currentRoute]);
|
||||
// Get checklist result from route
|
||||
const checklistResult = currentRoute?.checklist_result || [];
|
||||
const normalizeChecklistText = (value = '') =>
|
||||
String(value)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '');
|
||||
|
||||
// Include current route checklist, and also paired same-day route checklist.
|
||||
// This keeps inspection checks visible whether results were saved on inbound or outbound.
|
||||
const pairedRoute = getRelatedInboundOutboundRoutesForThisView(currentRoute?.type)
|
||||
?.find((route) =>
|
||||
route?.schedule_date === currentRoute?.schedule_date &&
|
||||
route?.name?.toLowerCase()?.replaceAll(' ', '') === currentRoute?.name?.toLowerCase()?.replaceAll(' ', '')
|
||||
);
|
||||
const checklistResult = [
|
||||
...(currentRoute?.checklist_result || []),
|
||||
...(pairedRoute?.checklist_result || [])
|
||||
];
|
||||
const getChecklistStatus = (itemKey) => {
|
||||
const item = checklistResult.find(c => c.key === itemKey || c.label?.toLowerCase().includes(itemKey.toLowerCase()));
|
||||
return item?.checked ? '✓' : '';
|
||||
const normalizedItemKey = normalizeChecklistText(itemKey);
|
||||
const item = checklistResult.find((c) => {
|
||||
const candidates = [c?.key, c?.label, c?.item]
|
||||
.map(normalizeChecklistText)
|
||||
.filter(Boolean);
|
||||
return candidates.some((candidate) => candidate.includes(normalizedItemKey));
|
||||
});
|
||||
return (item?.result === true || item?.checked === true) ? '✓' : '';
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,8 @@ 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 } from "../../services";
|
||||
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";
|
||||
@@ -23,12 +24,22 @@ const RouteView = () => {
|
||||
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 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 closeModal = () => {
|
||||
setShowVehicleDetails(false);
|
||||
}
|
||||
@@ -80,6 +91,29 @@ const RouteView = () => {
|
||||
setSignatureRequest(data.data);
|
||||
})
|
||||
}
|
||||
const saveRouteStatus = async () => {
|
||||
if (!currentRoute?.id || isSavingRouteStatus) return;
|
||||
try {
|
||||
setIsSavingRouteStatus(true);
|
||||
const nextStatus = routeStatusValue ? [routeStatusValue] : [];
|
||||
await TransRoutesService.updateRoute(currentRoute.id, {
|
||||
...currentRoute,
|
||||
status: nextStatus
|
||||
});
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const currentStatus = Array.isArray(currentRoute?.status) ? currentRoute.status[0] : '';
|
||||
setRouteStatusValue(currentStatus || '');
|
||||
}, [currentRoute?.id, currentRoute?.status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentRoute?.driver || currentDriver?.id) {
|
||||
setFallbackDriver(undefined);
|
||||
@@ -204,6 +238,35 @@ const RouteView = () => {
|
||||
</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">
|
||||
{currentRoute && <PersonnelSection transRoutes={[currentRoute]} showCompletedInfo={true} showGroupInfo={true} isInbound={currentRoute?.type === 'inbound' } allowForceEdit={AuthService.canViewRoutes()} sectionName="Personnel Status (click on each user to edit)" relatedOutbound={getRelatedOutboundRoutesForThisView()} vehicle={currentVehicle} driverName={resolvedDriverName} deleteFile={deleteFile}/>}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PERSONAL_ROUTE_STATUS, ROUTE_STATUS, reportRootUrl, CUSTOMER_TYPE_TEXT,
|
||||
import moment from 'moment';
|
||||
import DatePicker from "react-datepicker";
|
||||
import { CalendarWeek, ClockHistory, Copy, Download, Eraser, Plus, Clock, Send, Filter, CalendarCheck, Check } from "react-bootstrap-icons";
|
||||
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button } from "react-bootstrap";
|
||||
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button, ProgressBar } from "react-bootstrap";
|
||||
import RouteCustomerTable from "./RouteCustomerTable";
|
||||
|
||||
|
||||
@@ -82,6 +82,14 @@ const RoutesDashboard = () => {
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState('');
|
||||
const [applyingTemplate, setApplyingTemplate] = useState(false);
|
||||
const [pageLoading, setPageLoading] = useState(false);
|
||||
const [showImportDateDropdown, setShowImportDateDropdown] = useState(false);
|
||||
const [showImportTemplateDropdown, setShowImportTemplateDropdown] = useState(false);
|
||||
const [importSourceDate, setImportSourceDate] = useState(undefined);
|
||||
const [importTemplateId, setImportTemplateId] = useState('');
|
||||
const [isScheduleImporting, setIsScheduleImporting] = useState(false);
|
||||
const [scheduleImportProgress, setScheduleImportProgress] = useState(0);
|
||||
const [scheduleImportLabel, setScheduleImportLabel] = useState('');
|
||||
const scheduleImportProgressTimerRef = useRef(null);
|
||||
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@@ -244,6 +252,156 @@ const RoutesDashboard = () => {
|
||||
const now = new Date();
|
||||
const yesterday = new Date();
|
||||
const tomorrow = new Date();
|
||||
const isFutureScheduleSelected = moment(dateSelected).startOf('day').isAfter(moment().startOf('day'));
|
||||
|
||||
const startScheduleImportProgress = (label) => {
|
||||
if (scheduleImportProgressTimerRef.current) {
|
||||
clearInterval(scheduleImportProgressTimerRef.current);
|
||||
}
|
||||
setScheduleImportLabel(label);
|
||||
setScheduleImportProgress(10);
|
||||
setIsScheduleImporting(true);
|
||||
scheduleImportProgressTimerRef.current = setInterval(() => {
|
||||
setScheduleImportProgress((prev) => (prev >= 90 ? 90 : prev + 5));
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const finishScheduleImportProgress = () => {
|
||||
if (scheduleImportProgressTimerRef.current) {
|
||||
clearInterval(scheduleImportProgressTimerRef.current);
|
||||
scheduleImportProgressTimerRef.current = null;
|
||||
}
|
||||
setScheduleImportProgress(100);
|
||||
setTimeout(() => {
|
||||
setIsScheduleImporting(false);
|
||||
setScheduleImportProgress(0);
|
||||
setScheduleImportLabel('');
|
||||
}, 400);
|
||||
};
|
||||
|
||||
const refreshRoutesForCurrentDate = () => {
|
||||
const selectedDateString = getDateString(dateSelected);
|
||||
if (selectedDateString === getDateString(new Date())) {
|
||||
dispatch(fetchAllRoutes());
|
||||
} else if (dateSelected > new Date()) {
|
||||
dispatch(fetchAllTomorrowRoutes({dateText: moment(dateSelected).format('MM/DD/YYYY')}));
|
||||
} else {
|
||||
dispatch(fetchAllHistoryRoutes({dateText: getDateString(dateSelected)}));
|
||||
}
|
||||
};
|
||||
|
||||
const buildImportedRouteData = (draftRoute, targetDateText) => {
|
||||
return {
|
||||
name: draftRoute?.name,
|
||||
vehicle: draftRoute?.vehicle,
|
||||
driver: draftRoute?.driver,
|
||||
type: draftRoute?.type,
|
||||
schedule_date: targetDateText,
|
||||
start_mileage: draftRoute?.start_mileage || vehicles.find((vehicle) => vehicle.id === draftRoute?.vehicle)?.mileage,
|
||||
end_mileage: draftRoute?.end_mileage,
|
||||
status: [],
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
estimated_start_time: null,
|
||||
checklist_result: draftRoute?.checklist_result || [],
|
||||
route_customer_list: (draftRoute.route_customer_list || []).map((customer) => ({
|
||||
...customer,
|
||||
customer_enter_center_time: null,
|
||||
customer_leave_center_time: null,
|
||||
customer_pickup_time: null,
|
||||
customer_dropoff_time: null,
|
||||
customer_estimated_pickup_time: null,
|
||||
customer_estimated_dropoff_time: null,
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.NO_STATUS,
|
||||
customer_address_override: null
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
const importFromDate = async () => {
|
||||
if (!importSourceDate) {
|
||||
window.alert('Please select a source date.');
|
||||
return;
|
||||
}
|
||||
startScheduleImportProgress('Importing schedule from selected date...');
|
||||
try {
|
||||
const sourceDateText = getDateString(importSourceDate);
|
||||
const targetDateText = getDateString(dateSelected);
|
||||
const [{ data: sourceRoutes }, { data: existingRoutes }] = await Promise.all([
|
||||
TransRoutesService.getAll(sourceDateText),
|
||||
TransRoutesService.getAll(targetDateText)
|
||||
]);
|
||||
|
||||
let skippedCount = 0;
|
||||
const createPromises = [];
|
||||
for (const draftRoute of (sourceRoutes || [])) {
|
||||
const existed = (existingRoutes || []).find((r) => r.name === draftRoute.name && r.type === draftRoute.type);
|
||||
if (existed) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
const routeData = buildImportedRouteData(draftRoute, targetDateText);
|
||||
createPromises.push(TransRoutesService.createNewRoute(routeData));
|
||||
}
|
||||
await Promise.all(createPromises);
|
||||
refreshRoutesForCurrentDate();
|
||||
setShowImportDateDropdown(false);
|
||||
setSuccessMessage(`Imported ${createPromises.length} route(s) from ${sourceDateText}.`);
|
||||
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||
if (skippedCount > 0) {
|
||||
window.alert(`${skippedCount} route(s) already existed on target date and were skipped.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to import routes from date:', error);
|
||||
setErrorMessage(error?.message || 'Failed to import routes from date');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
} finally {
|
||||
finishScheduleImportProgress();
|
||||
}
|
||||
};
|
||||
|
||||
const importFromTemplate = async () => {
|
||||
if (!importTemplateId) {
|
||||
window.alert('Please select a template.');
|
||||
return;
|
||||
}
|
||||
const selectedTemplate = templates.find((t) => t.id === importTemplateId);
|
||||
if (!selectedTemplate) {
|
||||
window.alert('Template not found.');
|
||||
return;
|
||||
}
|
||||
startScheduleImportProgress('Copying routes from template...');
|
||||
try {
|
||||
const targetDateText = getDateString(dateSelected);
|
||||
const { data: existingRoutes } = await TransRoutesService.getAll(targetDateText);
|
||||
let skippedCount = 0;
|
||||
const createPromises = [];
|
||||
for (const route of (selectedTemplate.routes || [])) {
|
||||
const existed = (existingRoutes || []).find((r) => r.name === route.name && r.type === route.type);
|
||||
if (existed) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
const routeData = buildImportedRouteData(route, targetDateText);
|
||||
createPromises.push(TransRoutesService.createNewRoute(routeData));
|
||||
}
|
||||
await Promise.all(createPromises);
|
||||
refreshRoutesForCurrentDate();
|
||||
setShowImportTemplateDropdown(false);
|
||||
setImportTemplateId('');
|
||||
setSuccessMessage(`Imported ${createPromises.length} route(s) from template "${selectedTemplate.name}".`);
|
||||
setTimeout(() => setSuccessMessage(undefined), 5000);
|
||||
if (skippedCount > 0) {
|
||||
window.alert(`${skippedCount} route(s) already existed on target date and were skipped.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to import routes from template:', error);
|
||||
setErrorMessage(error?.message || 'Failed to import routes from template');
|
||||
setTimeout(() => setErrorMessage(undefined), 5000);
|
||||
} finally {
|
||||
finishScheduleImportProgress();
|
||||
}
|
||||
};
|
||||
yesterday.setDate(now.getDate() - 1);
|
||||
tomorrow.setDate(now.getDate() + 1);
|
||||
|
||||
@@ -883,6 +1041,74 @@ const RoutesDashboard = () => {
|
||||
},
|
||||
);
|
||||
|
||||
const customMenuImportFromDate = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Select Source Date (Past 14 Days)</div>
|
||||
<DatePicker
|
||||
selected={importSourceDate}
|
||||
onChange={(v) => setImportSourceDate(v)}
|
||||
minDate={moment().subtract(14, 'days').toDate()}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => setShowImportDateDropdown(false)}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={importFromDate}> Import </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const customMenuImportFromTemplate = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={className}
|
||||
aria-labelledby={labeledBy}
|
||||
>
|
||||
<div className="app-main-content-fields-section margin-sm dropdown-container">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Select Saved Template</div>
|
||||
<select
|
||||
className="form-select"
|
||||
value={importTemplateId}
|
||||
onChange={(e) => setImportTemplateId(e.target.value)}
|
||||
>
|
||||
<option value="">Choose a template</option>
|
||||
{templates.map((template) => (
|
||||
<option key={template.id} value={template.id}>
|
||||
{template.name} - {template.template_date}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list row">
|
||||
<div className="col-md-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => setShowImportTemplateDropdown(false)}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={importFromTemplate}> Copy </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const customMenuTargetDate = React.forwardRef(
|
||||
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
||||
return (
|
||||
@@ -1072,6 +1298,14 @@ const RoutesDashboard = () => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (scheduleImportProgressTimerRef.current) {
|
||||
clearInterval(scheduleImportProgressTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1079,6 +1313,12 @@ const RoutesDashboard = () => {
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</div>}
|
||||
{isScheduleImporting && <div className="spinner-overlay" style={{ flexDirection: 'column', gap: '12px', zIndex: 1060 }}>
|
||||
<div style={{ width: '360px', maxWidth: '80vw', textAlign: 'center' }}>
|
||||
<div style={{ marginBottom: '8px', color: '#333' }}>{scheduleImportLabel || 'Importing schedule...'}</div>
|
||||
<ProgressBar now={scheduleImportProgress} animated label={`${Math.round(scheduleImportProgress)}%`} />
|
||||
</div>
|
||||
</div>}
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
@@ -1106,9 +1346,7 @@ const RoutesDashboard = () => {
|
||||
<button className="btn btn-primary" onClick={() => generateRouteReport()}><Download size={16} className="me-2"></Download>Export Route Report</button>
|
||||
</div>}
|
||||
{(dateSelected && dateSelected > new Date()) && <div className="app-main-content-fields-section with-function">
|
||||
<button type="button" className="btn btn-primary btn-sm me-2" disabled={copyDisabled || showCopyTodayLoading} onClick={() => copyTodayRoutesOver()}><Copy size={16} className="me-2"></Copy> {showCopyTodayLoading? 'Loading...' : `Copy Today's Routes Over`}</button>
|
||||
{/* <button type="button" className="btn btn-primary btn-sm me-2" onClick={()=> validateSchedule()}><Check size={16} className="me-2"></Check> Validate and Finish Planning</button> */}
|
||||
<button type="button" className="btn btn-primary btn-sm" onClick={()=> triggerShowDeleteModal()}><Eraser size={16} className="me-2"></Eraser> Clean All Routes</button>
|
||||
</div>}
|
||||
{ (dateSelected <= new Date() || !dateSelected) && <div className="list row">
|
||||
{
|
||||
@@ -1116,9 +1354,6 @@ const RoutesDashboard = () => {
|
||||
<div className="col-md-12 mb-4">
|
||||
{(routesInboundForShowing && routesInboundForShowing.length > 0) || (routesOutboundForShowing && routesOutboundForShowing.length > 0) ? (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<button className="btn btn-primary btn-sm" onClick={openSaveTemplateModal}>
|
||||
Save Today's Routes as a Template
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
@@ -1180,9 +1415,6 @@ const RoutesDashboard = () => {
|
||||
<div className="col-md-12 mb-4">
|
||||
{(tmrInboundRoutes && tmrInboundRoutes.length > 0) || (tmrOutboundRoutes && tmrOutboundRoutes.length > 0) ? (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '10px'}}>
|
||||
<button className="btn btn-primary btn-sm" onClick={openSaveTemplateModal}>
|
||||
Save Today's Routes as a Template
|
||||
</button>
|
||||
{/* <button className="btn btn-secondary btn-sm" onClick={() => navigate('/trans-routes/daily-templates/list')}>
|
||||
View and Update Daily Route Templates
|
||||
</button> */}
|
||||
@@ -1217,30 +1449,19 @@ const RoutesDashboard = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection transRoutes={tmrInboundRoutes} copyList={tmrOutboundRoutes} addText="+Add Route" copyText="+Copy Route from Outbound" canAddNew={true} drivers={drivers} vehicles={vehicles} redirect={goToCreateRoute} routeType="inbound" sectionName="Inbound Routes"/>
|
||||
<RoutesSection transRoutes={tmrInboundRoutes} copyList={tmrOutboundRoutes} addText="Add New Route" copyText="Import Outbound Routes" canAddNew={true} drivers={drivers} vehicles={vehicles} redirect={goToCreateRoute} routeType="inbound" sectionName="Inbound Routes" showCheckedInText={false} showRouteHeaderButtons={true} clearAllRoutesAction={triggerShowDeleteModal}/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="col-md-12 mb-4">
|
||||
<RoutesSection transRoutes={tmrOutboundRoutes} copyList={tmrInboundRoutes} addText="+Add Route" copyText="+Copy Route from Inbound" canAddNew={true} drivers={drivers} vehicles={vehicles} redirect={goToCreateRoute} routeType="outbound" sectionName="Outbound Routes"/>
|
||||
<RoutesSection transRoutes={tmrOutboundRoutes} copyList={tmrInboundRoutes} addText="Add New Route" copyText="Import Inbound Routes" canAddNew={true} drivers={drivers} vehicles={vehicles} redirect={goToCreateRoute} routeType="outbound" sectionName="Outbound Routes" showRouteHeaderButtons={true} clearAllRoutesAction={triggerShowDeleteModal}/>
|
||||
</div>
|
||||
<hr />
|
||||
{(AuthService.canCreateOrEditDrivers() || AuthService.canAddOrEditEmployees()) && <div className="col-md-12 mb-4">
|
||||
<RoutesSection canAddNew={true} drivers={drivers} addText="+Add Driver" redirect={createDriver} sectionName="Drivers"/>
|
||||
</div>}
|
||||
<hr />
|
||||
{AuthService.canAddOrEditVechiles() && <div className="col-md-12 mb-4">
|
||||
<RoutesSection canAddNew={true} vehicles={vehicles} addText="+Add Vehicle" redirect={createVehicle} sectionName="Vehicles"/>
|
||||
</div>}
|
||||
<hr />
|
||||
<div className="col-md-12 mb-4">
|
||||
{routesForShowing && vehicles && <RouteCustomerTable transRoutes={routesForShowing} sectionName={'Customer Seating'} vehicles={vehicles}/>}
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</Tab>
|
||||
<Tab eventKey="allRoutesSignature" title="All Routes Signature">
|
||||
{!isFutureScheduleSelected && <Tab eventKey="allRoutesSignature" title="All Routes Signature">
|
||||
<input className="me-2 mb-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
<table className="personnel-info-table me-4">
|
||||
<thead>
|
||||
@@ -1280,8 +1501,8 @@ const RoutesDashboard = () => {
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</Tab>
|
||||
<Tab eventKey="allRoutesStatus" title="All Routes Status">
|
||||
</Tab>}
|
||||
{!isFutureScheduleSelected && <Tab eventKey="allRoutesStatus" title="All Routes Status">
|
||||
<div className="list row">
|
||||
<div className="col-md-12 mb-4">
|
||||
<PersonnelSection transRoutes={routesForShowing} showCompletedInfo={false}
|
||||
@@ -1296,7 +1517,7 @@ const RoutesDashboard = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tab>}
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
{
|
||||
@@ -1311,27 +1532,48 @@ const RoutesDashboard = () => {
|
||||
onToggle={() => setShowOriginDateDropdown(!showOriginDateDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<CalendarWeek size={16} className="me-2"></CalendarWeek>Select Date to View OR Copy From
|
||||
<Dropdown.Toggle variant="outline-secondary">
|
||||
<CalendarWeek size={16} className="me-2"></CalendarWeek>View By Date
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuOriginDate}/>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
key={'signature-date'}
|
||||
id="signature-date"
|
||||
className="me-2"
|
||||
show={showTargetDateDropdown}
|
||||
onToggle={() => setShowTargetDateDropdown(!showTargetDateDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="primary">
|
||||
<CalendarWeek size={16} className="me-2"></CalendarWeek>Select Date to Copy To
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuTargetDate}/>
|
||||
</Dropdown>
|
||||
{isFutureScheduleSelected && <>
|
||||
<Dropdown
|
||||
key={'import-from-date'}
|
||||
id="import-from-date"
|
||||
className="me-2"
|
||||
show={showImportDateDropdown}
|
||||
onToggle={() => setShowImportDateDropdown(!showImportDateDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="outline-secondary" disabled={isScheduleImporting}>
|
||||
<Copy size={16} className="me-2"></Copy>Import From Date
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuImportFromDate}/>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
key={'import-from-template'}
|
||||
id="import-from-template"
|
||||
className="me-2"
|
||||
show={showImportTemplateDropdown}
|
||||
onToggle={() => setShowImportTemplateDropdown(!showImportTemplateDropdown)}
|
||||
autoClose={false}
|
||||
>
|
||||
<Dropdown.Toggle variant="outline-secondary" disabled={isScheduleImporting}>
|
||||
<Copy size={16} className="me-2"></Copy>Import From Template
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu as={customMenuImportFromTemplate}/>
|
||||
</Dropdown>
|
||||
<button className="btn btn-outline-secondary me-2" onClick={() => createVehicle()} disabled={isScheduleImporting}>
|
||||
<Plus size={16} className="me-2"></Plus>Add New Vehicle
|
||||
</button>
|
||||
<button className="btn btn-outline-secondary me-2" disabled={isScheduleImporting}>
|
||||
<Check size={16} className="me-2"></Check>Check Routes
|
||||
</button>
|
||||
</>}
|
||||
</>
|
||||
}
|
||||
<button className="btn btn-primary me-2" onClick={() => goToCreateRoute()}><Plus size={16}></Plus>Add New Route</button>
|
||||
{!isFutureScheduleSelected && <button className="btn btn-primary me-2" onClick={() => goToCreateRoute()}><Plus size={16}></Plus>Add New Route</button>}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CUSTOMER_TYPE, PERSONAL_ROUTE_STATUS, PICKUP_STATUS } from "../../share
|
||||
import { AuthService } from "../../services";
|
||||
import moment from 'moment';
|
||||
|
||||
const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, canAddNew, addText, copyText, redirect, routeType, isTemplate, templateId}) => {
|
||||
const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, canAddNew, addText, copyText, redirect, routeType, isTemplate, templateId, showCheckedInText = true, showRouteHeaderButtons = false, clearAllRoutesAction = null}) => {
|
||||
const [showCopyModal, setShowCopyModal] = useState(false);
|
||||
const [routesOptions, setRouteOptions] = useState([]);
|
||||
const [newRoute, setNewRoute] = useState(undefined);
|
||||
@@ -85,22 +85,45 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`text-primary ${canAddNew? 'mb-2' : ''}`}>
|
||||
<h6 className="me-4">
|
||||
<div
|
||||
className={`text-primary ${canAddNew ? 'mb-2' : ''} d-flex align-items-center ${showRouteHeaderButtons ? 'justify-content-start' : 'justify-content-between'}`}
|
||||
style={showRouteHeaderButtons ? { gap: '12px', flexWrap: 'wrap' } : {}}
|
||||
>
|
||||
<h6 className={`${showRouteHeaderButtons ? 'me-1' : 'me-4'} mb-0`}>
|
||||
{`${sectionName}: `} <span className="route-stats">{
|
||||
(sectionName.includes('Inbound') ||
|
||||
sectionName.includes('Outbound')) &&
|
||||
(`${seniors?.length} Scheduled (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Visitors)${sectionName.includes('Inbound') ? ` ${seniors.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.IN_CENTER)?.length} checked in` : ''}`)}</span>
|
||||
(`${seniors?.length} Scheduled (${seniors.filter(item => [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.SELF_PAY].includes(item.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Members ${seniors.filter(item=> [CUSTOMER_TYPE.VISITOR].includes(item?.customer_type) && ![PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT, PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT].includes(item?.customer_route_status))?.length} Visitors)${sectionName.includes('Inbound') && showCheckedInText ? ` ${seniors.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.IN_CENTER)?.length} checked in` : ''}`)}</span>
|
||||
</h6>
|
||||
{ canAddNew && (
|
||||
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
||||
<a>{addText}</a>
|
||||
</small>)}
|
||||
{
|
||||
copyText && (
|
||||
<small onClick={() => {openCopyModal()} }>
|
||||
<a>{copyText}</a>
|
||||
</small>)}
|
||||
<div className="d-flex align-items-center" style={{ gap: '10px' }}>
|
||||
{canAddNew && (
|
||||
showRouteHeaderButtons ? (
|
||||
<button className="btn btn-outline-secondary" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
||||
{addText}
|
||||
</button>
|
||||
) : (
|
||||
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
||||
<a>{addText}</a>
|
||||
</small>
|
||||
)
|
||||
)}
|
||||
{copyText && (
|
||||
showRouteHeaderButtons ? (
|
||||
<button className="btn btn-outline-secondary" onClick={() => {openCopyModal()}}>
|
||||
{copyText}
|
||||
</button>
|
||||
) : (
|
||||
<small onClick={() => {openCopyModal()} }>
|
||||
<a>{copyText}</a>
|
||||
</small>
|
||||
)
|
||||
)}
|
||||
{clearAllRoutesAction && showRouteHeaderButtons && (
|
||||
<button className="btn btn-outline-secondary" onClick={clearAllRoutesAction}>
|
||||
Clear All Routes
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
transRoutes && (<>
|
||||
|
||||
Reference in New Issue
Block a user