fix
This commit is contained in:
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user