fix
All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 36s

This commit is contained in:
2026-03-12 15:05:16 -04:00
parent 240739e651
commit 21a378039c
11 changed files with 105 additions and 61 deletions

View File

@@ -30,6 +30,7 @@ const CustomersList = () => {
};
const getVisibleColumnsByPermission = (columnList = []) => {
return columnList.filter((column) => {
if (column.key === 'name') return true;
const mappedTab = CUSTOMER_LIST_COLUMN_TAB_MAP[column.key];
if (!mappedTab) return true;
return AuthService.canViewCustomerTab(mappedTab);
@@ -82,18 +83,20 @@ const CustomersList = () => {
{AuthService.canCreateCustomer() && <button className="btn btn-primary me-2" onClick={() => goToCreateNew()}>
<Plus size={16}></Plus>Add New Customer
</button>}
<Export
columns={columns}
data={customers.map((customer) => ({
...customer,
address: customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5,
phone: customer?.phone || customer?.home_phone || customer?.mobile_phone,
tags: customer?.tags?.join(', ')
}))}
filename="customers"
show={showExportDropdown}
onToggle={onExportToggle}
/>
{AuthService.canExportCustomerReport() && (
<Export
columns={columns}
data={customers.map((customer) => ({
...customer,
address: customer?.address1 || customer?.address2 || customer?.address3 || customer?.address4 || customer?.address5,
phone: customer?.phone || customer?.home_phone || customer?.mobile_phone,
tags: customer?.tags?.join(', ')
}))}
filename="customers"
show={showExportDropdown}
onToggle={onExportToggle}
/>
)}
</>
);

View File

@@ -27,11 +27,30 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
};
const getVisibleColumnsByPermission = (columnList = []) => {
return columnList.filter((column) => {
if (column.key === 'name') return true;
const mappedTab = CUSTOMER_LIST_COLUMN_TAB_MAP[column.key];
if (!mappedTab) return true;
return AuthService.canViewCustomerTab(mappedTab);
});
};
const CUSTOMER_FILTER_TAB_MAP = {
customerType: 'careServices',
programType: 'personalInfo',
paySource: 'personalInfo',
transportationType: 'careServices',
eyesOn: 'medicalInsurance'
};
const canViewFilterControl = (filterKey) => {
const mappedTab = CUSTOMER_FILTER_TAB_MAP[filterKey];
if (!mappedTab) return true;
return AuthService.canViewCustomerTab(mappedTab);
};
const visibleCustomerTypeFilter = canViewFilterControl('customerType');
const visibleProgramTypeFilter = canViewFilterControl('programType');
const visiblePaySourceFilter = canViewFilterControl('paySource');
const visibleTransportationTypeFilter = canViewFilterControl('transportationType');
const visibleEyesOnFilter = canViewFilterControl('eyesOn');
const hasVisibleFilters = visibleCustomerTypeFilter || visibleProgramTypeFilter || visiblePaySourceFilter || visibleTransportationTypeFilter || visibleEyesOnFilter;
const navigate = useNavigate();
const dispatch = useDispatch();
const site = EventsService.site;
@@ -192,23 +211,23 @@ 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);
}
if (customerTypeFilter) {
if (customerTypeFilter && visibleCustomerTypeFilter) {
filtered = filtered.filter((item) => (item?.type || '') === customerTypeFilter);
}
if (programTypeFilter) {
if (programTypeFilter && visibleProgramTypeFilter) {
filtered = filtered.filter((item) => (item?.program || '') === programTypeFilter);
}
if (paySourceFilter) {
if (paySourceFilter && visiblePaySourceFilter) {
filtered = filtered.filter((item) => (item?.pay_source || '') === paySourceFilter);
}
if (transportationTypeFilter) {
if (transportationTypeFilter && visibleTransportationTypeFilter) {
filtered = filtered.filter((item) => (item?.transportation_type || '') === transportationTypeFilter);
}
if (eyesOnFilter) {
if (eyesOnFilter && visibleEyesOnFilter) {
filtered = filtered.filter((item) => {
const normalized = `${item?.eyes_on || (item?.disability ? YES_NO.YES : '')}`.toLowerCase();
return normalized === eyesOnFilter;
@@ -411,7 +430,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
>
<h6>Filter By</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
{visibleCustomerTypeFilter && <div className="me-4">
<div className="field-label">Customer Type</div>
<select value={customerTypeFilter} onChange={(e) => setCustomerTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
@@ -419,8 +438,8 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
<div className="me-4">
</div>}
{visibleProgramTypeFilter && <div className="me-4">
<div className="field-label">Program Type</div>
<select value={programTypeFilter} onChange={(e) => setProgramTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
@@ -428,10 +447,10 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>}
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
{visiblePaySourceFilter && <div className="me-4">
<div className="field-label">Pay Source</div>
<select value={paySourceFilter} onChange={(e) => setPaySourceFilter(e.currentTarget.value)}>
<option value="">All</option>
@@ -439,8 +458,8 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
<div className="me-4">
</div>}
{visibleTransportationTypeFilter && <div className="me-4">
<div className="field-label">Tranportation Type</div>
<select value={transportationTypeFilter} onChange={(e) => setTransportationTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
@@ -448,10 +467,10 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>}
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
{visibleEyesOnFilter && <div className="me-4">
<div className="field-label">Eyes-On</div>
<select value={eyesOnFilter} onChange={(e) => setEyesOnFilter(e.currentTarget.value)}>
<option value="">All</option>
@@ -459,7 +478,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>}
</div>
<div className="list row">
<div className="col-md-12">
@@ -504,7 +523,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
<Dropdown
{hasVisibleFilters && <Dropdown
key={'filter-customers'}
id="filter-customers"
className="me-2"
@@ -522,7 +541,7 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
<Filter size={16} className="me-2"></Filter>Filter
</Dropdown.Toggle>
<Dropdown.Menu as={customFilterMenu}/>
</Dropdown>
</Dropdown>}
<ManageTable
columns={columns}
onColumnsChange={handleColumnsChange}

View File

@@ -2,13 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
import CircularTable from './CircularTable';
import { Breadcrumb, Tabs, Tab, Dropdown, Modal, Button, Form } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, ArrowBarRight, ChevronDown, ChevronRight, FileX, ArrowsFullscreen } from "react-bootstrap-icons";
import { CustomerService, LabelService, SeatingService, TransRoutesService } from '../../services';
import { AuthService, CustomerService, LabelService, SeatingService, TransRoutesService } from '../../services';
import { PERSONAL_ROUTE_STATUS } from '../../shared/constants';
import moment from 'moment';
import Select from 'react-select';
const Seating = () => {
const canManageSeatingChart = AuthService.canEditSeatingChart();
// OLD ROW-BASED INITIAL VALUE - COMMENTED OUT
// const initialValue = {
// rows: [{
@@ -973,7 +974,7 @@ const Seating = () => {
</div>
</div>
</div>
<div className="manage-seating-chart-container">
{canManageSeatingChart && <div className="manage-seating-chart-container">
<div className="manage-seating-chart-title-container">
<h6>Manage Seating Chart</h6>
{/* <ArrowBarRight color="#777" size={20}></ArrowBarRight> */}
@@ -1284,7 +1285,7 @@ const Seating = () => {
currentLabels.map((item) => <div className="mb-4" style={{fontSize: '12px', display: 'inline-flex', alignItems: 'center', marginRight: '16px'}} key={item.id}><span style={{width: '16px', height: '16px', borderRadius: '16px', background: item.label_color, display: 'inline-block', marginRight: '8px' }}></span>{item.label_name}</div>)
}
</div>
</div>
</div>}
</div>
)}

View File

@@ -3,7 +3,7 @@ import { TransRoutesService } from "../../services";
import moment from 'moment';
const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHasBreakfast, removeBreakfastRecord, selectedDate, refreshRecords}) => {
const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHasBreakfast, removeBreakfastRecord, selectedDate, refreshRecords, canEdit = false}) => {
const [customers, setCustomers] = useState([]);
const createBreakfastForAll = async () => {
@@ -40,7 +40,7 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
return (
<>
<div className="text-primary mb-4"><h5>{sectionName} {` (${customers?.length})`}</h5></div>
{customers?.some(c => !c.has_breakfast) && (<div className="mb-2 mt-2"><button onClick={() => createBreakfastForAll()} className="btn btn-primary btn-sm">Confirm All Breakfast</button></div>)}
{canEdit && customers?.some(c => !c.has_breakfast) && (<div className="mb-2 mt-2"><button onClick={() => createBreakfastForAll()} className="btn btn-primary btn-sm">Confirm All Breakfast</button></div>)}
<div className="list row">
<div className="col-md-12">
<table className="personnel-info-table">
@@ -48,7 +48,7 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
<tr>
<th>Name</th>
<th>Has Breakfast Today</th>
<th>Change Breakfast Status</th>
{canEdit && <th>Change Breakfast Status</th>}
</tr>
</thead>
<tbody>
@@ -57,10 +57,10 @@ const BreakfastSection = ({transRoutes, breakfastRecords, sectionName, confimHas
<tr key={customer?.customer_id} className={customer?.has_breakfast ? 'light-green' : 'red'}>
<td>{customer?.customer_name}</td>
<td>{customer?.has_breakfast ? 'Yes': 'No'}</td>
<td>
{canEdit && <td>
{!customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => confimHasBreakfast(customer)}>Confirm Customer Has breakfast</button>}
{customer?.has_breakfast && <button className="btn btn-link btn-sm" onClick={() => removeBreakfastRecord(customer?.customer_id)}>Mark Customer NOT have breakfast</button>}
</td>
</td>}
</tr>
))
}

View File

@@ -3,7 +3,7 @@ import { TransRoutesService } from "../../services";
import moment from 'moment';
const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch, removeLunchRecord, selectedDate, refreshRecords}) => {
const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch, removeLunchRecord, selectedDate, refreshRecords, canEdit = false}) => {
const [customers, setCustomers] = useState([]);
const createLunchForAll = async () => {
@@ -40,7 +40,7 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
return (
<>
<div className="text-primary mb-4"><h5>{sectionName} {` (${customers?.length})`}</h5></div>
{customers?.some(c => !c.has_lunch) && (<div className="mb-2 mt-2"><button onClick={() => createLunchForAll()} className="btn btn-primary btn-sm">Confirm All Lunch</button></div>)}
{canEdit && customers?.some(c => !c.has_lunch) && (<div className="mb-2 mt-2"><button onClick={() => createLunchForAll()} className="btn btn-primary btn-sm">Confirm All Lunch</button></div>)}
<div className="list row">
<div className="col-md-12">
<table className="personnel-info-table">
@@ -48,7 +48,7 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
<tr>
<th>Name</th>
<th>Has Lunch Today</th>
<th>Change Lunch Status</th>
{canEdit && <th>Change Lunch Status</th>}
</tr>
</thead>
<tbody>
@@ -57,10 +57,10 @@ const LunchSection = ({transRoutes, lunchRecords, sectionName, confirmHasLunch,
<tr key={customer?.customer_id} className={customer?.has_lunch ? 'light-green' : 'red'}>
<td>{customer?.customer_name}</td>
<td>{customer?.has_lunch ? 'Yes': 'No'}</td>
<td>
{canEdit && <td>
{!customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => confirmHasLunch(customer)}>Confirm Customer Has Lunch</button>}
{customer?.has_lunch && <button className="btn btn-link btn-sm" onClick={() => removeLunchRecord(customer?.customer_id)}>Mark Customer NOT have lunch</button>}
</td>
</td>}
</tr>
))
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useCallback } from "react";
import { TransRoutesService } from "../../services";
import { AuthService, TransRoutesService } from "../../services";
import BreakfastSection from './BreakfastSection';
import LunchSection from "./LunchSection";
import SnackSection from "./SnackSection";
@@ -7,6 +7,7 @@ import { Breadcrumb } from "react-bootstrap";
import moment from 'moment';
const MealStatus = () => {
const canEditMealStatus = AuthService.canEditMealStatus();
const [selectedDate, setSelectedDate] = useState(new Date());
const [allRoutes, setAllRoutes] = useState([]);
const [breakfastRecords, setBreakfastRecords] = useState([]);
@@ -169,6 +170,7 @@ const MealStatus = () => {
breakfastRecords={breakfastRecords}
confimHasBreakfast={confimHasBreakfast}
removeBreakfastRecord={removeBreakfastRecord}
canEdit={canEditMealStatus}
sectionName={'Breakfast Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}
@@ -180,6 +182,7 @@ const MealStatus = () => {
lunchRecords={lunchRecords}
confirmHasLunch={confirmHasLunch}
removeLunchRecord={removeLunchRecord}
canEdit={canEditMealStatus}
sectionName={'Lunch Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}
@@ -191,6 +194,7 @@ const MealStatus = () => {
snackRecords={snackRecords}
confirmHasSnack={confirmHasSnack}
removeSnackRecord={removeSnackRecord}
canEdit={canEditMealStatus}
sectionName={'Snack Info'}
selectedDate={selectedDate}
refreshRecords={fetchAllData}

View File

@@ -4,7 +4,7 @@ import { CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS, PERSONAL_ROUTE_STATUS_TEXT,
import { Modal, Button } from "react-bootstrap";
import { transRoutesSlice } from "./../../store";
import { CSVLink } from "react-csv";
import { ReportService, CustomerService } from "../../services";
import { ReportService, CustomerService, AuthService } from "../../services";
import TimePicker from 'react-time-picker';
import 'react-time-picker/dist/TimePicker.css';
import moment from 'moment';
@@ -18,6 +18,7 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
customerTableId, routeTypeFilter, customerTypeFilter,
onRouteUpdated = null
}) => {
const canExportRouteReport = AuthService.canExportTransportationScheduleReport();
const [show, setShow] = useState(false);
const [showGroupEditor, setShowGroupEditor] = useState(false);
const [showBulkUpdateModal, setShowBulkUpdateModal] = useState(false);
@@ -695,10 +696,14 @@ const PersonnelInfoTable = ({transRoutes, showCompletedInfo,
</div>)}
{showGroupInfo && (<div className="list row mb-4">
<div className="col-md-12">
<CSVLink className="btn btn-primary btn-no-deco btn-sm me-2" data={generateRouteReportData()} filename={`Route Report - ${transRoutes[0].name} (定线出车单)`}>
Generate Route Reports
</CSVLink>
<button className="btn btn-primary btn-sm me-2" onClick={() => goToReportWithSignature()}>Get Route Report With Signature</button>
{canExportRouteReport && (
<>
<CSVLink className="btn btn-primary btn-no-deco btn-sm me-2" data={generateRouteReportData()} filename={`Route Report - ${transRoutes[0].name} (定线出车单)`}>
Generate Route Reports
</CSVLink>
<button className="btn btn-primary btn-sm me-2" onClick={() => goToReportWithSignature()}>Get Route Report With Signature</button>
</>
)}
<button className="btn btn-primary btn-sm" onClick={() => openBulkUpdateModal()}>Bulk Update Route Customer Time</button>
</div>

View File

@@ -2003,9 +2003,11 @@ const RoutesDashboard = () => {
{
currentTab === 'allRoutesOverview' && <> {
!showCopyDateTargetLoading && <>
<button className="btn btn-outline-secondary me-2" onClick={() => generateRouteReport()} disabled={isExportingRouteReport}>
<Download size={16} className="me-2"></Download>Export Route Report
</button>
{AuthService.canExportTransportationScheduleReport() && (
<button className="btn btn-outline-secondary me-2" onClick={() => generateRouteReport()} disabled={isExportingRouteReport}>
<Download size={16} className="me-2"></Download>Export Route Report
</button>
)}
<Dropdown
key={'signature-date'}
id="signature-date"

View File

@@ -3,7 +3,7 @@ import { TransRoutesService } from "../../services";
import moment from 'moment';
const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack, removeSnackRecord, selectedDate, refreshRecords}) => {
const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack, removeSnackRecord, selectedDate, refreshRecords, canEdit = false}) => {
const [customers, setCustomers] = useState([]);
const createSnackForAll = async () => {
@@ -40,7 +40,7 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
return (
<>
<div className="text-primary mb-4"><h5>{sectionName} {` (${customers?.length})`}</h5></div>
{customers?.some(c => !c.has_snack) && (<div className="mb-2 mt-2"><button onClick={() => createSnackForAll()} className="btn btn-primary btn-sm">Confirm All Snack</button></div>)}
{canEdit && customers?.some(c => !c.has_snack) && (<div className="mb-2 mt-2"><button onClick={() => createSnackForAll()} className="btn btn-primary btn-sm">Confirm All Snack</button></div>)}
<div className="list row">
<div className="col-md-12">
<table className="personnel-info-table">
@@ -48,7 +48,7 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
<tr>
<th>Name</th>
<th>Has Snack Today</th>
<th>Change Snack Status</th>
{canEdit && <th>Change Snack Status</th>}
</tr>
</thead>
<tbody>
@@ -57,10 +57,10 @@ const SnackSection = ({transRoutes, snackRecords, sectionName, confirmHasSnack,
<tr key={customer?.customer_id} className={customer?.has_snack ? 'light-green' : 'red'}>
<td>{customer?.customer_name}</td>
<td>{customer?.has_snack ? 'Yes': 'No'}</td>
<td>
{canEdit && <td>
{!customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => confirmHasSnack(customer)}>Confirm Customer Has snack</button>}
{customer?.has_snack && <button className="btn btn-link btn-sm" onClick={() => removeSnackRecord(customer?.customer_id)}>Mark Customer NOT have snack</button>}
</td>
</td>}
</tr>
))
}

View File

@@ -384,11 +384,13 @@ const VehicleList = () => {
<Dropdown.Menu as={customFilterMenu}/>
</Dropdown>
{AuthService.canAddVehicle() && <button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Vehicle</button>}
<Export
columns={columns}
data={filteredVehicles}
filename="vehicles"
/>
{AuthService.canExportVehicleReport() && (
<Export
columns={columns}
data={filteredVehicles}
filename="vehicles"
/>
)}
</div>
</div>
</div>

View File

@@ -221,6 +221,8 @@ const canViewMealStatus = () => {
]);
}
const canEditMealStatus = () => hasPermission('Edit_Meal Status');
const canViewSeatingChart = () => {
return hasAnyPermission([
'View_Seating Chart',
@@ -228,6 +230,10 @@ const canViewSeatingChart = () => {
]);
}
const canEditSeatingChart = () => {
return hasPermission('Edit_Seating Chart');
}
const canViewLobby = () => {
return canViewMealStatus() || canViewSeatingChart();
}
@@ -373,7 +379,9 @@ export const AuthService = {
canViewMedicalEvents,
canEditMedicalEvents,
canViewMealStatus,
canEditMealStatus,
canViewSeatingChart,
canEditSeatingChart,
canViewLobby,
isAdmin,
canCreateOrEditDrivers,