fix
This commit is contained in:
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
BIN
app/views/.DS_Store
vendored
BIN
app/views/.DS_Store
vendored
Binary file not shown.
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.46cc12be.css",
|
"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/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
||||||
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"main.46cc12be.css.map": "/static/css/main.46cc12be.css.map",
|
"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"
|
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.46cc12be.css",
|
"static/css/main.46cc12be.css",
|
||||||
"static/js/main.b35b44f5.js"
|
"static/js/main.c97709ee.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.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
3
app/views/static/js/main.c97709ee.js
Normal file
3
app/views/static/js/main.c97709ee.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
client/.DS_Store
vendored
BIN
client/.DS_Store
vendored
Binary file not shown.
@@ -301,6 +301,15 @@ const EventsCalendar = () => {
|
|||||||
eventsDateMap.set(dateString, value);
|
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;
|
return eventsDateMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ const CreateCustomer = () => {
|
|||||||
const [consentToTextMessages, setConsentToTextMessages] = useState('');
|
const [consentToTextMessages, setConsentToTextMessages] = useState('');
|
||||||
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
|
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
|
||||||
const [consentToMediaUse, setConsentToMediaUse] = useState('');
|
const [consentToMediaUse, setConsentToMediaUse] = useState('');
|
||||||
|
const selectableCustomerTypes = [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.VISITOR];
|
||||||
|
|
||||||
// Medical & Insurance - Providers
|
// Medical & Insurance - Providers
|
||||||
const [primaryCarePhysician, setPrimaryCarePhysician] = useState('');
|
const [primaryCarePhysician, setPrimaryCarePhysician] = useState('');
|
||||||
@@ -595,8 +596,8 @@ const CreateCustomer = () => {
|
|||||||
<div className="field-label">Customer Type</div>
|
<div className="field-label">Customer Type</div>
|
||||||
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
|
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
|
||||||
<option value="">Select...</option>
|
<option value="">Select...</option>
|
||||||
{Object.keys(CUSTOMER_TYPE).map(key => (
|
{selectableCustomerTypes.map((typeOption) => (
|
||||||
<option key={key} value={CUSTOMER_TYPE[key]}>{CUSTOMER_TYPE_TEXT[CUSTOMER_TYPE[key]]}</option>
|
<option key={typeOption} value={typeOption}>{CUSTOMER_TYPE_TEXT[typeOption] || typeOption}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -937,6 +938,7 @@ const CreateCustomer = () => {
|
|||||||
onChange={setDietaryRestrictions}
|
onChange={setDietaryRestrictions}
|
||||||
options={DIETARY_RESTRICTIONS_GROUPED}
|
options={DIETARY_RESTRICTIONS_GROUPED}
|
||||||
placeholder="e.g., No Pork"
|
placeholder="e.g., No Pork"
|
||||||
|
exclusiveOptionValue="regular"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ const UpdateCustomer = () => {
|
|||||||
const [dischargeConfirmReason, setDischargeConfirmReason] = useState('');
|
const [dischargeConfirmReason, setDischargeConfirmReason] = useState('');
|
||||||
const [dischargeConfirmReasonOther, setDischargeConfirmReasonOther] = useState('');
|
const [dischargeConfirmReasonOther, setDischargeConfirmReasonOther] = useState('');
|
||||||
const [isDischarging, setIsDischarging] = useState(false);
|
const [isDischarging, setIsDischarging] = useState(false);
|
||||||
|
const [isReactivating, setIsReactivating] = useState(false);
|
||||||
|
|
||||||
// Care & Services
|
// Care & Services
|
||||||
const [dietaryRestrictions, setDietaryRestrictions] = useState([]);
|
const [dietaryRestrictions, setDietaryRestrictions] = useState([]);
|
||||||
@@ -105,6 +106,8 @@ const UpdateCustomer = () => {
|
|||||||
const [consentToTextMessages, setConsentToTextMessages] = useState('');
|
const [consentToTextMessages, setConsentToTextMessages] = useState('');
|
||||||
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
|
const [preferredTextLanguage, setPreferredTextLanguage] = useState('');
|
||||||
const [consentToMediaUse, setConsentToMediaUse] = useState('');
|
const [consentToMediaUse, setConsentToMediaUse] = useState('');
|
||||||
|
const selectableCustomerTypes = [CUSTOMER_TYPE.MEMBER, CUSTOMER_TYPE.VISITOR];
|
||||||
|
const hasLegacyCustomerType = !!customerType && !selectableCustomerTypes.includes(customerType);
|
||||||
|
|
||||||
// Medical & Insurance - Providers
|
// Medical & Insurance - Providers
|
||||||
const [primaryCarePhysician, setPrimaryCarePhysician] = useState('');
|
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) => {
|
const onPharmacyChange = (selectedPharmacy) => {
|
||||||
setPharmacy(selectedPharmacy);
|
setPharmacy(selectedPharmacy);
|
||||||
setPharmacyId(selectedPharmacy?.value);
|
setPharmacyId(selectedPharmacy?.value);
|
||||||
@@ -933,8 +966,11 @@ const UpdateCustomer = () => {
|
|||||||
<div className="field-label">Customer Type</div>
|
<div className="field-label">Customer Type</div>
|
||||||
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
|
<select value={customerType} onChange={e => setCustomerType(e.target.value)}>
|
||||||
<option value="">Select...</option>
|
<option value="">Select...</option>
|
||||||
{Object.keys(CUSTOMER_TYPE).map(key => (
|
{hasLegacyCustomerType && (
|
||||||
<option key={key} value={CUSTOMER_TYPE[key]}>{CUSTOMER_TYPE_TEXT[CUSTOMER_TYPE[key]]}</option>
|
<option value={customerType}>{CUSTOMER_TYPE_TEXT[customerType] || customerType}</option>
|
||||||
|
)}
|
||||||
|
{selectableCustomerTypes.map((typeOption) => (
|
||||||
|
<option key={typeOption} value={typeOption}>{CUSTOMER_TYPE_TEXT[typeOption] || typeOption}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1072,7 +1108,14 @@ const UpdateCustomer = () => {
|
|||||||
<div className="app-main-content-fields-section">
|
<div className="app-main-content-fields-section">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Address Line 1 {index === 0 && <span className="required">*</span>}</div>
|
<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>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Address Line 2</div>
|
<div className="field-label">Address Line 2</div>
|
||||||
@@ -1269,6 +1312,13 @@ const UpdateCustomer = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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="list row mb-5">
|
||||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||||
@@ -1290,6 +1340,7 @@ const UpdateCustomer = () => {
|
|||||||
onChange={setDietaryRestrictions}
|
onChange={setDietaryRestrictions}
|
||||||
options={DIETARY_RESTRICTIONS_GROUPED}
|
options={DIETARY_RESTRICTIONS_GROUPED}
|
||||||
placeholder="e.g., No Pork"
|
placeholder="e.g., No Pork"
|
||||||
|
exclusiveOptionValue="regular"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
|
|||||||
@@ -89,11 +89,17 @@ const Dashboard = () => {
|
|||||||
?.filter(item => item.status === 'active');
|
?.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
|
// Group events by date
|
||||||
const eventsDateMap = new Map();
|
const eventsDateMap = new Map();
|
||||||
processedEvents?.forEach(eventItem => {
|
sortedEvents?.forEach(eventItem => {
|
||||||
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
||||||
if (eventsDateMap.has(dateString)) {
|
if (eventsDateMap.has(dateString)) {
|
||||||
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
||||||
@@ -101,6 +107,16 @@ const Dashboard = () => {
|
|||||||
eventsDateMap.set(dateString, [eventItem]);
|
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);
|
setGroupedEvents(eventsDateMap);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching events:', error);
|
console.error('Error fetching events:', error);
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
||||||
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
|
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
|
||||||
const [showExportDropdown, setShowExportDropdown] = useState(false);
|
const [showExportDropdown, setShowExportDropdown] = useState(false);
|
||||||
const [healthConditionFilter, setHealthConditionFilter] = useState('');
|
|
||||||
const [paymentStatusFilter, setPaymentStatusFilter] = useState('');
|
|
||||||
const [serviceRequirementFilter, setServiceRequirementFilter] = useState('');
|
|
||||||
const [tagsFilter, setTagsFilter] = useState([]);
|
const [tagsFilter, setTagsFilter] = useState([]);
|
||||||
const [availableLabels, setAvailableLabels] = useState([]);
|
const [availableLabels, setAvailableLabels] = useState([]);
|
||||||
const [showAvatarModal, setShowAvatarModal] = useState(false);
|
const [showAvatarModal, setShowAvatarModal] = useState(false);
|
||||||
@@ -32,6 +29,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
const [avatarCustomerName, setAvatarCustomerName] = useState('');
|
const [avatarCustomerName, setAvatarCustomerName] = useState('');
|
||||||
const [avatarLoading, setAvatarLoading] = useState(false);
|
const [avatarLoading, setAvatarLoading] = useState(false);
|
||||||
const [customerAvatars, setCustomerAvatars] = useState({});
|
const [customerAvatars, setCustomerAvatars] = useState({});
|
||||||
|
const isAllCustomersPage = title === 'All Customers';
|
||||||
const [columns, setColumns] = useState([
|
const [columns, setColumns] = useState([
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@@ -98,26 +96,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
label: 'Fasting',
|
label: 'Fasting',
|
||||||
show: true
|
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',
|
key: 'tags',
|
||||||
label: '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);
|
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
|
// Tags filter
|
||||||
if (tagsFilter.length > 0) {
|
if (tagsFilter.length > 0) {
|
||||||
filtered = filtered.filter(item => {
|
filtered = filtered.filter(item => {
|
||||||
@@ -207,7 +170,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFilteredCustomers(filtered);
|
setFilteredCustomers(filtered);
|
||||||
}, [keyword, customers, showInactive, healthConditionFilter, paymentStatusFilter, serviceRequirementFilter, tagsFilter])
|
}, [keyword, customers, showInactive, tagsFilter])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newCustomers = [...customers];
|
const newCustomers = [...customers];
|
||||||
@@ -256,9 +219,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanFilterAndClose = () => {
|
const cleanFilterAndClose = () => {
|
||||||
setHealthConditionFilter('');
|
|
||||||
setPaymentStatusFilter('');
|
|
||||||
setServiceRequirementFilter('');
|
|
||||||
setTagsFilter([]);
|
setTagsFilter([]);
|
||||||
setShowFilterDropdown(false);
|
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>
|
<span className="clickable" style={{ color: '#0066B1', textDecoration: 'underline', cursor: 'pointer' }} onClick={() => goToView(customer?.id)}>{customer?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>}
|
</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 === '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 === '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 === '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>}
|
{columns.find(col => col.key === 'tags')?.show && <td>{customer?.tags?.join(', ')}</td>}
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
@@ -398,38 +354,6 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
|
|||||||
aria-labelledby={labeledBy}
|
aria-labelledby={labeledBy}
|
||||||
>
|
>
|
||||||
<h6>Filter By</h6>
|
<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="app-main-content-fields-section margin-sm dropdown-container">
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Tags</div>
|
<div className="field-label">Tags</div>
|
||||||
|
|||||||
@@ -145,6 +145,15 @@ const EventsCalendar = () => {
|
|||||||
eventsDateMap.set(dateString, value);
|
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;
|
return eventsDateMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
|
|
||||||
|
|
||||||
const SideMenu = () => {
|
const SideMenu = () => {
|
||||||
|
const SIDEBAR_COLLAPSE_STORAGE_KEY = 'globalSidebarIsCollapsed';
|
||||||
const sideNavs = [
|
const sideNavs = [
|
||||||
{
|
{
|
||||||
icon: <Grid1x2 color="#777" size={14}/>,
|
icon: <Grid1x2 color="#777" size={14}/>,
|
||||||
@@ -230,6 +231,17 @@ const SideMenu = () => {
|
|||||||
navigate(link);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`app-side-bar-container${collapse ? ' collapsed' : ''} noprint`}>
|
<div className={`app-side-bar-container${collapse ? ' collapsed' : ''} noprint`}>
|
||||||
|
|||||||
@@ -61,6 +61,55 @@ const InfoScreen = () => {
|
|||||||
const [showBackgroundModal, setShowBackgroundModal] = useState(false);
|
const [showBackgroundModal, setShowBackgroundModal] = useState(false);
|
||||||
const [selectedBackgroundFile, setSelectedBackgroundFile] = useState(null);
|
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 = () => {
|
const redirectTo = () => {
|
||||||
navigate('/dashboard/dashboard');
|
navigate('/dashboard/dashboard');
|
||||||
}
|
}
|
||||||
@@ -505,92 +554,89 @@ const InfoScreen = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customers?.length > 0 && resources?.length > 0) {
|
if (customers?.length > 0 && resources?.length > 0) {
|
||||||
// Get today's date in the format expected by the API
|
const now = moment();
|
||||||
const today = moment().format('YYYY-MM-DD');
|
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) => {
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
const todayEvents = mergedEvents.filter((item) => {
|
||||||
|
if (!item?.start_time) return false;
|
||||||
|
return moment(item.start_time).isSame(now, 'day');
|
||||||
|
});
|
||||||
|
|
||||||
// Filter medical events
|
// Filter medical events
|
||||||
const medicalEventsData = data.data.filter((item) => {
|
const medicalEventsData = todayEvents
|
||||||
// Filter for medical events that are confirmed and active
|
.filter((item) => item.type === 'medical' && item.confirmed && item.status === 'active')
|
||||||
if (item.type !== 'medical' || !item.confirmed || item.status !== 'active') {
|
.map((item) => ({
|
||||||
return false;
|
...item,
|
||||||
}
|
customer: item?.data?.customer
|
||||||
|
? (customers.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '')
|
||||||
// Add customer name
|
: (item?.data?.client_name || ''),
|
||||||
item.customer = item?.data?.customer ?
|
driverInfo: item?.link_event_name || '',
|
||||||
(customers.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') :
|
startTime: item?.start_time ? moment(item.start_time).format('MM/DD/YYYY h:mm A') : '',
|
||||||
(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;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter activity events
|
// Filter activity events
|
||||||
const activityEventsData = data.data.filter((item) => {
|
const activityEventsData = todayEvents
|
||||||
// Filter for activity events that are active
|
.filter((item) => item.type === 'activity' && item.status === 'active')
|
||||||
if (item.type !== 'activity' || item.status !== 'active') {
|
.map((item) => ({
|
||||||
return false;
|
...item,
|
||||||
}
|
startTime: item?.start_time ? moment(item.start_time).format('h:mm A') : '',
|
||||||
|
}));
|
||||||
// Format start time
|
|
||||||
item.startTime = item?.start_time ?
|
|
||||||
moment(item.start_time).format('h:mm A') : '';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter meal events and parse meal information
|
// Filter meal events and parse meal information
|
||||||
const mealEventsData = data.data.filter((item) => {
|
const mealEventsData = todayEvents
|
||||||
// Filter for meal_plan events that are active
|
.filter((item) => item.type === 'meal_plan' && item.status === 'active')
|
||||||
if (item.type !== 'meal_plan' || item.status !== 'active') {
|
.map((item) => {
|
||||||
return false;
|
const description = item.description || '';
|
||||||
}
|
|
||||||
|
let snack = '';
|
||||||
// Parse meal information from description
|
if (description.includes('Snack') || description.includes('snack')) {
|
||||||
const description = item.description || '';
|
const snackParts = description.split(/Snack|snack/);
|
||||||
|
if (snackParts.length > 1) {
|
||||||
// Parse snack first (last in description)
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let lunch = '';
|
||||||
// Parse lunch from the first part (before snack)
|
const firstPart = description.split(/Snack|snack/)[0];
|
||||||
let lunch = '';
|
if (firstPart.includes('Lunch') || firstPart.includes('lunch')) {
|
||||||
const firstPart = description.split(/Snack|snack/)[0];
|
const lunchParts = firstPart.split(/Lunch|lunch/);
|
||||||
if (firstPart.includes('Lunch') || firstPart.includes('lunch')) {
|
if (lunchParts.length > 1) {
|
||||||
const lunchParts = firstPart.split(/Lunch|lunch/);
|
lunch = lunchParts[1].replace(/:/g, '').trim();
|
||||||
if (lunchParts.length > 1) {
|
}
|
||||||
lunch = lunchParts[1].replace(/:/g, '').trim();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let breakfast = '';
|
||||||
// Parse breakfast from the first part (before lunch)
|
const secondPart = firstPart.split(/Lunch|lunch/)[0];
|
||||||
let breakfast = '';
|
if (secondPart.includes('Breakfast') || secondPart.includes('breakfast')) {
|
||||||
const secondPart = firstPart.split(/Lunch|lunch/)[0];
|
const breakfastParts = secondPart.split(/Breakfast|breakfast/);
|
||||||
if (secondPart.includes('Breakfast') || secondPart.includes('breakfast')) {
|
if (breakfastParts.length > 1) {
|
||||||
const breakfastParts = secondPart.split(/Breakfast|breakfast/);
|
breakfast = breakfastParts[1].replace(/:/g, '').trim();
|
||||||
if (breakfastParts.length > 1) {
|
}
|
||||||
breakfast = breakfastParts[1].replace(/:/g, '').trim();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return {
|
||||||
item.breakfast = breakfast;
|
...item,
|
||||||
item.lunch = lunch;
|
breakfast,
|
||||||
item.snack = snack;
|
lunch,
|
||||||
|
snack,
|
||||||
return true;
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setMedicalEvents(medicalEventsData);
|
setMedicalEvents(medicalEventsData);
|
||||||
setActivityEvents(activityEventsData);
|
setActivityEvents(activityEventsData);
|
||||||
setMealEvents(mealEventsData);
|
setMealEvents(mealEventsData);
|
||||||
|
|||||||
@@ -779,7 +779,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
|
|||||||
.sort((a, b) => a.customer_name.replace(' ', '') > b.customer_name.replace(' ', '') ? 1: -1 )
|
.sort((a, b) => a.customer_name.replace(' ', '') > b.customer_name.replace(' ', '') ? 1: -1 )
|
||||||
.map((customer, index) => {
|
.map((customer, index) => {
|
||||||
return (<tr key={index}>
|
return (<tr key={index}>
|
||||||
<td className="td-index"> {index}</td>
|
<td className="td-index"> {index + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
{ customer.customer_name}
|
{ customer.customer_name}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -121,6 +121,42 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
return assignedIds;
|
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(() => {
|
useEffect(() => {
|
||||||
// Fetch items from another resources.
|
// Fetch items from another resources.
|
||||||
const endOffset = itemOffset + itemsPerPage;
|
const endOffset = itemOffset + itemsPerPage;
|
||||||
@@ -224,10 +260,11 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
|
|
||||||
const toggleItemToRouteList = (customer, value) => {
|
const toggleItemToRouteList = (customer, value) => {
|
||||||
if (value === 'false') {
|
if (value === 'false') {
|
||||||
|
const customerAddresses = getCustomerAddressOptions(customer);
|
||||||
setNewRouteCustomerList([].concat(newRouteCustomerList).concat([{
|
setNewRouteCustomerList([].concat(newRouteCustomerList).concat([{
|
||||||
customer_id: customer.id,
|
customer_id: customer.id,
|
||||||
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
|
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_avatar: customer.avatar,
|
||||||
customer_type: customer.type,
|
customer_type: customer.type,
|
||||||
customer_pickup_status: customer.pickup_status,
|
customer_pickup_status: customer.pickup_status,
|
||||||
@@ -246,10 +283,11 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
|
|
||||||
const toggleGroupedItemToRouteList = (customer, value) => {
|
const toggleGroupedItemToRouteList = (customer, value) => {
|
||||||
if (value === 'false') {
|
if (value === 'false') {
|
||||||
|
const customerAddresses = getCustomerAddressOptions(customer);
|
||||||
setNewRouteGroupedCustomerList([].concat(newRouteGroupedCustomerList).concat([{
|
setNewRouteGroupedCustomerList([].concat(newRouteGroupedCustomerList).concat([{
|
||||||
customer_id: customer.id,
|
customer_id: customer.id,
|
||||||
customer_name: `${customer.name} ${customer.name_cn?.length > 0 ? `(${customer.name_cn})` : ``}`,
|
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_avatar: customer.avatar,
|
||||||
customer_group: newGroupName,
|
customer_group: newGroupName,
|
||||||
customer_group_address: newGroupAddress,
|
customer_group_address: newGroupAddress,
|
||||||
@@ -344,7 +382,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
const newCustomer = {
|
const newCustomer = {
|
||||||
customer_id: customerData.id,
|
customer_id: customerData.id,
|
||||||
customer_name: `${customerData.name} ${customerData.name_cn?.length > 0 ? `(${customerData.name_cn})` : ``}`,
|
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_avatar: customerData.avatar,
|
||||||
customer_type: customerData.type,
|
customer_type: customerData.type,
|
||||||
customer_pickup_status: customerData.pickup_status,
|
customer_pickup_status: customerData.pickup_status,
|
||||||
@@ -480,38 +518,46 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
|
|||||||
|
|
||||||
const Items = ({ currentItems }) => {
|
const Items = ({ currentItems }) => {
|
||||||
return currentItems?.map(
|
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)}/>
|
<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>
|
||||||
<div>{`${customer.name}(${customer.name_cn})`}</div>
|
<div>{`${customer.name}(${customer.name_cn})`}</div>
|
||||||
{newRouteCustomerList.find((item) => item.customer_id === customer.id) && (<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>}
|
{addressOptions.map((address, idx) => (
|
||||||
{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>}
|
<div key={`${customer.id}-address-${idx}`}>
|
||||||
{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>}
|
<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}/>
|
||||||
{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>}
|
<small>{address}</small>
|
||||||
{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>}
|
</div>
|
||||||
|
))}
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ItemsGroup = ({ currentItems }) => {
|
const ItemsGroup = ({ currentItems }) => {
|
||||||
const assignedIds = getAssignedCustomerIds();
|
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(
|
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)}/>
|
<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>
|
||||||
<div>{`${customer.name}(${customer.name_cn})`}</div>
|
<div>{`${customer.name}(${customer.name_cn})`}</div>
|
||||||
{newRouteGroupedCustomerList.find((item) => item.customer_id === customer.id) && (<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>}
|
{addressOptions.map((address, idx) => (
|
||||||
{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>}
|
<div key={`${customer.id}-group-address-${idx}`}>
|
||||||
{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>}
|
<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}/>
|
||||||
{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>}
|
<small>{address}</small>
|
||||||
{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>}
|
</div>
|
||||||
|
))}
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, {useEffect, useState, useRef} from "react";
|
import React, {useEffect, useState, useRef} from "react";
|
||||||
import { useSelector,useDispatch } from "react-redux";
|
import { useSelector,useDispatch } from "react-redux";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { selectAllRoutes, transRoutesSlice, vehicleSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
|
import { selectAllRoutes, transRoutesSlice, selectTomorrowAllRoutes, selectAllActiveDrivers, selectAllActiveVehicles, selectHistoryRoutes } from "./../../store";
|
||||||
import { Modal, Button, Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
import { Breadcrumb, Tabs, Tab } from "react-bootstrap";
|
||||||
import RouteCustomerEditor from "./RouteCustomerEditor";
|
import RouteCustomerEditor from "./RouteCustomerEditor";
|
||||||
import { AuthService, TransRoutesService, CustomerService, EventsService } from "../../services";
|
import { AuthService, TransRoutesService, CustomerService, EventsService } from "../../services";
|
||||||
import TimePicker from 'react-time-picker';
|
import TimePicker from 'react-time-picker';
|
||||||
@@ -26,15 +26,10 @@ const RouteEdit = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { updateRoute} = transRoutesSlice.actions;
|
const { updateRoute} = transRoutesSlice.actions;
|
||||||
const { updateVehicle} = vehicleSlice.actions;
|
|
||||||
const [routeName, setRouteName] = useState('');
|
const [routeName, setRouteName] = useState('');
|
||||||
const [newDriver, setNewDriver] = useState('');
|
const [newDriver, setNewDriver] = useState('');
|
||||||
const [newVehicle, setNewVehicle] = useState('');
|
const [newVehicle, setNewVehicle] = useState('');
|
||||||
const [newRouteType, setNewRouteType] = 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 [newCustomerList, setNewCustomerList] = useState([]);
|
||||||
const [errorMessage, setErrorMessage] = useState(undefined);
|
const [errorMessage, setErrorMessage] = useState(undefined);
|
||||||
const [estimatedStartTime, setEstimatedStartTime] = 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 combineDateAndTime = (date, time) => {
|
||||||
const dateObj = moment(date);
|
const dateObj = moment(date);
|
||||||
const timeObj = moment(time, 'HH:mm');
|
const timeObj = moment(time, 'HH:mm');
|
||||||
@@ -489,13 +448,11 @@ const RouteEdit = () => {
|
|||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Vehicle Checklist
|
<div className="field-label">Vehicle Checklist
|
||||||
</div>
|
</div>
|
||||||
{ currentVehicle?.checklist?.length > 0 && (<table className="mb-4">
|
{ vehicles.find(item => item.id === newVehicle)?.checklist?.length > 0 && (<table className="mb-4">
|
||||||
<tbody>
|
<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>
|
</tbody>
|
||||||
</table>) }
|
</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>
|
</div>
|
||||||
<div className="list row mb-5">
|
<div className="list row mb-5">
|
||||||
@@ -696,52 +653,6 @@ const RouteEdit = () => {
|
|||||||
</div>
|
</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>
|
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -135,10 +135,6 @@ const RouteView = () => {
|
|||||||
<div className="field-label">Route End Time</div>
|
<div className="field-label">Route End Time</div>
|
||||||
<div className="field-value">{currentRoute?.end_time && (new Date(currentRoute?.end_time))?.toLocaleTimeString()}</div>
|
<div className="field-value">{currentRoute?.end_time && (new Date(currentRoute?.end_time))?.toLocaleTimeString()}</div>
|
||||||
</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">
|
{currentRoute?.type === 'outbound' &&<div className="field-body">
|
||||||
<div className="field-label">Leave Center Time</div>
|
<div className="field-label">Leave Center Time</div>
|
||||||
<div className="field-value">{currentRoute?.start_time && (new Date(currentRoute?.start_time))?.toLocaleTimeString()}</div>
|
<div className="field-value">{currentRoute?.start_time && (new Date(currentRoute?.start_time))?.toLocaleTimeString()}</div>
|
||||||
|
|||||||
@@ -1271,62 +1271,6 @@ const RoutesDashboard = () => {
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
||||||
<Tab eventKey="allRoutesStatus" title="All Routes Status">
|
<Tab eventKey="allRoutesStatus" title="All Routes Status">
|
||||||
<div className="list row">
|
<div className="list row">
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const RoutesSection = ({transRoutes, copyList, sectionName, drivers, vehicles, c
|
|||||||
{`${sectionName}: `} <span className="route-stats">{
|
{`${sectionName}: `} <span className="route-stats">{
|
||||||
(sectionName.includes('Inbound') ||
|
(sectionName.includes('Inbound') ||
|
||||||
sectionName.includes('Outbound')) &&
|
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>
|
</h6>
|
||||||
{ canAddNew && (
|
{ canAddNew && (
|
||||||
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
<small className="me-4" onClick={() => { if (routeType) {redirect(routeType)} else {redirect()}}}>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect} from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
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 { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import { PERSONAL_ROUTE_STATUS, ROUTE_STATUS } from "../../shared";
|
import { ROUTE_STATUS } from "../../shared";
|
||||||
|
|
||||||
const RouteSignatureList = () => {
|
const RouteSignatureList = () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
@@ -15,91 +15,20 @@ const RouteSignatureList = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [dateSelected, setDateSelected] = useState(new Date());
|
const [dateSelected, setDateSelected] = useState(new Date());
|
||||||
const [routes, setRoutes] = useState([]);
|
const [routes, setRoutes] = useState([]);
|
||||||
const [directorSignature, setDirectorSignature] = useState(undefined);
|
|
||||||
const [selectedFile, setSelectedFile] = useState();
|
|
||||||
const [selectedDriver, setSelectedDriver] = useState(undefined);
|
const [selectedDriver, setSelectedDriver] = useState(undefined);
|
||||||
const [driverList, setDriverList] = useState([]);
|
const [driverList, setDriverList] = useState([]);
|
||||||
const [customers, setCustomers] = useState([]);
|
|
||||||
|
|
||||||
const redirectToDashboard = () => {
|
const redirectToDashboard = () => {
|
||||||
navigate(`/trans-routes/dashboard`);
|
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(() => {
|
useEffect(() => {
|
||||||
DriverService.getAllActiveDrivers('driver', 'active').then((data) => {
|
DriverService.getAllActiveDrivers('driver', 'active').then((data) => {
|
||||||
setDriverList(data.data);
|
setDriverList(data.data);
|
||||||
});
|
});
|
||||||
CustomerService.getAllCustomers().then((data) => setCustomers(data?.data));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
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 => {
|
TransRoutesService.getAll(moment(dateSelected)?.format('MM/DD/YYYY')).then(data => {
|
||||||
const routesResults = data.data;
|
const routesResults = data.data;
|
||||||
const finalRoutes = routesResults.map(async (routeItem) => {
|
const finalRoutes = routesResults.map(async (routeItem) => {
|
||||||
@@ -170,63 +99,6 @@ const RouteSignatureList = () => {
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -47,18 +47,25 @@ const AddVehicleInspection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!selectedFile || !inspectionDate) {
|
if (!inspectionDate) {
|
||||||
window.alert('Please select a date and a file.');
|
window.alert('Please select a date.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', selectedFile);
|
// File upload is optional on add page.
|
||||||
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
|
if (selectedFile) {
|
||||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
|
const formData = new FormData();
|
||||||
setSelectedFile(null);
|
formData.append('file', selectedFile);
|
||||||
setInspectionDate(null);
|
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
|
||||||
fetchFiles();
|
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
|
||||||
});
|
setSelectedFile(null);
|
||||||
|
setInspectionDate(null);
|
||||||
|
fetchFiles();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
@@ -119,7 +126,7 @@ const AddVehicleInspection = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="me-4">
|
<div className="me-4">
|
||||||
<div className="field-label">Inspection File</div>
|
<div className="field-label">Inspection File (Optional)</div>
|
||||||
<label className="custom-file-upload">
|
<label className="custom-file-upload">
|
||||||
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
||||||
<input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} />
|
<input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useDispatch } from "react-redux";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { AuthService, VehicleService } from "../../services";
|
import { AuthService, VehicleService } from "../../services";
|
||||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
|
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";
|
import { ManageTable, Export } from "../../shared/components";
|
||||||
|
|
||||||
const VehicleList = () => {
|
const VehicleList = () => {
|
||||||
@@ -183,7 +183,7 @@ const VehicleList = () => {
|
|||||||
filteredVehicles.map((vehicle, index) => <tr key={vehicle.id}>
|
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-checkbox"><input type="checkbox" checked={selectedItems.includes(vehicle.id)} onClick={()=>toggleItem(vehicle?.id)}/></td>
|
||||||
<td className="td-index">{index + 1}</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 === 'tag')?.show && <td>{vehicle?.tag}</td>}
|
||||||
{columns.find(col => col.key === 'capacity')?.show && <td>{vehicle?.capacity}</td>}
|
{columns.find(col => col.key === 'capacity')?.show && <td>{vehicle?.capacity}</td>}
|
||||||
{columns.find(col => col.key === 'mileage')?.show && <td>{vehicle?.mileage}</td>}
|
{columns.find(col => col.key === 'mileage')?.show && <td>{vehicle?.mileage}</td>}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const MultiSelectDropdown = ({
|
|||||||
onChange,
|
onChange,
|
||||||
options = [],
|
options = [],
|
||||||
placeholder = 'Select...',
|
placeholder = 'Select...',
|
||||||
|
exclusiveOptionValue = null,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@@ -30,29 +31,42 @@ const MultiSelectDropdown = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const selectedValues = (value || []).map(v => v.value);
|
const selectedValues = (value || []).map(v => v.value);
|
||||||
|
const hasExclusiveSelected = !!exclusiveOptionValue && selectedValues.includes(exclusiveOptionValue);
|
||||||
|
|
||||||
const filteredOptions = search
|
const filteredOptions = search
|
||||||
? flatOptions.filter(opt => opt.label.toLowerCase().includes(search.toLowerCase()))
|
? flatOptions.filter(opt => opt.label.toLowerCase().includes(search.toLowerCase()))
|
||||||
: flatOptions;
|
: 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) => {
|
const toggleOption = (opt) => {
|
||||||
|
if (isOptionDisabled(opt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const exists = selectedValues.includes(opt.value);
|
const exists = selectedValues.includes(opt.value);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
onChange(value.filter(v => v.value !== opt.value));
|
onChange(value.filter(v => v.value !== opt.value));
|
||||||
} else {
|
} else {
|
||||||
|
if (exclusiveOptionValue && opt.value === exclusiveOptionValue) {
|
||||||
|
onChange([opt]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
onChange([...value, opt]);
|
onChange([...value, opt]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleSelectAll = () => {
|
const toggleSelectAll = () => {
|
||||||
if (allFilteredSelected) {
|
if (allFilteredSelected) {
|
||||||
const filteredValues = filteredOptions.map(o => o.value);
|
const filteredValues = selectableFilteredOptions.map(o => o.value);
|
||||||
onChange(value.filter(v => !filteredValues.includes(v.value)));
|
onChange(value.filter(v => !filteredValues.includes(v.value)));
|
||||||
} else {
|
} else {
|
||||||
const currentValues = new Set(selectedValues);
|
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]);
|
onChange([...value, ...newItems]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -122,8 +136,9 @@ const MultiSelectDropdown = ({
|
|||||||
{filteredOptions.map((opt) => (
|
{filteredOptions.map((opt) => (
|
||||||
<div
|
<div
|
||||||
key={opt.value}
|
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)}
|
onClick={() => toggleOption(opt)}
|
||||||
|
style={isOptionDisabled(opt) ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -131,6 +146,7 @@ const MultiSelectDropdown = ({
|
|||||||
onChange={() => toggleOption(opt)}
|
onChange={() => toggleOption(opt)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
className="multi-select-dropdown__checkbox"
|
className="multi-select-dropdown__checkbox"
|
||||||
|
disabled={isOptionDisabled(opt)}
|
||||||
/>
|
/>
|
||||||
<span>{opt.label}</span>
|
<span>{opt.label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user