This commit is contained in:
2026-03-03 16:37:48 -05:00
parent 1c71c42bdd
commit 465ea5c5ff
28 changed files with 352 additions and 491 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

BIN
app/views/.DS_Store vendored

Binary file not shown.

View File

@@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.46cc12be.css",
"main.js": "/static/js/main.b35b44f5.js",
"main.js": "/static/js/main.c97709ee.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.b35b44f5.js.map": "/static/js/main.b35b44f5.js.map",
"main.c97709ee.js.map": "/static/js/main.c97709ee.js.map",
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
},
"entrypoints": [
"static/css/main.46cc12be.css",
"static/js/main.b35b44f5.js"
"static/js/main.c97709ee.js"
]
}

View File

@@ -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.b35b44f5.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.c97709ee.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>

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

Binary file not shown.

View File

@@ -301,6 +301,15 @@ const EventsCalendar = () => {
eventsDateMap.set(dateString, value);
}
}
// Sort each day by earliest start time first
eventsDateMap.forEach((items, key) => {
const sortedItems = [...items].sort((a, b) => {
const aTime = moment(a?.start_time).valueOf();
const bTime = moment(b?.start_time).valueOf();
return aTime - bTime;
});
eventsDateMap.set(key, sortedItems);
});
return eventsDateMap;
};

View File

@@ -94,6 +94,7 @@ const CreateCustomer = () => {
const [consentToTextMessages, setConsentToTextMessages] = useState('');
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
const [consentToMediaUse, setConsentToMediaUse] = useState('');
const selectableCustomerTypes = [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.VISITOR];
// Medical & Insurance - Providers
const [primaryCarePhysician, setPrimaryCarePhysician] = useState('');
@@ -595,8 +596,8 @@ const CreateCustomer = () => {
<div className="field-label">Customer Type</div>
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
<option value="">Select...</option>
{Object.keys(CUSTOMER_TYPE).map(key => (
<option key={key} value={CUSTOMER_TYPE[key]}>{CUSTOMER_TYPE_TEXT[CUSTOMER_TYPE[key]]}</option>
{selectableCustomerTypes.map((typeOption) => (
<option key={typeOption} value={typeOption}>{CUSTOMER_TYPE_TEXT[typeOption] || typeOption}</option>
))}
</select>
</div>
@@ -937,6 +938,7 @@ const CreateCustomer = () => {
onChange={setDietaryRestrictions}
options={DIETARY_RESTRICTIONS_GROUPED}
placeholder="e.g., No Pork"
exclusiveOptionValue="regular"
/>
</div>
<div className="me-4">

View File

@@ -94,6 +94,7 @@ const UpdateCustomer = () => {
const [dischargeConfirmReason, setDischargeConfirmReason] = useState('');
const [dischargeConfirmReasonOther, setDischargeConfirmReasonOther] = useState('');
const [isDischarging, setIsDischarging] = useState(false);
const [isReactivating, setIsReactivating] = useState(false);
// Care & Services
const [dietaryRestrictions, setDietaryRestrictions] = useState([]);
@@ -105,6 +106,8 @@ const UpdateCustomer = () => {
const [consentToTextMessages, setConsentToTextMessages] = useState('');
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
const [consentToMediaUse, setConsentToMediaUse] = useState('');
const selectableCustomerTypes = [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.VISITOR];
const hasLegacyCustomerType = !!customerType && !selectableCustomerTypes.includes(customerType);
// Medical & Insurance - Providers
const [primaryCarePhysician, setPrimaryCarePhysician] = useState('');
@@ -268,6 +271,36 @@ const UpdateCustomer = () => {
}
};
const handleReactivate = async () => {
if (!currentCustomer?.id) return;
setIsReactivating(true);
const currentUserName = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user'))?.name : '';
const reactivateData = {
status: 'active',
type: CUSTOMER_TYPE.MEMBER,
edit_by: currentUserName,
edit_date: new Date()
};
try {
await CustomerService.updateCustomer(currentCustomer.id, reactivateData);
setCurrentCustomer((prev) => ({
...prev,
status: 'active',
type: CUSTOMER_TYPE.MEMBER
}));
setCustomerType(CUSTOMER_TYPE.MEMBER);
alert('Customer has been reactivated successfully.');
} catch (error) {
console.error('Error reactivating customer:', error);
alert('Error reactivating customer. Please try again.');
} finally {
setIsReactivating(false);
}
};
const onPharmacyChange = (selectedPharmacy) => {
setPharmacy(selectedPharmacy);
setPharmacyId(selectedPharmacy?.value);
@@ -933,8 +966,11 @@ const UpdateCustomer = () => {
<div className="field-label">Customer Type</div>
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
<option value="">Select...</option>
{Object.keys(CUSTOMER_TYPE).map(key => (
<option key={key} value={CUSTOMER_TYPE[key]}>{CUSTOMER_TYPE_TEXT[CUSTOMER_TYPE[key]]}</option>
{hasLegacyCustomerType && (
<option value={customerType}>{CUSTOMER_TYPE_TEXT[customerType] || customerType}</option>
)}
{selectableCustomerTypes.map((typeOption) => (
<option key={typeOption} value={typeOption}>{CUSTOMER_TYPE_TEXT[typeOption] || typeOption}</option>
))}
</select>
</div>
@@ -1072,7 +1108,14 @@ const UpdateCustomer = () => {
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Address Line 1 {index === 0 && <span className="required">*</span>}</div>
<input type="text" placeholder="e.g., 100 Sunshine Lane" className="long" value={address.line1} onChange={e => updateAddress(index, 'line1', e.target.value)}/>
<input
type="text"
placeholder="e.g., 100 Sunshine Lane"
className="long"
value={address.line1}
onChange={e => updateAddress(index, 'line1', e.target.value)}
required={index === 0}
/>
</div>
<div className="me-4">
<div className="field-label">Address Line 2</div>
@@ -1269,6 +1312,13 @@ const UpdateCustomer = () => {
</button>
</div>
)}
{!isCustomerActive() && (
<div style={{ marginTop: '16px' }}>
<button className="btn btn-warning btn-sm" onClick={handleReactivate} disabled={isReactivating}>
<BoxArrowRight className="me-2" size={16}></BoxArrowRight>{isReactivating ? 'Reactivating...' : 'Reactivate Customer'}
</button>
</div>
)}
<div className="list row mb-5">
<div className="col-md-12 col-sm-12 col-xs-12">
@@ -1290,6 +1340,7 @@ const UpdateCustomer = () => {
onChange={setDietaryRestrictions}
options={DIETARY_RESTRICTIONS_GROUPED}
placeholder="e.g., No Pork"
exclusiveOptionValue="regular"
/>
</div>
<div className="me-4">

View File

@@ -89,11 +89,17 @@ const Dashboard = () => {
?.filter(item => item.status === 'active');
}
setEvents(processedEvents);
const sortedEvents = [...(processedEvents || [])].sort((a, b) => {
const aTime = moment(a?.start_time).valueOf();
const bTime = moment(b?.start_time).valueOf();
return aTime - bTime;
});
setEvents(sortedEvents);
// Group events by date
const eventsDateMap = new Map();
processedEvents?.forEach(eventItem => {
sortedEvents?.forEach(eventItem => {
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
if (eventsDateMap.has(dateString)) {
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
@@ -101,6 +107,16 @@ const Dashboard = () => {
eventsDateMap.set(dateString, [eventItem]);
}
});
eventsDateMap.forEach((items, key) => {
const sortedItems = [...items].sort((a, b) => {
const aTime = moment(a?.start_time).valueOf();
const bTime = moment(b?.start_time).valueOf();
return aTime - bTime;
});
eventsDateMap.set(key, sortedItems);
});
setGroupedEvents(eventsDateMap);
} catch (error) {
console.error('Error fetching events:', error);

View File

@@ -22,9 +22,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
const [showExportDropdown, setShowExportDropdown] = useState(false);
const [healthConditionFilter, setHealthConditionFilter] = useState('');
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
const [tagsFilter, setTagsFilter] = useState([]);
const [availableLabels, setAvailableLabels] = useState([]);
const [showAvatarModal, setShowAvatarModal] = useState(false);
@@ -32,6 +29,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
const [avatarCustomerName, setAvatarCustomerName] = useState('');
const [avatarLoading, setAvatarLoading] = useState(false);
const [customerAvatars, setCustomerAvatars] = useState({});
const isAllCustomersPage = title === 'All Customers';
const [columns, setColumns] = useState([
{
key: 'name',
@@ -98,26 +96,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
label: 'Fasting',
show: true
},
{
key: 'health_condition',
label: 'Health Condition',
show: true
},
{
key: 'payment_status',
label: 'Payment Status',
show: true
},
{
key: 'payment_due_date',
label: 'Payment Due Date',
show: true
},
{
key: 'service_requirement',
label: 'Service Requirement',
show: true
},
{
key: 'tags',
label: 'Tags',
@@ -183,21 +161,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
filtered = filtered.filter(item => item.status === 'active' && item.type !== CUSTOMER_TYPE.TRANSFERRED && item.type !== CUSTOMER_TYPE.DECEASED && item.type !== CUSTOMER_TYPE.DISCHARGED);
}
// Health condition filter
if (healthConditionFilter) {
filtered = filtered.filter(item => item?.health_condition === healthConditionFilter);
}
// Payment status filter
if (paymentStatusFilter) {
filtered = filtered.filter(item => item?.payment_status === paymentStatusFilter);
}
// Service requirement filter
if (serviceRequirementFilter) {
filtered = filtered.filter(item => item?.service_requirement === serviceRequirementFilter);
}
// Tags filter
if (tagsFilter.length > 0) {
filtered = filtered.filter(item => {
@@ -207,7 +170,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
}
setFilteredCustomers(filtered);
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
}, [keyword, customers, showInactive, tagsFilter])
useEffect(() => {
const newCustomers = [...customers];
@@ -256,9 +219,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
}
const cleanFilterAndClose = () => {
setHealthConditionFilter('');
setPaymentStatusFilter('');
setServiceRequirementFilter('');
setTagsFilter([]);
setShowFilterDropdown(false);
}
@@ -360,7 +320,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
/>
)
)}
{AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable" onClick={() => goToView(customer?.id)} style={{ flexShrink: 0 }}></PencilSquare>}
{AuthService.canAddOrEditCustomers() && <PencilSquare size={16} className="clickable" onClick={() => isAllCustomersPage ? goToEdit(customer?.id) : goToView(customer?.id)} style={{ flexShrink: 0 }}></PencilSquare>}
<span className="clickable" style={{ color: '#0066B1', textDecoration: 'underline', cursor: 'pointer' }} onClick={() => goToView(customer?.id)}>{customer?.name}</span>
</div>
</td>}
@@ -376,10 +336,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
{columns.find(col => col.key === 'address')?.show && <td>{customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5}</td>}
{columns.find(col => col.key === 'phone')?.show && <td>{customer?.phone || customer?.home_phone || customer?.mobile_phone}</td>}
{columns.find(col => col.key === 'emergency_contact')?.show && <td>{customer?.emergency_contact}</td>}
{columns.find(col => col.key === 'health_condition')?.show && <td>{customer?.health_condition}</td>}
{columns.find(col => col.key === 'payment_status')?.show && <td>{customer?.payment_status}</td>}
{columns.find(col => col.key === 'payment_due_date')?.show && <td>{customer?.payment_due_date}</td>}
{columns.find(col => col.key === 'service_requirement')?.show && <td>{customer?.service_requirement}</td>}
{columns.find(col => col.key === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
</tr>)
}
@@ -398,38 +354,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
aria-labelledby={labeledBy}
>
<h6>Filter By</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Health Condition</div>
<select value={healthConditionFilter} onChange={(e) => setHealthConditionFilter(e.target.value)}>
<option value=""></option>
<option value="diabetes">Diabetes</option>
<option value="1-1">1-1</option>
<option value="rounding list">Rounding List</option>
<option value="MOLST/POA/Advanced Directive">MOLST/POA/Advanced Directive</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Payment Status</div>
<select value={paymentStatusFilter} onChange={(e) => setPaymentStatusFilter(e.target.value)}>
<option value=""></option>
<option value="paid">Paid</option>
<option value="overdue">Overdue</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Service Requirement</div>
<select value={serviceRequirementFilter} onChange={(e) => setServiceRequirementFilter(e.target.value)}>
<option value=""></option>
<option value="wheelchair">Wheelchair</option>
<option value="special care">Special Care</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Tags</div>

View File

@@ -145,6 +145,15 @@ const EventsCalendar = () => {
eventsDateMap.set(dateString, value);
}
}
// Sort each day by earliest start time first
eventsDateMap.forEach((items, key) => {
const sortedItems = [...items].sort((a, b) => {
const aTime = moment(a?.start_time).valueOf();
const bTime = moment(b?.start_time).valueOf();
return aTime - bTime;
});
eventsDateMap.set(key, sortedItems);
});
return eventsDateMap;
};

View File

@@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom';
const SideMenu = () => {
const SIDEBAR_COLLAPSE_STORAGE_KEY = 'globalSidebarIsCollapsed';
const sideNavs = [
{
icon: <Grid1x2 color="#777" size={14}/>,
@@ -230,6 +231,17 @@ const SideMenu = () => {
navigate(link);
}
useEffect(() => {
const savedValue = sessionStorage.getItem(SIDEBAR_COLLAPSE_STORAGE_KEY);
if (savedValue === 'true' || savedValue === 'false') {
setCollapse(savedValue === 'true');
}
}, []);
useEffect(() => {
sessionStorage.setItem(SIDEBAR_COLLAPSE_STORAGE_KEY, String(collapse));
}, [collapse]);
return (
<>
<div className={`app-side-bar-container${collapse ? ' collapsed' : ''} noprint`}>

View File

@@ -61,6 +61,55 @@ const InfoScreen = () => {
const [showBackgroundModal, setShowBackgroundModal] = useState(false);
const [selectedBackgroundFile, setSelectedBackgroundFile] = useState(null);
// Expand a recurring rule into instances within a date range
const expandRecurrence = (rule, rangeFrom, rangeTo) => {
const instances = [];
const startDate = new Date(rule.start_repeat_date);
const endDate = new Date(rule.end_repeat_date);
const from = new Date(rangeFrom);
const to = new Date(rangeTo);
const effectiveStart = from > startDate ? from : startDate;
const effectiveEnd = to < endDate ? to : endDate;
if (effectiveStart > effectiveEnd) return instances;
const freq = rule.rrule;
let current = new Date(startDate);
let count = 0;
while (current <= effectiveEnd && count < 1000) {
if (current >= effectiveStart) {
const dateStr = moment(current).format('YYYY-MM-DD');
instances.push({
...rule,
id: `recur-${rule.id}-${dateStr}`,
_recur_id: rule.id,
start_time: new Date(current),
stop_time: new Date(current),
});
}
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 instances;
};
const redirectTo = () => {
navigate('/dashboard/dashboard');
}
@@ -505,91 +554,88 @@ const InfoScreen = () => {
useEffect(() => {
if (customers?.length > 0 && resources?.length > 0) {
// Get today's date in the format expected by the API
const today = moment().format('YYYY-MM-DD');
const now = moment();
const fromDate = new Date(now.year(), now.month(), 1);
const toDate = new Date(now.year(), now.month() + 1, 0);
EventsService.getAllEvents({ date: today }).then((data) => {
// Filter medical events
const medicalEventsData = data.data.filter((item) => {
// Filter for medical events that are confirmed and active
if (item.type !== 'medical' || !item.confirmed || item.status !== 'active') {
return false;
}
Promise.all([
EventsService.getAllEvents({
from: EventsService.formatDate(fromDate),
to: EventsService.formatDate(toDate)
}),
EventsService.getAllEventRecurrences()
]).then(([eventsRes, recurRes]) => {
const allEvents = eventsRes?.data || [];
const recurrenceRules = (recurRes?.data || []).filter(
rule => ['medical', 'activity', 'meal_plan'].includes(rule.type) && rule.status === 'active'
);
const recurInstances = recurrenceRules.flatMap(rule => expandRecurrence(rule, fromDate, toDate));
const mergedEvents = [...allEvents, ...recurInstances];
// Add customer name
item.customer = item?.data?.customer ?
(customers.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') :
(item?.data?.client_name || '');
// Add driver info (from linked transportation event)
item.driverInfo = item?.link_event_name || '';
// Format start time
item.startTime = item?.start_time ?
moment(item.start_time).format('MM/DD/YYYY h:mm A') : '';
return true;
const todayEvents = mergedEvents.filter((item) => {
if (!item?.start_time) return false;
return moment(item.start_time).isSame(now, 'day');
});
// Filter medical events
const medicalEventsData = todayEvents
.filter((item) => item.type === 'medical' && item.confirmed && item.status === 'active')
.map((item) => ({
...item,
customer: item?.data?.customer
? (customers.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '')
: (item?.data?.client_name || ''),
driverInfo: item?.link_event_name || '',
startTime: item?.start_time ? moment(item.start_time).format('MM/DD/YYYY h:mm A') : '',
}));
// Filter activity events
const activityEventsData = data.data.filter((item) => {
// Filter for activity events that are active
if (item.type !== 'activity' || item.status !== 'active') {
return false;
}
// Format start time
item.startTime = item?.start_time ?
moment(item.start_time).format('h:mm A') : '';
return true;
});
const activityEventsData = todayEvents
.filter((item) => item.type === 'activity' && item.status === 'active')
.map((item) => ({
...item,
startTime: item?.start_time ? moment(item.start_time).format('h:mm A') : '',
}));
// Filter meal events and parse meal information
const mealEventsData = data.data.filter((item) => {
// Filter for meal_plan events that are active
if (item.type !== 'meal_plan' || item.status !== 'active') {
return false;
}
const mealEventsData = todayEvents
.filter((item) => item.type === 'meal_plan' && item.status === 'active')
.map((item) => {
const description = item.description || '';
// Parse meal information from description
const description = item.description || '';
// Parse snack first (last in description)
let snack = '';
if (description.includes('Snack') || description.includes('snack')) {
const snackParts = description.split(/Snack|snack/);
if (snackParts.length > 1) {
snack = snackParts[1].replace(/:/g, '').trim();
let snack = '';
if (description.includes('Snack') || description.includes('snack')) {
const snackParts = description.split(/Snack|snack/);
if (snackParts.length > 1) {
snack = snackParts[1].replace(/:/g, '').trim();
}
}
}
// Parse lunch from the first part (before snack)
let lunch = '';
const firstPart = description.split(/Snack|snack/)[0];
if (firstPart.includes('Lunch') || firstPart.includes('lunch')) {
const lunchParts = firstPart.split(/Lunch|lunch/);
if (lunchParts.length > 1) {
lunch = lunchParts[1].replace(/:/g, '').trim();
let lunch = '';
const firstPart = description.split(/Snack|snack/)[0];
if (firstPart.includes('Lunch') || firstPart.includes('lunch')) {
const lunchParts = firstPart.split(/Lunch|lunch/);
if (lunchParts.length > 1) {
lunch = lunchParts[1].replace(/:/g, '').trim();
}
}
}
// Parse breakfast from the first part (before lunch)
let breakfast = '';
const secondPart = firstPart.split(/Lunch|lunch/)[0];
if (secondPart.includes('Breakfast') || secondPart.includes('breakfast')) {
const breakfastParts = secondPart.split(/Breakfast|breakfast/);
if (breakfastParts.length > 1) {
breakfast = breakfastParts[1].replace(/:/g, '').trim();
let breakfast = '';
const secondPart = firstPart.split(/Lunch|lunch/)[0];
if (secondPart.includes('Breakfast') || secondPart.includes('breakfast')) {
const breakfastParts = secondPart.split(/Breakfast|breakfast/);
if (breakfastParts.length > 1) {
breakfast = breakfastParts[1].replace(/:/g, '').trim();
}
}
}
item.breakfast = breakfast;
item.lunch = lunch;
item.snack = snack;
return true;
});
return {
...item,
breakfast,
lunch,
snack,
};
});
setMedicalEvents(medicalEventsData);
setActivityEvents(activityEventsData);

View File

@@ -779,7 +779,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
.sort((a, b) => a.customer_name.replace(' ', '') > b.customer_name.replace(' ', '') ? 1: -1 )
.map((customer, index) => {
return (<tr key={index}>
<td className="td-index"> {index}</td>
<td className="td-index"> {index + 1}</td>
<td>
{ customer.customer_name}
</td>

View File

@@ -121,6 +121,42 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
return assignedIds;
};
const formatStructuredAddress = (line1, line2, city, state, zipCode) => {
const cityState = [city, state].filter(Boolean).join(', ');
return [line1, line2, cityState, zipCode]
.filter(item => item && String(item).trim() !== '')
.join(' ')
.trim();
};
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 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 !== '')
)
);
};
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
@@ -224,10 +260,11 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
const toggleItemToRouteList = (customer, value) => {
if (value === 'false') {
const customerAddresses = getCustomerAddressOptions(customer);
setNewRouteCustomerList([].concat(newRouteCustomerList).concat([{
customer_id: customer.id,
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
customer_address: customer.address1,
customer_address: customerAddresses[0] || '',
customer_avatar: customer.avatar,
customer_type: customer.type,
customer_pickup_status: customer.pickup_status,
@@ -246,10 +283,11 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
const toggleGroupedItemToRouteList = (customer, value) => {
if (value === 'false') {
const customerAddresses = getCustomerAddressOptions(customer);
setNewRouteGroupedCustomerList([].concat(newRouteGroupedCustomerList).concat([{
customer_id: customer.id,
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
customer_address: customer.address1,
customer_address: customerAddresses[0] || '',
customer_avatar: customer.avatar,
customer_group: newGroupName,
customer_group_address: newGroupAddress,
@@ -344,7 +382,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
const newCustomer = {
customer_id: customerData.id,
customer_name: `${customerData.name} ${customerData.name_cn?.length > 0 ? `(${customerData.name_cn})` : ``}`,
customer_address: customerData.address1 || '',
customer_address: getCustomerAddressOptions(customerData)[0] || '',
customer_avatar: customerData.avatar,
customer_type: customerData.type,
customer_pickup_status: customerData.pickup_status,
@@ -480,38 +518,46 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
const Items = ({ currentItems }) => {
return currentItems?.map(
(customer) => <div key={customer.id} className="option-item">
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
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>
{newRouteCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{customer.address1 && customer.address1 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address1} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address1}/><small>{customer.address1}</small></div>}
{customer.address2 && customer.address2 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address2} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address2}/><small>{customer.address2}</small></div>}
{customer.address3 && customer.address3 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address3} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address3}/><small>{customer.address3}</small></div>}
{customer.address4 && customer.address4 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address4} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address4}/><small>{customer.address4}</small></div>}
{customer.address5 && customer.address5 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address5} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address5}/><small>{customer.address5}</small></div>}
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-address-${idx}`}>
<input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setCustomerAddress(customer.id, e.currentTarget.value)} value={address} checked={newRouteCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===address}/>
<small>{address}</small>
</div>
))}
</div>)}
</div>
</div>
}
)
};
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(
(customer) => <div key={customer.id} className="option-item">
(customer) => {
const addressOptions = getCustomerAddressOptions(customer);
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>
{newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id) && (<div>
{customer.address1 && customer.address1 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address1} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address1}/><small>{customer.address1}</small></div>}
{customer.address2 && customer.address2 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address2} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address2}/><small>{customer.address2}</small></div>}
{customer.address3 && customer.address3 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address3} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address3}/><small>{customer.address3}</small></div>}
{customer.address4 && customer.address4 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address4} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address4}/><small>{customer.address4}</small></div>}
{customer.address5 && customer.address5 !== '' && <div><input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={customer.address5} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===customer.address5}/><small>{customer.address5}</small></div>}
{addressOptions.map((address, idx) => (
<div key={`${customer.id}-group-address-${idx}`}>
<input className="me-4" name={`${customer.id}-address`} type="radio" onChange={(e) => setGroupedCustomerAddress(customer.id, e.currentTarget.value)} value={address} checked={newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id)?.customer_address===address}/>
<small>{address}</small>
</div>
))}
</div>)}
</div>
</div>
}
)
}

View File

@@ -1,8 +1,8 @@
import React, {useEffect, useState, useRef} from "react";
import { useSelector,useDispatch } from "react-redux";
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 { selectAllRoutes, transRoutesSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
import { Breadcrumb, Tabs, Tab } from "react-bootstrap";
import RouteCustomerEditor from "./RouteCustomerEditor";
import { AuthService, TransRoutesService, CustomerService, EventsService } from "../../services";
import TimePicker from 'react-time-picker';
@@ -26,15 +26,10 @@ const RouteEdit = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { updateRoute} = transRoutesSlice.actions;
const { updateVehicle} = vehicleSlice.actions;
const [routeName, setRouteName] = useState('');
const [newDriver, setNewDriver] = useState('');
const [newVehicle, setNewVehicle] = useState('');
const [newRouteType, setNewRouteType] = useState('');
const [showAddCheckItem, setShowAddCheckItem] = useState(false);
const [showCopyCheckItem, setShowCopyCheckItem] = useState(false);
const [newChecklistItems, setNewChecklistItems] = useState([]);
const [selectedRouteChecklistToCopy, setSelectedRouteChecklistToCopy] = useState({});
const [newCustomerList, setNewCustomerList] = useState([]);
const [errorMessage, setErrorMessage] = useState(undefined);
const [estimatedStartTime, setEstimatedStartTime] = useState(undefined);
@@ -166,42 +161,6 @@ const RouteEdit = () => {
}
}
const addItemToArray = () => {
const arr = [...newChecklistItems, ''];
setNewChecklistItems(arr);
}
const saveChecklistItems = () => {
const data = Object.assign({}, currentVehicle, {checklist: newChecklistItems});
dispatch(updateVehicle({ id: currentVehicle.id, data }));
setShowAddCheckItem(false);
}
const copyChecklistItems = () => {
const data = Object.assign({}, currentVehicle, {checklist: vehicles.find(vehicle => vehicle.id === selectedRouteChecklistToCopy.vehicle)?.checklist});
dispatch(updateVehicle({ id: currentVehicle.id, data }));
setShowCopyCheckItem(false);
}
const closeAddCheckItemModal = () => {
setNewChecklistItems([])
setShowAddCheckItem(false);
}
const showAddCheckItemModal = () => {
setNewChecklistItems(currentVehicle.checklist || [])
setShowAddCheckItem(true);
}
const closeCopyCheckItemModal = () => {
setSelectedRouteChecklistToCopy({});
setShowCopyCheckItem(false);
}
const showCopyCheckItemModal = () => {
setShowCopyCheckItem(true);
}
const combineDateAndTime = (date, time) => {
const dateObj = moment(date);
const timeObj = moment(time, 'HH:mm');
@@ -489,13 +448,11 @@ const RouteEdit = () => {
<div className="me-4">
<div className="field-label">Vehicle Checklist
</div>
{ currentVehicle?.checklist?.length > 0 && (<table className="mb-4">
{ vehicles.find(item => item.id === newVehicle)?.checklist?.length > 0 && (<table className="mb-4">
<tbody>
{currentVehicle.checklist.map((item, index) => (<tr key={index}><td>{item}</td></tr>))}
{vehicles.find(item => item.id === newVehicle)?.checklist?.map((item, index) => (<tr key={index}><td>{item}</td></tr>))}
</tbody>
</table>) }
<div className="mb-4"><button className="btn btn-link btn-sm" onClick={() => showAddCheckItemModal()}>+Add Check Items</button></div>
<div className="mb-4"><button className="btn btn-link btn-sm" onClick={() => showCopyCheckItemModal()}>Copy Checklist From Other Route</button></div>
</div>
</div>
<div className="list row mb-5">
@@ -696,52 +653,6 @@ const RouteEdit = () => {
</div>
<Modal show={showAddCheckItem} onHide={() => closeAddCheckItemModal()}>
<Modal.Header closeButton>
<Modal.Title>Add New Checklist Item</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
{newChecklistItems?.map((item, index) => (<div className="mb-4" key={index}><input type="text" value={item} onChange={(e) => setNewChecklistItems([...newChecklistItems].map((a, index1) => {if (index1 === index) {return e.target.value;} return a;}))}/>
<button className="btn btn-link btn-sm" onClick={(e) => setNewChecklistItems([...newChecklistItems].filter((value, index1) => index1 != index))}>Remove</button>
</div>))}
<button className="btn btn-link" onClick={() => addItemToArray()}>+Add New Item</button>
</>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => closeAddCheckItemModal()}>
Close
</Button>
<Button variant="primary" onClick={() => saveChecklistItems()}>
Save Checklist Items
</Button>
</Modal.Footer>
</Modal>
<Modal show={showCopyCheckItem} onHide={() => closeCopyCheckItemModal()}>
<Modal.Header closeButton>
<Modal.Title> Click on Route to Select</Modal.Title>
</Modal.Header>
<Modal.Body>
<>
{[...allRoutes, ...tomorrowRoutes].filter(r => r.id !== currentRoute?.id).map((route) => {
return (<div className={`card-container ${route.id === selectedRouteChecklistToCopy.id ? 'selected': ''}`} key={route.id} onClick={() => setSelectedRouteChecklistToCopy(route)}>
<div>{route.name}</div>
<div>
{vehicles.find((a) => a.id === route.vehicle)?.checklist?.map((item, index) => <small key={index} className="me-2">{item}</small>)}
</div>
</div>);
})}
</>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => closeCopyCheckItemModal()}>
Close
</Button>
<Button variant="primary" onClick={() => copyChecklistItems()}>
Copy Checklist Items
</Button>
</Modal.Footer>
</Modal>
</>
);

View File

@@ -135,10 +135,6 @@ const RouteView = () => {
<div className="field-label">Route End Time</div>
<div className="field-value">{currentRoute?.end_time && (new Date(currentRoute?.end_time))?.toLocaleTimeString()}</div>
</div>
{currentRoute?.type === 'inbound' &&<div className="field-body">
<div className="field-label">Arrive Center Time</div>
<div className="field-value">{currentRoute?.end_time && (new Date(currentRoute?.end_time))?.toLocaleTimeString()}</div>
</div>}
{currentRoute?.type === 'outbound' &&<div className="field-body">
<div className="field-label">Leave Center Time</div>
<div className="field-value">{currentRoute?.start_time && (new Date(currentRoute?.start_time))?.toLocaleTimeString()}</div>

View File

@@ -1271,62 +1271,6 @@ const RoutesDashboard = () => {
}
</tbody>
</table>
<hr></hr>
<h6 className="text-primary">{moment(dateSelected)?.format('MM/DD/YYYY')}</h6>
<div style={{'display': 'flex'}}>
<div>
<table className="personnel-info-table">
<thead>
<tr>
<th>Index</th>
<th>Customer Name</th>
<th>Pickup Time</th>
<th>Enter Center Time</th>
<th>Leave Center Time</th>
<th>Drop off TIme</th>
<th>MA Number</th>
<th>Inbound Name</th>
<th>Outbound Name</th>
<th>Total Hours</th>
</tr>
</thead>
<tbody>
{
getAllUniqueCustomers(routesForSignature
?.filter((route) => {
if (!selectedDriver) {
return route;
} else {
return route?.driver === selectedDriver;
}
}))?.map(({customer_name, customer_status_inbound, customer_status_outbound, customer_id, customer_enter_center_time, customer_dropoff_time, customer_leave_center_time, customer_pickup_time, inbound, outbound}, index) => {
return (<tr key={index}>
<td className="td-index">{index+1}</td>
<td>{customer_name}</td>
<td> <div style={{'padding': '4px 8px', 'border-radius': '8px', 'backgroundColor': `${customer_status_inbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_inbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_inbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_inbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT? (customer_pickup_time ? new Date(customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : '') : ''}</div></td>
<td> <div style={{'padding': '4px 8px', 'border-radius': '8px', 'backgroundColor': `${customer_status_inbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_inbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_inbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_inbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? (customer_enter_center_time ? new Date(customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''): ''}</div></td>
<td> <div style={{'padding': '4px 8px', 'border-radius': '8px', 'backgroundColor': `${customer_status_outbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_outbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_outbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_outbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? (customer_leave_center_time ? new Date(customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''): ''}</div></td>
<td> <div style={{'padding': '4px 8px', 'border-radius': '8px', 'backgroundColor': `${customer_status_outbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_outbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_outbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_outbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT? (customer_dropoff_time ? new Date(customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : ''): ''}</div></td>
<td>{customers.length > 0 && customers.find(c => c?.id === customer_id || c?.name === customer_name)?.medicaid_number || customers.find(c => c?.id === customer_id)?.medicare_number}</td>
<td>{inbound?.name || ''}</td>
<td>{outbound?.name || ''}</td>
<td>{customer_leave_center_time && customer_enter_center_time && Math.round((new Date(customer_leave_center_time) - new Date(customer_enter_center_time))/1000/3600) || ''}</td>
</tr>)
})
}
</tbody>
</table>
</div>
<div className="ms-4">
<div className="mb-4"><strong>Center Director Signature:</strong></div>
{directorSignature && <div className="mb-4"><img width="200px" src={`data:image/jpg;base64, ${directorSignature}`}/></div>}
{!directorSignature && <div className="mb-4">No Director Signature Uploaded yet</div>}
<div className="mb-4"><strong>Upload Center Director New Signature: </strong></div>
<div className="mb-4"><input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} className="form-control-file border"></input></div>
<div className="mb-4"><button onClick={() => uploadDirectorSignature()} className="btn btn-sm btn-primary">Submit</button></div>
</div>
</div>
</Tab>
<Tab eventKey="allRoutesStatus" title="All Routes Status">
<div className="list row">

View File

@@ -90,7 +90,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
{`${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)`)}</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') ? ` ${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()}}}>

View File

@@ -1,11 +1,11 @@
import React, {useState, useEffect} from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { CustomerService, DriverService, EmployeeService, EventsService, TransRoutesService } from "../../services";
import { CustomerService, DriverService, EventsService, TransRoutesService } from "../../services";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
import moment from 'moment';
import DatePicker from "react-datepicker";
import { PERSONAL_ROUTE_STATUS, ROUTE_STATUS } from "../../shared";
import { ROUTE_STATUS } from "../../shared";
const RouteSignatureList = () => {
const params = new URLSearchParams(window.location.search);
@@ -15,91 +15,20 @@ const RouteSignatureList = () => {
const navigate = useNavigate();
const [dateSelected, setDateSelected] = useState(new Date());
const [routes, setRoutes] = useState([]);
const [directorSignature, setDirectorSignature] = useState(undefined);
const [selectedFile, setSelectedFile] = useState();
const [selectedDriver, setSelectedDriver] = useState(undefined);
const [driverList, setDriverList] = useState([]);
const [customers, setCustomers] = useState([]);
const redirectToDashboard = () => {
navigate(`/trans-routes/dashboard`);
}
const uploadDirectorSignature = () => {
const formData = new FormData();
const site = EventsService.site;
formData.append("file", selectedFile);
if (selectedFile) {
if (directorSignature) {
CustomerService.deleteFile({'name': `center_director_signature_site_${site}`}).then(() => {
CustomerService.uploadAvatar(`center_director_signature_site_${site}`, formData).then(() => {
CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => {
if (data?.data) {
setDirectorSignature(data?.data)
}
});
})
})
} else {
CustomerService.uploadAvatar(`center_director_signature_site_${site}`, formData).then(() => {
CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => {
if (data?.data) {
setDirectorSignature(data?.data)
}
});
})
}
}
}
const getAllUniqueCustomers = (routes) => {
let result = [];
for (const route of routes) {
const customerList = route.route_customer_list.map(item => Object.assign({}, item, {routeType: route.type, routeId: route.id, route: route, customer_status_inbound: route.type === 'inbound' && item.customer_route_status, customer_status_outbound: route.type === 'outbound' && item.customer_route_status, inbound: route.type === 'inbound' && route, outbound: route.type === 'outbound' && route}))
for (const customer of customerList) {
const existItem = result.find((item => (item.customer_id === customer.customer_id) || (item?.customer_name?.replaceAll(' ', '')?.toLowerCase() === customer?.customer_name?.replaceAll(' ', '')?.toLowerCase()) ));
if (existItem) {
result = result.filter(item => item !== existItem);
const newItem = Object.assign({}, existItem, {
customer_enter_center_time: existItem?.customer_enter_center_time || customer?.customer_enter_center_time,
customer_leave_center_time: existItem?.customer_leave_center_time || customer?.customer_leave_center_time,
customer_pickup_time: existItem?.customer_pickup_time || customer?.customer_pickup_time,
customer_dropoff_time: existItem?.customer_dropoff_time || customer?.customer_dropoff_time,
inbound: existItem?.inbound || customer?.inbound,
outbound: existItem?.outbound || customer?.outbound,
customer_status_inbound: existItem?.customer_status_inbound || customer?.customer_status_inbound,
customer_status_outbound: existItem?.customer_status_outbound || customer?.customer_status_outbound
})
result.push(newItem);
} else {
result.push(customer);
}
}
}
return result.sort((a, b) => {
if (a.customer_name < b.customer_name) {
return -1;
} else {
return 1;
}
});
}
useEffect(() => {
DriverService.getAllActiveDrivers('driver', 'active').then((data) => {
setDriverList(data.data);
});
CustomerService.getAllCustomers().then((data) => setCustomers(data?.data));
}, []);
useEffect(() => {
const site = EventsService.site;
CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => {
if (data?.data) {
setDirectorSignature(data?.data)
}
});
TransRoutesService.getAll(moment(dateSelected)?.format('MM/DD/YYYY')).then(data => {
const routesResults = data.data;
const finalRoutes = routesResults.map(async (routeItem) => {
@@ -170,63 +99,6 @@ const RouteSignatureList = () => {
}
</tbody>
</table>
<hr></hr>
{moment(dateSelected)?.format('MM/DD/YYYY')}
<div style={{'display': 'flex'}}>
<div>
<table className="personnel-info-table">
<thead>
<tr>
<th>Index</th>
<th>Customer Name</th>
<th>Pickup Time</th>
<th>Enter Center Time</th>
<th>Leave Center Time</th>
<th>Drop off TIme</th>
<th>MA Number</th>
<th>Inbound Name</th>
<th>Outbound Name</th>
<th>Total Hours</th>
</tr>
</thead>
<tbody>
{
getAllUniqueCustomers(routes?.filter((route) => {
if (!selectedDriver) {
return route;
} else {
return route?.driver === selectedDriver;
}
}))?.map(({customer_name, customer_status_inbound, customer_status_outbound, customer_id, customer_enter_center_time, customer_dropoff_time, customer_leave_center_time, customer_pickup_time, inbound, outbound}, index) => {
console.log('customers', customers);
return (<tr key={index}>
<td>{index+1}</td>
<td>{customer_name}</td>
<td style={{'backgroundColor': `${customer_status_inbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_inbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_inbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_inbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT? (customer_pickup_time ? new Date(customer_pickup_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : '') : ''}</td>
<td style={{'backgroundColor': `${customer_status_inbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_inbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_inbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_inbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? (customer_enter_center_time ? new Date(customer_enter_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''): ''}</td>
<td style={{'backgroundColor': `${customer_status_outbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_outbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_outbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_outbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? (customer_leave_center_time ? new Date(customer_leave_center_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}): ''): ''}</td>
<td style={{'backgroundColor': `${customer_status_outbound === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT || customer_status_outbound === PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT ? 'red':'white'}`}}>{customer_status_outbound !== PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT && customer_status_outbound !== PERSONAL_ROUTE_STATUS.UNEXPECTED_ABSENT? (customer_dropoff_time ? new Date(customer_dropoff_time).toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}) : ''): ''}</td>
<td>{customers.length > 0 && customers.find(c => c?.id === customer_id || c?.name === customer_name)?.medicaid_number || customers.find(c => c?.id === customer_id)?.medicare_number}</td>
<td>{inbound?.name || ''}</td>
<td>{outbound?.name || ''}</td>
<td>{customer_leave_center_time && customer_enter_center_time && Math.round((new Date(customer_leave_center_time) - new Date(customer_enter_center_time))/1000/3600) || ''}</td>
</tr>)
})
}
</tbody>
</table>
</div>
<div className="ms-4">
<div className="mb-4"><strong>Center Director Signature:</strong></div>
{directorSignature && <div className="mb-4"><img width="200px" src={`data:image/jpg;base64, ${directorSignature}`}/></div>}
{!directorSignature && <div className="mb-4">No Director Signature Uploaded yet</div>}
<div className="mb-4"><strong>Upload Center Director New Signature: </strong></div>
<div className="mb-4"><input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} className="form-control-file border"></input></div>
<div className="mb-4"><button onClick={() => uploadDirectorSignature()} className="btn btn-sm btn-primary">Submit</button></div>
</div>
</div>
</div>
</div>
</>

View File

@@ -47,18 +47,25 @@ const AddVehicleInspection = () => {
};
const handleSave = () => {
if (!selectedFile || !inspectionDate) {
window.alert('Please select a date and a file.');
if (!inspectionDate) {
window.alert('Please select a date.');
return;
}
const formData = new FormData();
formData.append('file', selectedFile);
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
setSelectedFile(null);
setInspectionDate(null);
fetchFiles();
});
// File upload is optional on add page.
if (selectedFile) {
const formData = new FormData();
formData.append('file', selectedFile);
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
setSelectedFile(null);
setInspectionDate(null);
fetchFiles();
});
return;
}
goBack();
};
const goBack = () => {
@@ -119,7 +126,7 @@ const AddVehicleInspection = () => {
/>
</div>
<div className="me-4">
<div className="field-label">Inspection File</div>
<div className="field-label">Inspection File (Optional)</div>
<label className="custom-file-upload">
<Upload width={20} color={"#fff"} className="me-2" /> Upload
<input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} />

View File

@@ -3,7 +3,7 @@ import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { AuthService, VehicleService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
import { Columns, Download, Filter, PersonSquare, Plus } from "react-bootstrap-icons";
import { ManageTable, Export } from "../../shared/components";
const VehicleList = () => {
@@ -183,7 +183,7 @@ const VehicleList = () => {
filteredVehicles.map((vehicle, index) => <tr key={vehicle.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(vehicle.id)} onClick={()=>toggleItem(vehicle?.id)}/></td>
<td className="td-index">{index + 1}</td>
{columns.find(col => col.key === 'vehicle_number')?.show && <td> {AuthService.canAddOrEditVechiles() && <PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(vehicle?.id)}></PencilSquare>} {AuthService.canViewVechiles() ? <button className="btn btn-link btn-sm" onClick={() => goToView(vehicle?.id)}>{vehicle?.vehicle_number}</button> : vehicle?.vehicle_number } </td>}
{columns.find(col => col.key === 'vehicle_number')?.show && <td>{AuthService.canViewVechiles() ? <button className="btn btn-link btn-sm" onClick={() => goToView(vehicle?.id)}>{vehicle?.vehicle_number}</button> : vehicle?.vehicle_number }</td>}
{columns.find(col => col.key === 'tag')?.show && <td>{vehicle?.tag}</td>}
{columns.find(col => col.key === 'capacity')?.show && <td>{vehicle?.capacity}</td>}
{columns.find(col => col.key === 'mileage')?.show && <td>{vehicle?.mileage}</td>}

View File

@@ -5,6 +5,7 @@ const MultiSelectDropdown = ({
onChange,
options = [],
placeholder = 'Select...',
exclusiveOptionValue = null,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState('');
@@ -30,29 +31,42 @@ const MultiSelectDropdown = ({
}, []);
const selectedValues = (value || []).map(v => v.value);
const hasExclusiveSelected = !!exclusiveOptionValue && selectedValues.includes(exclusiveOptionValue);
const filteredOptions = search
? flatOptions.filter(opt => opt.label.toLowerCase().includes(search.toLowerCase()))
: flatOptions;
const allFilteredSelected = filteredOptions.length > 0 && filteredOptions.every(opt => selectedValues.includes(opt.value));
const isOptionDisabled = (opt) => (
hasExclusiveSelected && opt.value !== exclusiveOptionValue
);
const selectableFilteredOptions = filteredOptions.filter(opt => !isOptionDisabled(opt));
const allFilteredSelected = selectableFilteredOptions.length > 0
&& selectableFilteredOptions.every(opt => selectedValues.includes(opt.value));
const toggleOption = (opt) => {
if (isOptionDisabled(opt)) {
return;
}
const exists = selectedValues.includes(opt.value);
if (exists) {
onChange(value.filter(v => v.value !== opt.value));
} else {
if (exclusiveOptionValue && opt.value === exclusiveOptionValue) {
onChange([opt]);
return;
}
onChange([...value, opt]);
}
};
const toggleSelectAll = () => {
if (allFilteredSelected) {
const filteredValues = filteredOptions.map(o => o.value);
const filteredValues = selectableFilteredOptions.map(o => o.value);
onChange(value.filter(v => !filteredValues.includes(v.value)));
} else {
const currentValues = new Set(selectedValues);
const newItems = filteredOptions.filter(o => !currentValues.has(o.value));
const newItems = selectableFilteredOptions.filter(o => !currentValues.has(o.value));
onChange([...value, ...newItems]);
}
};
@@ -122,8 +136,9 @@ const MultiSelectDropdown = ({
{filteredOptions.map((opt) => (
<div
key={opt.value}
className={`multi-select-dropdown__option ${selectedValues.includes(opt.value) ? 'multi-select-dropdown__option--selected' : ''}`}
className={`multi-select-dropdown__option ${selectedValues.includes(opt.value) ? 'multi-select-dropdown__option--selected' : ''} ${isOptionDisabled(opt) ? 'multi-select-dropdown__option--disabled' : ''}`}
onClick={() => toggleOption(opt)}
style={isOptionDisabled(opt) ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
>
<input
type="checkbox"
@@ -131,6 +146,7 @@ const MultiSelectDropdown = ({
onChange={() => toggleOption(opt)}
onClick={(e) => e.stopPropagation()}
className="multi-select-dropdown__checkbox"
disabled={isOptionDisabled(opt)}
/>
<span>{opt.label}</span>
</div>