This commit is contained in:
2026-03-05 18:46:47 -05:00
parent 40ec244364
commit ec98dc95fd
9 changed files with 131 additions and 84 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@@ -2,7 +2,7 @@ import React, {useState, useEffect, useRef, useCallback} from "react";
import { useNavigate } from "react-router-dom";
import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services";
import moment from 'moment';
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown } from "react-bootstrap";
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, Spinner } from "react-bootstrap";
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import {
viewMonthGrid,
@@ -125,6 +125,7 @@ const EventsCalendar = () => {
// Delete confirmation modal
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleteTargetId, setDeleteTargetId] = useState(null);
const [showSpinner, setShowSpinner] = useState(false);
// Helper function to format name from "lastname, firstname" to "firstname lastname"
const formatFullName = (name) => {
@@ -320,20 +321,25 @@ const EventsCalendar = () => {
AuthService.logout();
navigate(`/login`);
}
VehicleService.getAllActiveVehicles().then((data) => {
setVehicles(data.data)
});
EmployeeService.getAllEmployees().then((data) => {
setEmployees(data.data);
});
CustomerService.getAllActiveCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then((data) => {
setResources(data.data);
});
EventsService.getTimeData().then(data => {
setTimeData(data.data);
setShowSpinner(true);
Promise.all([
VehicleService.getAllActiveVehicles().then((data) => {
setVehicles(data.data)
}),
EmployeeService.getAllEmployees().then((data) => {
setEmployees(data.data);
}),
CustomerService.getAllActiveCustomers().then((data) => {
setCustomers(data.data);
}),
ResourceService.getAll().then((data) => {
setResources(data.data);
}),
EventsService.getTimeData().then(data => {
setTimeData(data.data);
})
]).finally(() => {
setShowSpinner(false);
});
}, []);
@@ -1275,6 +1281,11 @@ const getReminderTitleLabel = (value) => {
return (
<>
{showSpinner && <div className="spinner-overlay">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>}
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/">General</Breadcrumb.Item>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Breadcrumb, BreadcrumbItem, Card, Row, Col, Dropdown } from 'react-bootstrap';
import { Breadcrumb, BreadcrumbItem, Card, Row, Col, Dropdown, Spinner } from 'react-bootstrap';
import { AuthService, EventsService, CustomerService, TransRoutesService, ResourceService } from '../../services';
import DashboardCustomersList from './DashboardCustomersList';
import { CUSTOMER_TYPE, PERSONAL_ROUTE_STATUS } from '../../shared';
@@ -19,6 +19,7 @@ const Dashboard = () => {
const [selectedEventType, setSelectedEventType] = useState('medical');
const [customers, setCustomers] = useState([]);
const [resources, setResources] = useState([]);
const [showSpinner, setShowSpinner] = useState(false);
const eventTypes = [
{ value: 'medical', label: 'Medical Appointments' },
@@ -194,15 +195,18 @@ const Dashboard = () => {
};
useEffect(() => {
fetchTodayAttendance();
fetchMedicalAppointments();
// Fetch customers and resources for event mapping
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then((data) => {
setResources(data.data);
setShowSpinner(true);
Promise.all([
fetchTodayAttendance(),
fetchMedicalAppointments(),
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
}),
ResourceService.getAll().then((data) => {
setResources(data.data);
})
]).finally(() => {
setShowSpinner(false);
});
}, []);
@@ -215,6 +219,11 @@ const Dashboard = () => {
return (
<>
{showSpinner && <div className="spinner-overlay">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>}
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item>Dashboard</Breadcrumb.Item>

View File

@@ -109,17 +109,22 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
AuthService.logout();
navigate(`/login`);
}
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data.map((item) =>{
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
setShowSpinner(true);
Promise.all([
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data.map((item) =>{
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
})
LabelService.getAll().then((data) => {
setAvailableLabels(data.data);
})
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
}),
LabelService.getAll().then((data) => {
setAvailableLabels(data.data);
})
]).finally(() => {
setShowSpinner(false);
});
}, []);
// Load avatars for customers - same approach as ViewCustomer

View File

@@ -121,40 +121,28 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
return assignedIds;
};
const formatStructuredAddress = (line1, line2, city, state, zipCode) => {
const formatStructuredAddress = (line1, line2, city, state, zipCode, note) => {
const cityState = [city, state].filter(Boolean).join(', ');
return [line1, line2, cityState, zipCode]
const mainAddress = [line1, line2, cityState, zipCode]
.filter(item => item && String(item).trim() !== '')
.join(' ')
.trim();
const addressNote = (note || '').trim();
if (!mainAddress) return '';
return addressNote ? `${mainAddress} (${addressNote})` : mainAddress;
};
const getCustomerAddressOptions = (customer) => {
if (!customer) return [];
const structuredAddresses = [
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),
formatStructuredAddress(customer.address5_line_1, customer.address5_line_2, customer.city5, customer.state5, customer.zip_code5),
const addresses = [
formatStructuredAddress(customer.address_line_1, customer.address_line_2, customer.city, customer.state, customer.zip_code, customer.address_note),
formatStructuredAddress(customer.address2_line_1, customer.address2_line_2, customer.city2, customer.state2, customer.zip_code2, customer.address2_note),
formatStructuredAddress(customer.address3_line_1, customer.address3_line_2, customer.city3, customer.state3, customer.zip_code3, customer.address3_note),
formatStructuredAddress(customer.address4_line_1, customer.address4_line_2, customer.city4, customer.state4, customer.zip_code4, customer.address4_note),
];
const legacyAddresses = [
customer.address1,
customer.address2,
customer.address3,
customer.address4,
customer.address5,
];
return Array.from(
new Set(
[...structuredAddresses, ...legacyAddresses]
.map(item => (item || '').trim())
.filter(item => item !== '')
)
);
return addresses.filter(address => (address || '').trim() !== '');
};
useEffect(() => {
@@ -520,10 +508,12 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
return currentItems?.map(
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
const customerDisplayName = customer?.name || '';
const customerChineseName = customer?.name_cn || '';
return <div key={customer.id} className="option-item">
<input className="me-4 mt-2" type="checkbox" checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)!==undefined} value={newRouteCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleItemToRouteList(customer, e.target.value)}/>
<div>
<div>{`${customer.name}(${customer.name_cn})`}</div>
<div>{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}</div>
{newRouteCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-address-${idx}`}>
@@ -543,10 +533,12 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
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(
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
const customerDisplayName = customer?.name || '';
const customerChineseName = customer?.name_cn || '';
return <div key={customer.id} className="option-item">
<input className="me-4 mt-2" type="checkbox" checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} value={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleGroupedItemToRouteList(customer, e.target.value)}/>
<div>
<div>{`${customer.name}(${customer.name_cn})`}</div>
<div>{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}</div>
{newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-group-address-${idx}`}>

View File

@@ -3,7 +3,7 @@ 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 } from "../../services";
import { AuthService, CustomerService, SignatureRequestService, EmployeeService } from "../../services";
import moment from 'moment';
import { Breadcrumb, Tabs, Tab, Dropdown, Spinner, Modal, Button } from "react-bootstrap";
import { Download, Pencil } from "react-bootstrap-icons";
@@ -19,6 +19,7 @@ const RouteView = () => {
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);
@@ -26,6 +27,8 @@ const RouteView = () => {
const scheduleDate = paramsQuery.get('dateSchedule');
const navigate = useNavigate();
const resolvedDriverId = currentDriver?.id || fallbackDriver?.id || currentRoute?.driver;
const resolvedDriverName = currentDriver?.name || fallbackDriver?.name || '';
const closeModal = () => {
setShowVehicleDetails(false);
}
@@ -62,9 +65,13 @@ const RouteView = () => {
}
}
const generateSignatureRequest = () => {
if (!resolvedDriverId) {
window.alert('Driver is not assigned for this route.');
return;
}
SignatureRequestService.createNewSignatureRequest({
driver_id: currentDriver?.id,
driver_name: currentDriver?.name,
driver_id: resolvedDriverId,
driver_name: resolvedDriverName,
route_id: currentRoute?.id,
route_date: currentRoute?.schedule_date,
route_name: currentRoute?.name,
@@ -73,18 +80,32 @@ const RouteView = () => {
setSignatureRequest(data.data);
})
}
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: currentDriver?.id, route_id: currentRoute?.id, route_date: currentRoute?.scheduleDate}).then((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]);
}, [currentRoute, resolvedDriverId]);
return (
<>
<div className="list row mb-4">
@@ -119,7 +140,7 @@ const RouteView = () => {
</div>
<div className="field-body">
<div className="field-label">Driver</div>
<div className="field-value">{currentDriver?.name}</div>
<div className="field-value">{resolvedDriverName || 'Not assigned'}</div>
</div>
<div className="field-body">
<div className="field-label">Route Type</div>
@@ -184,7 +205,7 @@ const RouteView = () => {
<Tab eventKey="routeStatus" title="Route Status">
<div className="list row">
<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={currentDriver?.name} deleteFile={deleteFile}/>}
{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>
</div>
</Tab>

View File

@@ -81,6 +81,7 @@ const RoutesDashboard = () => {
const [templates, setTemplates] = useState([]);
const [selectedTemplateId, setSelectedTemplateId] = useState('');
const [applyingTemplate, setApplyingTemplate] = useState(false);
const [pageLoading, setPageLoading] = useState(false);
const params = new URLSearchParams(window.location.search);
@@ -159,6 +160,7 @@ const RoutesDashboard = () => {
}, [dispatch, fetchAllRoutes])
useEffect(() => {
setPageLoading(true);
if (scheduleDate) {
const [year, month, day] = scheduleDate?.split('-').map(Number);
setDateSelected(new Date(year, month-1, day));
@@ -166,21 +168,23 @@ const RoutesDashboard = () => {
setDateSelected(new Date())
}
const site = EventsService.site;
DriverService.getAllActiveDrivers('driver', 'active').then((data) => {
setDriverList(data.data);
});
CustomerService.getAllCustomers().then((data) => setCustomers(data?.data));
CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => {
if (data?.data) {
setDirectorSignature(data?.data)
}
});
// Fetch all daily routes templates
DailyRoutesTemplateService.getAll().then((response) => {
setTemplates(response.data || []);
}).catch(err => {
console.error('Error fetching templates:', err);
Promise.all([
DriverService.getAllActiveDrivers('driver', 'active').then((data) => {
setDriverList(data.data);
}),
CustomerService.getAllCustomers().then((data) => setCustomers(data?.data)),
CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => {
if (data?.data) {
setDirectorSignature(data?.data)
}
}).catch(() => {}),
DailyRoutesTemplateService.getAll().then((response) => {
setTemplates(response.data || []);
}).catch(err => {
console.error('Error fetching templates:', err);
})
]).finally(() => {
setPageLoading(false);
});
}, []);
@@ -1071,6 +1075,11 @@ const RoutesDashboard = () => {
return (
<>
{pageLoading && <div className="spinner-overlay">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>}
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/">Transportation</Breadcrumb.Item>

View File

@@ -26,7 +26,7 @@ const updateDriver = (id, data) => {
data.roles = ['driver'];
} else {
if (data.roles && !data.roles.includes('driver')) {
data.roles.push('dirver');
data.roles.push('driver');
}
}
return http.put(`/employees/${id}`, data);
@@ -51,7 +51,7 @@ const updateDriverInStaff = (id, data) => {
data.roles = ['driver'];
} else {
if (data.roles && !data.roles.includes('driver')) {
data.roles.push('dirver');
data.roles.push('driver');
}
}
return http.put(`/staffs/${id}`, data);