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.57cff37a.css",
|
||||
"main.js": "/static/js/main.1a821513.js",
|
||||
"main.js": "/static/js/main.68aeca0a.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.57cff37a.css.map": "/static/css/main.57cff37a.css.map",
|
||||
"main.1a821513.js.map": "/static/js/main.1a821513.js.map",
|
||||
"main.68aeca0a.js.map": "/static/js/main.68aeca0a.js.map",
|
||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.57cff37a.css",
|
||||
"static/js/main.1a821513.js"
|
||||
"static/js/main.68aeca0a.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.1a821513.js"></script><link href="/static/css/main.57cff37a.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.68aeca0a.js"></script><link href="/static/css/main.57cff37a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
client/.DS_Store
vendored
BIN
client/.DS_Store
vendored
Binary file not shown.
@@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router-dom";
|
||||
import { selectAllRoutes, transRoutesSlice, vehicleSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
|
||||
import { Modal, Button, Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
||||
import RouteCustomerEditor from "./RouteCustomerEditor";
|
||||
import { AuthService, TransRoutesService, CustomerService } from "../../services";
|
||||
import { AuthService, TransRoutesService, CustomerService, EventsService } from "../../services";
|
||||
import TimePicker from 'react-time-picker';
|
||||
import 'react-time-picker/dist/TimePicker.css';
|
||||
import moment from 'moment';
|
||||
@@ -42,6 +42,7 @@ const RouteEdit = () => {
|
||||
const [allCustomers, setAllCustomers] = useState([]);
|
||||
const [unassignedCustomers, setUnassignedCustomers] = useState([]);
|
||||
const [addCustomerToRoute, setAddCustomerToRoute] = useState(null);
|
||||
const [attendanceAbsentCustomers, setAttendanceAbsentCustomers] = useState([]); // customers with attendance notes on route date
|
||||
const paramsQuery = new URLSearchParams(window.location.search);
|
||||
const scheduleDate = paramsQuery.get('dateSchedule');
|
||||
const editSection = paramsQuery.get('editSection')
|
||||
@@ -95,7 +96,18 @@ const RouteEdit = () => {
|
||||
if (!validateRoute()) {
|
||||
return;
|
||||
}
|
||||
let data = Object.assign({}, currentRoute, {name: routeName, driver: newDriver, vehicle: newVehicle, type: newRouteType, route_customer_list: newCustomerList});
|
||||
// Merge attendance-based absences into the customer list for saving
|
||||
const existingAbsentInRoute = (currentRoute?.route_customer_list || []).filter(
|
||||
c => c?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT
|
||||
);
|
||||
const existingAbsentIds = new Set(existingAbsentInRoute.map(a => a.customer_id));
|
||||
// Also exclude attendance-absent customers from newCustomerList (they shouldn't be in the main list)
|
||||
const attendanceAbsentIds = new Set(attendanceAbsentCustomers.map(a => a.customer_id));
|
||||
const filteredCustomerList = (newCustomerList || []).filter(c => !attendanceAbsentIds.has(c.customer_id));
|
||||
// Add attendance-based absences that aren't already in existing absences
|
||||
const newAttendanceAbsences = attendanceAbsentCustomers.filter(a => !existingAbsentIds.has(a.customer_id));
|
||||
const fullCustomerList = [...filteredCustomerList, ...existingAbsentInRoute, ...newAttendanceAbsences];
|
||||
let data = Object.assign({}, currentRoute, {name: routeName, driver: newDriver, vehicle: newVehicle, type: newRouteType, route_customer_list: fullCustomerList});
|
||||
if (estimatedStartTime && estimatedStartTime !== '') {
|
||||
data = Object.assign({}, data, {estimated_start_time: combineDateAndTime(currentRoute.schedule_date, estimatedStartTime)})
|
||||
}
|
||||
@@ -187,6 +199,43 @@ const RouteEdit = () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: check if a recurring rule has an occurrence on a specific date
|
||||
const recurRuleFallsOnDate = (rule, targetDate) => {
|
||||
const start = new Date(rule.start_repeat_date);
|
||||
const end = new Date(rule.end_repeat_date);
|
||||
const target = new Date(targetDate);
|
||||
// Normalize to date-only comparison
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
target.setHours(0, 0, 0, 0);
|
||||
|
||||
if (target < start || target > end) return false;
|
||||
|
||||
const freq = rule.rrule;
|
||||
let current = new Date(start);
|
||||
let count = 0;
|
||||
while (current <= target && count < 5000) {
|
||||
if (current.getTime() === target.getTime()) return true;
|
||||
if (freq === 'FREQ=DAILY') {
|
||||
current = new Date(current.getTime() + 24 * 60 * 60 * 1000);
|
||||
} else if (freq === 'FREQ=WEEKLY') {
|
||||
current = new Date(current.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||
} else if (freq === 'FREQ=MONTHLY') {
|
||||
const next = new Date(current);
|
||||
next.setMonth(next.getMonth() + 1);
|
||||
current = next;
|
||||
} else if (freq === 'FREQ=YEARLY') {
|
||||
const next = new Date(current);
|
||||
next.setFullYear(next.getFullYear() + 1);
|
||||
current = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditRoutes()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
||||
@@ -229,9 +278,67 @@ const RouteEdit = () => {
|
||||
{ route_customer_list: newCustomerList || [] }
|
||||
];
|
||||
|
||||
const unassigned = calculateUnassignedCustomers(allCustomers, routesWithCurrentEdits);
|
||||
// Also exclude attendance-absent customers from unassigned list
|
||||
const attendanceAbsentIds = new Set(attendanceAbsentCustomers.map(c => c.customer_id));
|
||||
const unassigned = calculateUnassignedCustomers(allCustomers, routesWithCurrentEdits)
|
||||
.filter(customer => !attendanceAbsentIds.has(customer.id));
|
||||
setUnassignedCustomers(unassigned);
|
||||
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute, newCustomerList]);
|
||||
}, [allCustomers, allRoutes, tomorrowRoutes, historyRoutes, currentRoute, newCustomerList, attendanceAbsentCustomers]);
|
||||
|
||||
// Fetch attendance notes (single + recurring) for the route date
|
||||
useEffect(() => {
|
||||
if (!currentRoute?.schedule_date || !allCustomers?.length) return;
|
||||
|
||||
const routeDate = currentRoute.schedule_date; // MM/DD/YYYY
|
||||
// Convert to YYYY-MM-DD for the API
|
||||
const dateParts = routeDate.split('/');
|
||||
if (dateParts.length !== 3) return;
|
||||
const apiDate = `${dateParts[2]}-${dateParts[0].padStart(2, '0')}-${dateParts[1].padStart(2, '0')}`;
|
||||
// Parse into a Date object for recurring rule checking
|
||||
const routeDateObj = new Date(parseInt(dateParts[2]), parseInt(dateParts[0]) - 1, parseInt(dateParts[1]));
|
||||
routeDateObj.setHours(0, 0, 0, 0);
|
||||
|
||||
// Fetch single attendance notes and recurring rules in parallel
|
||||
Promise.all([
|
||||
EventsService.getAllEvents({ date: apiDate, type: 'incident' }),
|
||||
EventsService.getAllEventRecurrences()
|
||||
]).then(([eventsRes, recurRes]) => {
|
||||
const absentCustomerIds = new Set();
|
||||
|
||||
// Single attendance notes for this date
|
||||
const singleNotes = (eventsRes?.data || []).filter(
|
||||
e => e.type === 'incident' && e.status === 'active' && e.target_uuid
|
||||
);
|
||||
singleNotes.forEach(note => absentCustomerIds.add(note.target_uuid));
|
||||
|
||||
// Recurring attendance rules that fall on this date
|
||||
const recurRules = (recurRes?.data || []).filter(
|
||||
r => r.type === 'incident' && r.status === 'active' && r.target_uuid && r.rrule
|
||||
);
|
||||
recurRules.forEach(rule => {
|
||||
if (recurRuleFallsOnDate(rule, routeDateObj)) {
|
||||
absentCustomerIds.add(rule.target_uuid);
|
||||
}
|
||||
});
|
||||
|
||||
// Build the list of absent customer objects
|
||||
const absentList = [];
|
||||
absentCustomerIds.forEach(customerId => {
|
||||
const customer = allCustomers.find(c => c.id === customerId);
|
||||
if (customer) {
|
||||
absentList.push({
|
||||
customer_id: customer.id,
|
||||
customer_name: customer.name,
|
||||
customer_address: customer.address1 || '',
|
||||
customer_route_status: PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT,
|
||||
customer_pickup_status: 'scheduleAbsent',
|
||||
_attendance_based: true // flag to identify these are from attendance notes
|
||||
});
|
||||
}
|
||||
});
|
||||
setAttendanceAbsentCustomers(absentList);
|
||||
});
|
||||
}, [currentRoute?.schedule_date, allCustomers]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (currentRoute) {
|
||||
@@ -462,7 +569,8 @@ const RouteEdit = () => {
|
||||
currentRoute={currentRoute ? {
|
||||
...currentRoute,
|
||||
route_customer_list: currentRoute.route_customer_list?.filter(
|
||||
customer => customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT
|
||||
customer => customer?.customer_route_status !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT &&
|
||||
!attendanceAbsentCustomers.some(a => a.customer_id === customer.customer_id)
|
||||
) || []
|
||||
} : undefined}
|
||||
setNewCustomerList={setNewCustomerList}
|
||||
@@ -473,10 +581,16 @@ const RouteEdit = () => {
|
||||
</div>
|
||||
<div className="column-container">
|
||||
<div className="column-card adjust">
|
||||
<h6 className="text-primary">Scheduled Absences ({currentRoute?.route_customer_list?.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.length || 0})</h6>
|
||||
<h6 className="text-primary">Scheduled Absences ({(() => {
|
||||
const existingAbsent = currentRoute?.route_customer_list?.filter(item => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT) || [];
|
||||
const existingIds = new Set(existingAbsent.map(a => a.customer_id));
|
||||
const attendanceOnly = attendanceAbsentCustomers.filter(a => !existingIds.has(a.customer_id));
|
||||
return existingAbsent.length + attendanceOnly.length;
|
||||
})()})</h6>
|
||||
<div className="customers-container mb-4">
|
||||
{
|
||||
currentRoute?.route_customer_list.filter(customer => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.map((abItem) => {
|
||||
// Existing scheduled absences from route data
|
||||
currentRoute?.route_customer_list?.filter(customer => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)?.map((abItem) => {
|
||||
return <div key={abItem.customer_id} className="customers-dnd-item-container-absent">
|
||||
<GripVertical className="me-4" size={14}></GripVertical>
|
||||
<div className="customer-dnd-item">
|
||||
@@ -484,10 +598,30 @@ const RouteEdit = () => {
|
||||
<small className="me-2">{abItem.customer_address}</small>
|
||||
<small className="me-2">{abItem.customer_pickup_status}</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
})
|
||||
}
|
||||
{
|
||||
// Attendance-based absences (not already in existing scheduled absences)
|
||||
(() => {
|
||||
const existingIds = new Set(
|
||||
(currentRoute?.route_customer_list?.filter(c => c?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT) || [])
|
||||
.map(a => a.customer_id)
|
||||
);
|
||||
return attendanceAbsentCustomers
|
||||
.filter(a => !existingIds.has(a.customer_id))
|
||||
.map((abItem) => (
|
||||
<div key={`att-${abItem.customer_id}`} className="customers-dnd-item-container-absent" style={{ opacity: 0.85 }}>
|
||||
<GripVertical className="me-4" size={14}></GripVertical>
|
||||
<div className="customer-dnd-item">
|
||||
<span>{abItem.customer_name} </span>
|
||||
<small className="me-2">{abItem.customer_address}</small>
|
||||
<small className="me-2 text-muted">(Attendance Note)</small>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
})()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user