diff --git a/.DS_Store b/.DS_Store index 0b1b130..e5979d2 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app/.DS_Store b/app/.DS_Store index 5279aa7..3a45cc4 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/client/src/components/center-calendar/CenterCalendar.js b/client/src/components/center-calendar/CenterCalendar.js index f32ac0d..311ead8 100644 --- a/client/src/components/center-calendar/CenterCalendar.js +++ b/client/src/components/center-calendar/CenterCalendar.js @@ -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 &&
+ + Loading... + +
}
General diff --git a/client/src/components/dashboard/Dashboard.js b/client/src/components/dashboard/Dashboard.js index 7662b6a..4108191 100644 --- a/client/src/components/dashboard/Dashboard.js +++ b/client/src/components/dashboard/Dashboard.js @@ -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 &&
+ + Loading... + +
}
Dashboard diff --git a/client/src/components/dashboard/DashboardCustomersList.js b/client/src/components/dashboard/DashboardCustomersList.js index 98e6f05..bc90ea4 100644 --- a/client/src/components/dashboard/DashboardCustomersList.js +++ b/client/src/components/dashboard/DashboardCustomersList.js @@ -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 diff --git a/client/src/components/trans-routes/RouteCustomerEditor.js b/client/src/components/trans-routes/RouteCustomerEditor.js index 80e7c29..027886f 100644 --- a/client/src/components/trans-routes/RouteCustomerEditor.js +++ b/client/src/components/trans-routes/RouteCustomerEditor.js @@ -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
item.customer_id === customer.id)!==undefined} value={newRouteCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleItemToRouteList(customer, e.target.value)}/>
-
{`${customer.name}(${customer.name_cn})`}
+
{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}
{newRouteCustomerList.find((item) => item.customer_id === customer.id) && (
{addressOptions.map((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
item.customer_id === customer.id)!==undefined} value={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)!==undefined} onChange={(e) => toggleGroupedItemToRouteList(customer, e.target.value)}/>
-
{`${customer.name}(${customer.name_cn})`}
+
{`${customerDisplayName}${customerChineseName ? `(${customerChineseName})` : ''}`}
{newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id) && (
{addressOptions.map((address, idx) => (
diff --git a/client/src/components/trans-routes/RouteView.js b/client/src/components/trans-routes/RouteView.js index 057eed9..581862f 100644 --- a/client/src/components/trans-routes/RouteView.js +++ b/client/src/components/trans-routes/RouteView.js @@ -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 ( <>
@@ -119,7 +140,7 @@ const RouteView = () => {
Driver
-
{currentDriver?.name}
+
{resolvedDriverName || 'Not assigned'}
Route Type
@@ -184,7 +205,7 @@ const RouteView = () => {
- {currentRoute && } + {currentRoute && }
diff --git a/client/src/components/trans-routes/RoutesDashboard.js b/client/src/components/trans-routes/RoutesDashboard.js index 6adaf84..1911364 100644 --- a/client/src/components/trans-routes/RoutesDashboard.js +++ b/client/src/components/trans-routes/RoutesDashboard.js @@ -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 &&
+ + Loading... + +
}
Transportation diff --git a/client/src/services/DriverService.js b/client/src/services/DriverService.js index d61a74b..47a5f44 100644 --- a/client/src/services/DriverService.js +++ b/client/src/services/DriverService.js @@ -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);