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

This commit is contained in:
2026-03-09 14:23:24 -04:00
parent 952c30b557
commit 93eb32d477
6 changed files with 322 additions and 61 deletions

View File

@@ -662,6 +662,19 @@ table .group td {
height: 45px;
}
.manage-table-columns-wrapper {
width: 100%;
margin-right: 0;
}
.manage-table-columns-scroll {
max-height: 240px;
overflow-y: auto;
overflow-x: hidden;
padding-right: 0;
margin-right: 0;
}
.app-main-content-fields-section.with-function .react-datepicker-wrapper input[type=text] {
height: 45px;
width: 120px;

View File

@@ -2,8 +2,8 @@ import React, {useState, useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { customerSlice } from "./../../store";
import { AuthService, CustomerService, EventsService, LabelService } from "../../services";
import { CUSTOMER_TYPE, ManageTable } from "../../shared";
import { AuthService, CustomerService, EventsService } from "../../services";
import { CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT, PROGRAM_TYPE, PROGRAM_TYPE_TEXT, PAY_SOURCE, PAY_SOURCE_TEXT, TRANSPORTATION_TYPE, TRANSPORTATION_TYPE_TEXT, YES_NO, YES_NO_TEXT, ManageTable } from "../../shared";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown, Modal } from "react-bootstrap";
import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons";
@@ -22,8 +22,11 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [showManageTableDropdown, setShowManageTableDropdown] = useState(false);
const [showExportDropdown, setShowExportDropdown] = useState(false);
const [tagsFilter, setTagsFilter] = useState([]);
const [availableLabels, setAvailableLabels] = useState([]);
const [customerTypeFilter, setCustomerTypeFilter] = useState('');
const [programTypeFilter, setProgramTypeFilter] = useState('');
const [paySourceFilter, setPaySourceFilter] = useState('');
const [transportationTypeFilter, setTransportationTypeFilter] = useState('');
const [eyesOnFilter, setEyesOnFilter] = useState('');
const [showAvatarModal, setShowAvatarModal] = useState(false);
const [avatarData, setAvatarData] = useState(null);
const [avatarCustomerName, setAvatarCustomerName] = useState('');
@@ -110,19 +113,14 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
navigate(`/login`);
}
setShowSpinner(true);
Promise.all([
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data.map((item) =>{
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data.map((item) =>{
item.phone = item?.phone || item?.home_phone || item?.mobile_phone;
item.address = item?.address1 || item?.address2 || item?.address3 || item?.address4|| item?.address5;
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
}),
LabelService.getAll().then((data) => {
setAvailableLabels(data.data);
})
]).finally(() => {
return item;
}).sort((a, b) => a.lastname > b.lastname ? 1: -1));
}).finally(() => {
setShowSpinner(false);
});
}, []);
@@ -166,16 +164,31 @@ 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);
}
// Tags filter
if (tagsFilter.length > 0) {
filtered = filtered.filter(item => {
if (!item?.tags || item.tags.length === 0) return false;
return tagsFilter.some(tag => item.tags.includes(tag));
if (customerTypeFilter) {
filtered = filtered.filter((item) => (item?.type || '') === customerTypeFilter);
}
if (programTypeFilter) {
filtered = filtered.filter((item) => (item?.program || '') === programTypeFilter);
}
if (paySourceFilter) {
filtered = filtered.filter((item) => (item?.pay_source || '') === paySourceFilter);
}
if (transportationTypeFilter) {
filtered = filtered.filter((item) => (item?.transportation_type || '') === transportationTypeFilter);
}
if (eyesOnFilter) {
filtered = filtered.filter((item) => {
const normalized = `${item?.eyes_on || (item?.disability ? YES_NO.YES : '')}`.toLowerCase();
return normalized === eyesOnFilter;
});
}
setFilteredCustomers(filtered);
}, [keyword, customers, showInactive, tagsFilter])
}, [keyword, customers, showInactive, customerTypeFilter, programTypeFilter, paySourceFilter, transportationTypeFilter, eyesOnFilter])
useEffect(() => {
const newCustomers = [...customers];
@@ -224,7 +237,11 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
}
const cleanFilterAndClose = () => {
setTagsFilter([]);
setCustomerTypeFilter('');
setProgramTypeFilter('');
setPaySourceFilter('');
setTransportationTypeFilter('');
setEyesOnFilter('');
setShowFilterDropdown(false);
}
@@ -232,13 +249,12 @@ const DashboardCustomersList = ({ additionalButtons, showBreadcrumb = false, tit
setShowFilterDropdown(false);
}
const toggleTagFilter = (tagName) => {
if (tagsFilter.includes(tagName)) {
setTagsFilter(tagsFilter.filter(tag => tag !== tagName));
} else {
setTagsFilter([...tagsFilter, tagName]);
}
}
const getOptionsFromEnum = (enumMap, textMap) => {
return Object.values(enumMap).map((value) => ({
value,
label: textMap?.[value] || value
}));
};
const handleColumnsChange = (newColumns) => {
setColumns(newColumns);
@@ -361,22 +377,53 @@ 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">
<div className="field-label">Tags</div>
<div style={{ maxHeight: '150px', overflowY: 'auto' }}>
{availableLabels.map((label) => (
<div key={label.id} style={{ marginBottom: '5px' }}>
<input
type="checkbox"
id={`tag-${label.id}`}
checked={tagsFilter.includes(label.label_name)}
onChange={() => toggleTagFilter(label.label_name)}
/>
<label htmlFor={`tag-${label.id}`} style={{ marginLeft: '5px' }}>
{label.label_name}
</label>
</div>
<div className="field-label">Customer Type</div>
<select value={customerTypeFilter} onChange={(e) => setCustomerTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
{getOptionsFromEnum(CUSTOMER_TYPE, CUSTOMER_TYPE_TEXT).map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</div>
</select>
</div>
<div className="me-4">
<div className="field-label">Program Type</div>
<select value={programTypeFilter} onChange={(e) => setProgramTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
{getOptionsFromEnum(PROGRAM_TYPE, PROGRAM_TYPE_TEXT).map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Pay Source</div>
<select value={paySourceFilter} onChange={(e) => setPaySourceFilter(e.currentTarget.value)}>
<option value="">All</option>
{getOptionsFromEnum(PAY_SOURCE, PAY_SOURCE_TEXT).map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
<div className="me-4">
<div className="field-label">Tranportation Type</div>
<select value={transportationTypeFilter} onChange={(e) => setTransportationTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
{getOptionsFromEnum(TRANSPORTATION_TYPE, TRANSPORTATION_TYPE_TEXT).map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Eyes-On</div>
<select value={eyesOnFilter} onChange={(e) => setEyesOnFilter(e.currentTarget.value)}>
<option value="">All</option>
{getOptionsFromEnum(YES_NO, YES_NO_TEXT).map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
</div>
<div className="list row">

View File

@@ -94,7 +94,7 @@ const Card = ({ content, index, moveCard }) => {
)
}
const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, viewMode, editFun, onAddCustomer = null}) => {
const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, viewMode, editFun, onAddCustomer = null, scheduledAbsentCustomerIds = []}) => {
const [customers, setCustomers] = useState([]);
const [initializedRouteId, setInitializedRouteId] = useState(null);
const [showAddPersonnelModal, setShowAddPersonnelModal] = useState(false);
@@ -117,6 +117,16 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
const [pageCount, setPageCount] = useState(0);
const itemsPerPage = 10;
const hasActiveFilters = Boolean((customerFilter || '').trim()) || Boolean(lastNameFilter);
const scheduledAbsentIdsSet = new Set([
...(Array.isArray(scheduledAbsentCustomerIds) ? scheduledAbsentCustomerIds : []),
...((currentRoute?.route_customer_list || [])
.filter((item) => item?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)
.map((item) => item?.customer_id)
.filter(Boolean))
]);
const getAbsentNameStyle = (customerId) => (
scheduledAbsentIdsSet.has(customerId) ? { color: '#dc3545', fontWeight: 700 } : {}
);
// Helper function to get all customer IDs already in the route
const getAssignedCustomerIds = () => {
@@ -699,7 +709,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
<div className="customer-dnd-item-content">{item.customers.map(customer =>
<div key={customer.customer_id}>
<small className="me-2">{customer.customer_name}</small>
<small className="me-2" style={getAbsentNameStyle(customer.customer_id)}>{customer.customer_name}</small>
<small className="me-2">{customer.customer_address}</small>
<small className="me-2">{customer.customer_pickup_status}</small>
</div>)}
@@ -712,7 +722,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<GripVertical className="me-4" size={20}></GripVertical>
<div className="customer-dnd-item">
<span>{item.customer_name} </span>
<span style={getAbsentNameStyle(item.customer_id)}>{item.customer_name} </span>
<small className="me-2">{item.customer_address}</small>
<small className="me-2">{item.customer_pickup_status}</small>
</div>
@@ -742,7 +752,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
<div className="customer-dnd-item-content">{item.customers.map(customer =>
<div key={customer.customer_id}>
<small className="me-2">{customer.customer_name}</small>
<small className="me-2" style={getAbsentNameStyle(customer.customer_id)}>{customer.customer_name}</small>
<small className="me-2">{customer.customer_address}</small>
<small className="me-2">{customer.customer_pickup_status}</small>
</div>)}
@@ -753,7 +763,7 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view
return <div className="customers-dnd-item-container">
<div className="stop-index"><span>{`Stop ${index+1}`}</span><RecordCircleFill size={16} color={"#0066B1"} className="ms-2"></RecordCircleFill> </div>
<div className="customer-dnd-item">
<span>{item.customer_name} </span>
<span style={getAbsentNameStyle(item.customer_id)}>{item.customer_name} </span>
<small className="me-2">{item.customer_address}</small>
<small className="me-2">{item.customer_pickup_status}</small>
</div>

View File

@@ -303,12 +303,28 @@ const RouteEdit = () => {
EventsService.getAllEventRecurrences()
]).then(([eventsRes, recurRes]) => {
const absentCustomerIds = new Set();
const attendanceReasonsByCustomer = new Map();
const addAttendanceReason = (customerId, reasonText) => {
if (!customerId || !reasonText) return;
const cleaned = `${reasonText}`.trim();
if (!cleaned) return;
const existing = attendanceReasonsByCustomer.get(customerId) || [];
if (!existing.includes(cleaned)) {
attendanceReasonsByCustomer.set(customerId, [...existing, cleaned]);
}
};
const extractAttendanceReason = (item) => {
return item?.description || item?.data?.description || item?.data?.note || item?.title || '';
};
// Single attendance notes for this date
const singleNotes = (eventsRes?.data || []).filter(
e => e.type === 'incident' && e.status === 'active' && e.target_uuid
);
singleNotes.forEach(note => absentCustomerIds.add(note.target_uuid));
singleNotes.forEach(note => {
absentCustomerIds.add(note.target_uuid);
addAttendanceReason(note.target_uuid, extractAttendanceReason(note));
});
// Recurring attendance rules that fall on this date
const recurRules = (recurRes?.data || []).filter(
@@ -317,6 +333,7 @@ const RouteEdit = () => {
recurRules.forEach(rule => {
if (recurRuleFallsOnDate(rule, routeDateObj)) {
absentCustomerIds.add(rule.target_uuid);
addAttendanceReason(rule.target_uuid, extractAttendanceReason(rule));
}
});
@@ -325,12 +342,14 @@ const RouteEdit = () => {
absentCustomerIds.forEach(customerId => {
const customer = allCustomers.find(c => c.id === customerId);
if (customer) {
const reasons = attendanceReasonsByCustomer.get(customer.id) || [];
absentList.push({
customer_id: customer.id,
customer_name: customer.name,
customer_address: customer.address1 || '',
customer_route_status: PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT,
customer_pickup_status: 'scheduleAbsent',
attendance_note: reasons.join('; '),
_attendance_based: true // flag to identify these are from attendance notes
});
}
@@ -562,6 +581,16 @@ const RouteEdit = () => {
<div className="column-container">
<div className="column-card adjust" style={{paddingRight: '30px'}}>
<div className="col-md-12 mb-4">
{(() => {
const existingScheduledAbsentIds = (currentRoute?.route_customer_list || [])
.filter((customer) => customer?.customer_route_status === PERSONAL_ROUTE_STATUS.SCHEDULED_ABSENT)
.map((customer) => customer?.customer_id)
.filter(Boolean);
const attendanceBasedAbsentIds = (attendanceAbsentCustomers || [])
.map((customer) => customer?.customer_id)
.filter(Boolean);
const mergedAbsentIds = Array.from(new Set([].concat(existingScheduledAbsentIds, attendanceBasedAbsentIds)));
return (
<RouteCustomerEditor
currentRoute={currentRoute ? {
...currentRoute,
@@ -572,7 +601,10 @@ const RouteEdit = () => {
} : undefined}
setNewCustomerList={setNewCustomerList}
onAddCustomer={(addFn) => setAddCustomerToRoute(() => addFn)}
scheduledAbsentCustomerIds={mergedAbsentIds}
/>
);
})()}
</div>
</div>
</div>
@@ -613,7 +645,7 @@ const RouteEdit = () => {
<div className="customer-dnd-item">
<span>{abItem.customer_name} </span>
<small className="me-2">{abItem.customer_address}</small>
<small className="me-2 text-muted">(Attendance Note)</small>
{!!abItem.attendance_note && <small className="me-2 text-muted">({abItem.attendance_note})</small>}
</div>
</div>
));

View File

@@ -2,9 +2,10 @@ import React, {useState, useEffect} from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { AuthService, VehicleService } from "../../services";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab } from "react-bootstrap";
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Dropdown } from "react-bootstrap";
import { Columns, Download, Filter, PersonSquare, Plus } from "react-bootstrap-icons";
import { ManageTable, Export } from "../../shared/components";
import { FUEL_TYPE, FUEL_TYPE_TEXT, VEHICLE_TITLE, VEHICLE_TITLE_TEXT, SEATING_CAPACITY_OPTIONS, LIFT_EQUIPPED, LIFT_EQUIPPED_TEXT } from "../../shared";
const VehicleList = () => {
const navigate = useNavigate();
@@ -16,6 +17,11 @@ const VehicleList = () => {
const [selectedItems, setSelectedItems] = useState([]);
const [filteredVehicles, setFilteredVehicles] = useState(vehicles);
const [showInactive, setShowInactive] = useState(false);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const [seatingCapacityFilter, setSeatingCapacityFilter] = useState('');
const [fuelTypeFilter, setFuelTypeFilter] = useState('');
const [titleFilter, setTitleFilter] = useState('');
const [liftEquippedFilter, setLiftEquippedFilter] = useState('');
const [columns, setColumns] = useState([
{
key: 'vehicle_number',
@@ -32,6 +38,11 @@ const VehicleList = () => {
label: 'Seating Capacity',
show: true
},
{
key: 'responsible_driver',
label: 'Responsible Driver',
show: true
},
{
key: 'mileage',
label: 'Mileage',
@@ -51,6 +62,36 @@ const VehicleList = () => {
key: 'year',
label: 'Year',
show: true
},
{
key: 'vin',
label: 'VIN Number',
show: true
},
{
key: 'gps_tag',
label: 'GPS ID',
show: true
},
{
key: 'ezpass',
label: 'E-ZPass',
show: true
},
{
key: 'has_lift_equip',
label: 'Lift Equipped',
show: true
},
{
key: 'fuel_type',
label: 'Fuel Type',
show: true
},
{
key: 'title',
label: 'Title',
show: true
}
]);
@@ -72,24 +113,44 @@ const VehicleList = () => {
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.responsible_driver?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.vin?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.fuel_type?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.title?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
item?.status?.toLowerCase() !== 'active'
))
).filter((item) => {
if (seatingCapacityFilter && `${item?.capacity || ''}` !== `${seatingCapacityFilter}`) return false;
if (fuelTypeFilter && `${item?.fuel_type || ''}` !== fuelTypeFilter) return false;
if (titleFilter && `${item?.title || ''}` !== titleFilter) return false;
if (liftEquippedFilter && `${item?.has_lift_equip}` !== liftEquippedFilter) return false;
return true;
}))
} else {
setFilteredVehicles(vehicles && vehicles.filter(item =>
(item?.vehicle_number?.toString()?.startsWith(keyword.toLowerCase()) ||
item?.tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.ezpass?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.gps_tag?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.responsible_driver?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.make?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.vehicle_model?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
item?.year?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.vin?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.fuel_type?.toLowerCase()?.startsWith(keyword.toLowerCase()) ||
item?.title?.toLowerCase()?.startsWith(keyword.toLowerCase())) &&
item?.status?.toLowerCase() === 'active'
))
).filter((item) => {
if (seatingCapacityFilter && `${item?.capacity || ''}` !== `${seatingCapacityFilter}`) return false;
if (fuelTypeFilter && `${item?.fuel_type || ''}` !== fuelTypeFilter) return false;
if (titleFilter && `${item?.title || ''}` !== titleFilter) return false;
if (liftEquippedFilter && `${item?.has_lift_equip}` !== liftEquippedFilter) return false;
return true;
}))
}
}, [keyword, vehicles]);
}, [keyword, vehicles, showInactive, seatingCapacityFilter, fuelTypeFilter, titleFilter, liftEquippedFilter]);
useEffect(() => {
const newVehicles = [...vehicles];
@@ -154,6 +215,10 @@ const VehicleList = () => {
// Recover all filters
setKeyword('');
setTag('');
setSeatingCapacityFilter('');
setFuelTypeFilter('');
setTitleFilter('');
setLiftEquippedFilter('');
setSorting({key: '', order: ''});
setSelectedItems([]);
}
@@ -162,6 +227,77 @@ const VehicleList = () => {
return selectedItems.length === filteredVehicles.length && selectedItems.length > 0;
}
const clearAndCloseFilter = () => {
setSeatingCapacityFilter('');
setFuelTypeFilter('');
setTitleFilter('');
setLiftEquippedFilter('');
setShowFilterDropdown(false);
};
const applyAndCloseFilter = () => {
setShowFilterDropdown(false);
};
const customFilterMenu = React.forwardRef(
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
>
<h6>Filter By</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Seating Capacity</div>
<select value={seatingCapacityFilter} onChange={(e) => setSeatingCapacityFilter(e.currentTarget.value)}>
<option value="">All</option>
{SEATING_CAPACITY_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>{item.label}</option>
))}
</select>
</div>
<div className="me-4">
<div className="field-label">Fuel Type</div>
<select value={fuelTypeFilter} onChange={(e) => setFuelTypeFilter(e.currentTarget.value)}>
<option value="">All</option>
{Object.values(FUEL_TYPE).map((value) => (
<option key={value} value={value}>{FUEL_TYPE_TEXT[value] || value}</option>
))}
</select>
</div>
</div>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Title</div>
<select value={titleFilter} onChange={(e) => setTitleFilter(e.currentTarget.value)}>
<option value="">All</option>
{Object.values(VEHICLE_TITLE).map((value) => (
<option key={value} value={value}>{VEHICLE_TITLE_TEXT[value] || value}</option>
))}
</select>
</div>
<div className="me-4">
<div className="field-label">Lift Equipped</div>
<select value={liftEquippedFilter} onChange={(e) => setLiftEquippedFilter(e.currentTarget.value)}>
<option value="">All</option>
{Object.values(LIFT_EQUIPPED).map((value) => (
<option key={value} value={value}>{LIFT_EQUIPPED_TEXT[value] || value}</option>
))}
</select>
</div>
</div>
<div className="list row">
<div className="col-md-12">
<button className="btn btn-default btn-sm float-right" onClick={clearAndCloseFilter}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" onClick={applyAndCloseFilter}> Filter </button>
</div>
</div>
</div>
)
);
const table = <div className="list row mb-4">
<div className="col-md-12">
@@ -186,10 +322,17 @@ const VehicleList = () => {
{columns.find(col => col.key === 'vehicle_number')?.show && <td>{AuthService.canViewVechiles() ? <button className="btn btn-link btn-sm" onClick={() => goToView(vehicle?.id)}>{vehicle?.vehicle_number}</button> : vehicle?.vehicle_number }</td>}
{columns.find(col => col.key === 'tag')?.show && <td>{vehicle?.tag}</td>}
{columns.find(col => col.key === 'capacity')?.show && <td>{vehicle?.capacity}</td>}
{columns.find(col => col.key === 'responsible_driver')?.show && <td>{vehicle?.responsible_driver}</td>}
{columns.find(col => col.key === 'mileage')?.show && <td>{vehicle?.mileage}</td>}
{columns.find(col => col.key === 'make')?.show && <td>{vehicle?.make}</td>}
{columns.find(col => col.key === 'model')?.show && <td>{vehicle?.vehicle_model}</td>}
{columns.find(col => col.key === 'year')?.show && <td>{vehicle?.year}</td>}
{columns.find(col => col.key === 'vin')?.show && <td>{vehicle?.vin}</td>}
{columns.find(col => col.key === 'gps_tag')?.show && <td>{vehicle?.gps_tag}</td>}
{columns.find(col => col.key === 'ezpass')?.show && <td>{vehicle?.ezpass}</td>}
{columns.find(col => col.key === 'has_lift_equip')?.show && <td>{vehicle?.has_lift_equip ? 'Yes' : 'No'}</td>}
{columns.find(col => col.key === 'fuel_type')?.show && <td>{FUEL_TYPE_TEXT[vehicle?.fuel_type] || vehicle?.fuel_type}</td>}
{columns.find(col => col.key === 'title')?.show && <td>{VEHICLE_TITLE_TEXT[vehicle?.title] || vehicle?.title}</td>}
</tr>)
}
</tbody>
@@ -226,8 +369,20 @@ const VehicleList = () => {
</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)} />
{/* <button className="btn btn-primary me-2"><Filter size={16} className="me-2"></Filter>Filter</button> */}
<ManageTable columns={columns} onColumnsChange={setColumns} />
<Dropdown
key={'filter-vehicles'}
id="filter-vehicles"
className="me-2"
show={showFilterDropdown}
onToggle={(isOpen) => setShowFilterDropdown(isOpen)}
autoClose={false}
>
<Dropdown.Toggle variant="primary">
<Filter size={16} className="me-2"></Filter>Filter
</Dropdown.Toggle>
<Dropdown.Menu as={customFilterMenu}/>
</Dropdown>
<button className="btn btn-primary me-2" onClick={() => goToCreateNew()}><Plus size={16}></Plus>Add New Vehicle</button>
<Export
columns={columns}

View File

@@ -10,6 +10,10 @@ const ManageTable = ({ columns, onColumnsChange, show, onToggle }) => {
const showManageTableDropdown = show !== undefined ? show : internalShow;
const handleToggle = onToggle || (() => setInternalShow(!internalShow));
React.useEffect(() => {
setTempColumns(columns);
}, [columns]);
const handleColumnToggle = (columnKey) => {
const updatedColumns = tempColumns.map(col =>
col.key === columnKey ? { ...col, show: !col.show } : col
@@ -46,8 +50,8 @@ const ManageTable = ({ columns, onColumnsChange, show, onToggle }) => {
>
<h6>Manage Table Columns</h6>
<div className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div style={{ maxHeight: '200px', overflowY: 'auto' }}>
<div className="manage-table-columns-wrapper">
<div className="manage-table-columns-scroll">
{tempColumns.map((column) => (
<div key={column.key} style={{ marginBottom: '8px' }}>
<input