diff --git a/package.json b/package.json
index 58206d8394..d6ca55a67f 100644
--- a/package.json
+++ b/package.json
@@ -94,7 +94,7 @@
"react-chartjs-2": "^5.3.0",
"react-circular-progressbar": "^2.1.0",
"react-collapsible": "^2.10.0",
- "react-datepicker": "^4.8.0",
+ "react-datepicker": "^4.25.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-icons": "^4.12.0",
@@ -180,6 +180,7 @@
"@testing-library/user-event": "^12.0.14",
"@types/html-to-pdfmake": "^2.4.4",
"@types/pdfmake": "^0.2.11",
+ "@types/react-datepicker": "^6.2.0",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.5.0",
"@vitest/coverage-v8": "^3.2.4",
diff --git a/src/components/BMDashboard/ItemList/ItemListView.jsx b/src/components/BMDashboard/ItemList/ItemListView.jsx
index 677ae90af4..036ca746f8 100644
--- a/src/components/BMDashboard/ItemList/ItemListView.jsx
+++ b/src/components/BMDashboard/ItemList/ItemListView.jsx
@@ -1,5 +1,4 @@
import { useState, useEffect, useMemo } from 'react';
-import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
@@ -10,18 +9,10 @@ import SelectItem from './SelectItem';
import ItemsTable from './ItemsTable';
import styles from './ItemListView.module.css';
-export function ItemListView({
- itemType,
- items,
- errors,
- UpdateItemModal,
- dynamicColumns,
- children,
-}) {
- const darkMode = useSelector(state => state.theme.darkMode);
- const [filteredItems, setFilteredItems] = useState(items || []);
- const [selectedProject, setSelectedProject] = useState('all');
- const [selectedItem, setSelectedItem] = useState('all');
+export function ItemListView({ itemType, items, errors, UpdateItemModal, dynamicColumns }) {
+ const [filteredItems, setFilteredItems] = useState([]);
+ const [selectedProject, setSelectedProject] = useState([]); // Array of strings
+ const [selectedItem, setSelectedItem] = useState([]); // Array of strings
const [isError, setIsError] = useState(false);
const [selectedTime, setSelectedTime] = useState(new Date());
@@ -30,32 +21,37 @@ export function ItemListView({
const [currentPage, setCurrentPage] = useState(1);
const [rowsPerPage, setRowsPerPage] = useState(25);
+ // Sync initial items load
useEffect(() => {
if (items) setFilteredItems([...items]);
}, [items]);
+ // Unified Filtering Logic (Treats empty arrays as 'unfiltered/show all')
useEffect(() => {
- let filterItems;
if (!items) return;
- if (selectedProject === 'all' && selectedItem === 'all') {
- setFilteredItems([...items]);
- } else if (selectedProject !== 'all' && selectedItem === 'all') {
- filterItems = items.filter(item => item.project?.name === selectedProject);
- setFilteredItems([...filterItems]);
- } else if (selectedProject === 'all' && selectedItem !== 'all') {
- filterItems = items.filter(item => item.itemType?.name === selectedItem);
- setFilteredItems([...filterItems]);
- } else {
- filterItems = items.filter(
- item => item.project?.name === selectedProject && item.itemType?.name === selectedItem,
+ const hasProjectFilter = selectedProject.length > 0;
+ const hasItemFilter = selectedItem.length > 0;
+
+ let matchedItems = items;
+
+ if (hasProjectFilter && !hasItemFilter) {
+ matchedItems = items.filter(item => selectedProject.includes(item.project?.name));
+ } else if (!hasProjectFilter && hasItemFilter) {
+ matchedItems = items.filter(item => selectedItem.includes(item.itemType?.name));
+ } else if (hasProjectFilter && hasItemFilter) {
+ matchedItems = items.filter(
+ item =>
+ selectedProject.includes(item.project?.name) &&
+ selectedItem.includes(item.itemType?.name),
);
- setFilteredItems([...filterItems]);
}
+
+ setFilteredItems(matchedItems);
}, [selectedProject, selectedItem, items]);
useEffect(() => {
- setIsError(errors ? Object.keys(errors).length > 0 : false);
+ setIsError(Object.entries(errors || {}).length > 0);
}, [errors]);
useEffect(() => {
@@ -152,10 +148,7 @@ export function ItemListView({
if (isError) {
return (
-
- {itemType}
- {' List'}
-
+ {itemType} List
);
@@ -197,8 +190,7 @@ export function ItemListView({
selectedProject={selectedProject}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
- label={itemType === 'Materials' ? 'Material' : itemType}
- darkMode={darkMode}
+ label={itemType}
/>
)}
@@ -313,12 +305,10 @@ ItemListView.propTypes = {
key: PropTypes.string.isRequired,
}),
).isRequired,
- children: PropTypes.node,
};
ItemListView.defaultProps = {
errors: {},
- children: null,
};
export default ItemListView;
diff --git a/src/components/BMDashboard/ItemList/ItemListView.module.css b/src/components/BMDashboard/ItemList/ItemListView.module.css
index 47d65ea217..c8aac31547 100644
--- a/src/components/BMDashboard/ItemList/ItemListView.module.css
+++ b/src/components/BMDashboard/ItemList/ItemListView.module.css
@@ -11,6 +11,10 @@ table td {
padding: 0 1rem;
}
+.itemsListContainer section {
+ margin-top: 2rem;
+}
+
.itemsTableContainer {
overflow: auto;
max-height: 600px;
@@ -18,20 +22,20 @@ table td {
border-radius: 6px;
}
-.items_list_container section {
- margin-top: 2rem;
-}
-
-.items_list_container table {
+.itemsListContainer table {
text-align: center;
min-width: 1024px;
overflow: scroll;
font-size: small;
margin-bottom: 0 !important;
+ background-color: #ffffff;
+ color: #111827;
}
-.items_list_container th {
+.itemsListContainer th {
height: 2rem;
+ background-color: #f9fafb;
+ color: #374151;
}
.itemsCell td {
@@ -53,7 +57,7 @@ table td {
color: black;
}
-
+/* --- Search Row --- */
.searchRow {
display: flex;
align-items: center;
@@ -82,6 +86,8 @@ table td {
padding: 5px 34px 5px 10px;
border: 1px solid #ccc;
border-radius: 6px;
+ background-color: #ffffff;
+ color: #111827;
}
.clearSearch {
@@ -105,6 +111,7 @@ table td {
opacity: 0.85;
}
+/* --- Table Headers --- */
.stickyThead th {
position: sticky;
top: 0;
@@ -122,12 +129,14 @@ table td {
text-decoration: underline;
}
+/* --- Input Selection Grid --- */
.selectInput {
display: grid;
- grid-template-columns: auto 1fr auto 1fr auto 1fr;
+ grid-template-columns: auto 1fr auto 1fr;
align-items: center;
gap: 15px;
width: 100%;
+ min-width: 400px;
max-width: 1200px;
margin: 0 auto 10px;
overflow: visible;
@@ -144,12 +153,20 @@ table td {
.selectInput select {
height: 38px;
width: 100%;
- min-width: 180px;
- max-width: 240px;
+ min-width: 400px;
+ max-width: 1200px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 8px;
+ background-color: #ffffff;
+ color: #111827;
+}
+
+.selectInput :global(.react-select__control) {
+ width: 100%;
+ max-width: 1200px;
+ min-width: 400px;
}
.selectInput input[type='text'] {
@@ -157,6 +174,7 @@ table td {
margin-bottom: 8px;
}
+/* --- Buttons --- */
.btnPrimary {
background-color: #468ef9;
color: white;
@@ -177,6 +195,7 @@ table td {
background-color: #3b82f6;
}
+/* --- Pagination --- */
.paginationBar {
display: flex;
align-items: center;
@@ -187,6 +206,7 @@ table td {
border-top: none;
border-radius: 0 0 6px 6px;
flex-wrap: wrap;
+ background-color: #ffffff;
}
.rowsPerPage {
@@ -202,6 +222,7 @@ table td {
border-radius: 6px;
border: 1px solid #d1d5db;
background: #fff;
+ color: #111827;
}
.rangeInfo {
@@ -222,6 +243,7 @@ table td {
border-radius: 6px;
border: 1px solid #d1d5db;
background: white;
+ color: #111827;
cursor: pointer;
}
@@ -241,88 +263,62 @@ table td {
opacity: 0.75;
}
+.filterForm,
+.filterItem {
+ display: flex;
+ justify-content: flex-start;
+ margin-bottom: 20px;
+}
+
+/* ==========================================================================
+ DARK MODE CONTROL PANEL
+ All dark mode styles are scoped safely under .darkMode or :global(.dark-mode)
+ ========================================================================== */
+
.darkMode {
color: #e8edf4;
background-color: #0d1117 !important;
-
-/* color: #fff !important; */
min-height: 100vh;
padding-bottom: 2rem;
}
-.darkTable tbody td,
-.darkTable tbody th {
- border-color: #2f4157 !important;
-}
-
-.darkTable tbody tr:nth-child(odd) {
- background-color: #1f2e45;
-}
-
-.darkTable tbody tr:nth-child(even) {
- background-color: #23375b;
-}
-
-.darkTable .stickyThead th {
- background-color: #2f4157;
- border-bottom: 1px solid #3f5269;
-}
-
-:global(.dark-mode) .items_table_container {
- background-color: transparent !important;
+/* Form element Dark Mode Fallbacks */
+.darkMode input,
+.darkMode select,
+.darkMode option {
+ background-color: #1e2632;
+ color: #ffffff;
+ border-color: #30363d;
}
-:global(.dark-mode) .items_list_container table {
+/* Deep component tree modifications under Dark Mode */
+.darkMode .itemsListContainer table {
background-color: #161b22;
- color: #fff;
+ color: #ffffff;
border: 1px solid #30363d;
}
-:global(.dark-mode) .items_list_container th {
+.darkMode .itemsListContainer th {
background-color: #1f2937;
- color: #fff;
+ color: #ffffff;
border-bottom: 1px solid #30363d;
}
-:global(.dark-mode) .items_list_container td {
+.darkMode .itemsListContainer td {
border-bottom: 1px solid #30363d;
- color: #fff;
+ color: #ffffff;
}
-:global(.dark-mode) .items_list_container tr:hover {
+.darkMode .itemsListContainer tr:hover {
background-color: #1f2a37;
}
-:global(.dark-mode) .items_cell svg {
- color: #fff;
+.darkMode .itemsCell svg {
+ color: #ffffff;
}
-:global(.dark-mode) .items_cell svg:hover {
- color: #fff;
-}
-
-:global(.dark-mode) .select_input {
- background-color: #1e2632;
- color: #fff;
- border: 1px solid #30363d;
-}
-
-:global(.dark-mode) select,
-:global(.dark-mode) input {
- background-color: #1e2632;
- color: #fff;
- border: 1px solid #30363d;
-}
-
-:global(.dark-mode) option {
- background-color: #111827;
- color: #fff;
-}
-
-:global(.dark-mode) body,
-:global(.dark-mode) html,
-:global(.dark-mode) #root {
- background-color: #0d1117 !important;
+.darkMode .itemsCell svg:hover {
+ color: #468ef9;
}
.darkMode .searchBox input {
@@ -336,11 +332,12 @@ table td {
}
.darkMode .clearSearch:hover {
- color: #fff;
+ color: #ffffff;
}
.darkMode .paginationBar {
border-color: #2f4157;
+ background-color: #161b22;
}
.darkMode .rowsPerPage select {
@@ -355,6 +352,14 @@ table td {
border: 1px solid #3f5269;
}
+/* Global Dark Mode Overrides (Only used if the class is appended to body/html container) */
+:global(.dark-mode) body,
+:global(.dark-mode) html,
+:global(.dark-mode) #root {
+ background-color: #0d1117 !important;
+}
+
+/* --- Dark Mode DatePicker Sub-styles --- */
.darkDatePickerInput {
background-color: #2a3f5f !important;
color: #e8edf4 !important;
@@ -404,10 +409,7 @@ table td {
.darkDatePicker :global(.react-datepicker__time-container),
.darkDatePicker :global(.react-datepicker__time-container .react-datepicker__time),
.darkDatePicker :global(.react-datepicker__time-container .react-datepicker__time-box),
-.darkDatePicker
- :global(.react-datepicker__time-container
- .react-datepicker__time-box
- ul.react-datepicker__time-list) {
+.darkDatePicker :global(.react-datepicker__time-container .react-datepicker__time-box ul.react-datepicker__time-list) {
background-color: #2a3f5f !important;
border-color: #3f5269 !important;
color: #e8edf4 !important;
@@ -424,15 +426,11 @@ table td {
color: #fff !important;
}
-.darkDatePicker :global(.react-datepicker__time-list-item--selected) {
- background-color: #468ef9 !important;
- color: #fff !important;
-}
-
.darkDatePickerPopper .react-datepicker__triangle {
display: none;
}
+/* --- Light Mode DatePicker Sub-styles --- */
.lightDatePickerInput {
background-color: #fff !important;
color: #111827 !important;
@@ -469,35 +467,11 @@ table td {
color: #fff !important;
}
-.lightDatePicker .react-datepicker__time-container {
- border-color: #d1d5db !important;
- background-color: #fff !important;
-}
-
-.lightDatePicker .react-datepicker__time-container .react-datepicker__time {
- background-color: #fff !important;
- border-color: #d1d5db !important;
-}
-
-.lightDatePicker .react-datepicker__time-container .react-datepicker__time-box {
- background-color: #fff !important;
- border-color: #d1d5db !important;
-}
-
-.lightDatePicker .react-datepicker__time-container .react-datepicker__time-list {
- background-color: #fff !important;
- color: #1f2937 !important;
- border-color: #d1d5db !important;
-}
-
.lightDatePicker :global(.react-datepicker__time-container),
.lightDatePicker :global(.react-datepicker__time-container .react-datepicker__time),
.lightDatePicker :global(.react-datepicker__time-container .react-datepicker__time-box),
-.lightDatePicker
- :global(.react-datepicker__time-container
- .react-datepicker__time-box
- ul.react-datepicker__time-list) {
- background-color: #fff !important;
+.lightDatePicker :global(.react-datepicker__time-container .react-datepicker__time-box ul.react-datepicker__time-list) {
+ background-color: #ffffff !important;
border-color: #d1d5db !important;
color: #1f2937 !important;
}
@@ -513,57 +487,6 @@ table td {
color: #111827 !important;
}
-.lightDatePicker :global(.react-datepicker__time-list-item--selected) {
- background-color: #468ef9 !important;
- color: #fff !important;
-}
-
-.items_list_container.dark_mode {
- background-color: #1b1f27 !important;
- color: #fff !important;
- border-color: #2a2f3a !important;
-}
-
-.dark-mode .items_table_container .table thead th {
- background-color: #1c2541 !important;
- color: #fff !important;
- border-color: #555 !important;
-}
-
.lightDatePickerPopper .react-datepicker__triangle {
display: none;
-}
-
-.fieldRequired {
- color: red;
-}
-
-:global(body.dark-mode) .fieldRequired,
-:global(body.bm-dashboard-dark) .fieldRequired {
- color: #f87171 !important;
-}
-
-.historyTable {
- overflow: scroll;
- height: 75vh;
- text-align: center;
- font-size: small;
- white-space: nowrap;
-}
-
-@media (width <= 900px) {
- .selectInput {
- grid-template-columns: 1fr;
- max-width: 100%;
- }
-
- .selectInput label {
- text-align: left;
- }
-
- .selectInput input,
- .selectInput select {
- max-width: 100%;
- min-width: 0;
- }
-}
+}
\ No newline at end of file
diff --git a/src/components/BMDashboard/ItemList/SelectForm.jsx b/src/components/BMDashboard/ItemList/SelectForm.jsx
index b5f18ec0a3..1a739cd788 100644
--- a/src/components/BMDashboard/ItemList/SelectForm.jsx
+++ b/src/components/BMDashboard/ItemList/SelectForm.jsx
@@ -1,49 +1,66 @@
-import { Form, FormGroup, Label, Input } from 'reactstrap';
+import { useEffect, useMemo } from 'react';
+import { Form, FormGroup, Label } from 'reactstrap';
+import Select from 'react-select';
+import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
+import styles from './ItemListView.module.css';
+import { getReactSelectStyles } from './selectStyles.js';
-export default function SelectForm({ items, setSelectedProject, setSelectedItem }) {
- const darkMode = useSelector(state => state.theme.darkMode);
+const PROJECT_KEY = 'tool_selected_projects';
- let projectsSet = [];
- if (items.length) {
- projectsSet = [...new Set(items.map(el => el.project?.name))];
- }
+export default function SelectForm({ items, setSelectedProject, localValues, setLocalValues }) {
+ const darkMode = useSelector(state => state.theme?.darkMode || false);
- const handleChange = event => {
- setSelectedItem('all');
- setSelectedProject(event.target.value);
+ const projectOptions = useMemo(() => {
+ if (!items?.length) return [];
+ const unique = [...new Set(items.map(i => i.project?.name).filter(Boolean))];
+ return unique.map(name => ({ label: name, value: name }));
+ }, [items]);
+
+ useEffect(() => {
+ try {
+ const saved = JSON.parse(localStorage.getItem(PROJECT_KEY));
+ if (Array.isArray(saved) && saved.length > 0) {
+ setLocalValues(saved);
+ setSelectedProject(saved.map(p => p.value));
+ }
+ } catch (error) {
+ console.error('Failed to parse cached project filters:', error);
+ }
+ }, []);
+
+ const handleChange = selected => {
+ const values = selected || [];
+ setLocalValues(values);
+ setSelectedProject(values.map(v => v.value));
+ localStorage.setItem(PROJECT_KEY, JSON.stringify(values));
};
return (
-
);
}
+
+SelectForm.propTypes = {
+ items: PropTypes.array.isRequired,
+ setSelectedProject: PropTypes.func.isRequired,
+ localValues: PropTypes.array.isRequired,
+ setLocalValues: PropTypes.func.isRequired,
+};
diff --git a/src/components/BMDashboard/ItemList/SelectItem.jsx b/src/components/BMDashboard/ItemList/SelectItem.jsx
index 93527c1e30..e79891a466 100644
--- a/src/components/BMDashboard/ItemList/SelectItem.jsx
+++ b/src/components/BMDashboard/ItemList/SelectItem.jsx
@@ -1,138 +1,99 @@
+import { useEffect, useMemo, useState } from 'react';
+import { Form, FormGroup, Label } from 'reactstrap';
+import Select from 'react-select';
import PropTypes from 'prop-types';
-import { Form, FormGroup, Label, Input } from 'reactstrap';
+import { useSelector } from 'react-redux';
+import styles from './ItemListView.module.css';
+import { getReactSelectStyles } from './selectStyles.js';
-const getConsumablesSet = (items, selectedProject) => {
- if (selectedProject === 'all') {
- return [...new Set(items.filter(m => m.name && m.name !== 'N/A').map(m => m.name))];
- }
- return [
- ...new Set(
- items
- .filter(mat => mat.project?.name === selectedProject && mat.name && mat.name !== 'N/A')
- .map(m => m.name),
- ),
- ];
-};
-
-const getConditionSet = (items, selectedProject) => {
- if (selectedProject === 'all') {
- return [...new Set(items.filter(m => m.condition).map(m => m.condition))];
- }
- return [
- ...new Set(
- items
- .filter(mat => mat.project?.name === selectedProject && mat.condition)
- .map(m => m.condition),
- ),
- ];
-};
-
-const getDefaultSet = (items, selectedProject) => {
- if (selectedProject === 'all') {
- return [...new Set(items.map(m => m.itemType?.name))];
- }
- return [
- ...new Set(
- items.filter(mat => mat.project?.name === selectedProject).map(m => m.itemType?.name),
- ),
- ];
-};
+const ITEM_KEY = 'tool_selected_items';
export default function SelectItem({
items,
selectedProject,
selectedItem,
setSelectedItem,
- selectedToolStatus,
- setSelectedToolStatus,
- selectedCondition,
- setSelectedCondition,
label,
- darkMode,
}) {
- let itemSet = [];
+ const darkMode = useSelector(state => state.theme?.darkMode || false);
+ const [localValues, setLocalValues] = useState([]);
+
+ const itemOptions = useMemo(() => {
+ if (!items?.length) return [];
- if (items && items.length > 0) {
- if (label === 'Consumables') {
- itemSet = getConsumablesSet(items, selectedProject);
- } else if (label === 'Tool Status') {
- itemSet = ['Using', 'Available', 'Under Maintenance'];
- } else if (label === 'Condition') {
- itemSet = getConditionSet(items, selectedProject);
- } else {
- itemSet = getDefaultSet(items, selectedProject);
+ let list = items;
+ if (Array.isArray(selectedProject) && selectedProject.length > 0) {
+ list = items.filter(i => selectedProject.includes(i.project?.name));
}
- }
- const darkStyle = darkMode
- ? { backgroundColor: '#1e293b', color: '#e5e7eb', borderColor: '#334155' }
- : undefined;
+ const names = [...new Set(list.map(i => i.itemType?.name).filter(Boolean))];
+ return names.map(name => ({ label: name, value: name }));
+ }, [items, selectedProject]);
- const getSelectValue = () => {
- if (label === 'Condition') return selectedCondition;
- if (label === 'Tool Status') return selectedToolStatus;
- return selectedItem;
- };
+ useEffect(() => {
+ try {
+ const saved = JSON.parse(localStorage.getItem(ITEM_KEY));
+ if (Array.isArray(saved)) {
+ setLocalValues(saved);
+ setSelectedItem(saved.map(s => s.value));
+ }
+ } catch (error) {
+ console.error('Failed to parse cached item filter scope:', error);
+ }
+ }, [setSelectedItem]);
+
+ useEffect(() => {
+ if (Array.isArray(selectedItem) && selectedItem.length === 0) {
+ setLocalValues([]);
+ }
+ }, [selectedItem]);
+
+ useEffect(() => {
+ if (localValues.length > 0 && itemOptions.length > 0) {
+ const activeKeys = itemOptions.map(opt => opt.value);
+ const alignedValues = localValues.filter(val => activeKeys.includes(val.value));
+
+ if (alignedValues.length !== localValues.length) {
+ setLocalValues(alignedValues);
+ setSelectedItem(alignedValues.map(v => v.value));
+ localStorage.setItem(ITEM_KEY, JSON.stringify(alignedValues));
+ }
+ }
+ }, [itemOptions, localValues, setSelectedItem]);
- const handleSelectChange = e => {
- const val = e.target.value;
- if (label === 'Tool Status') setSelectedToolStatus(val);
- else if (label === 'Condition') setSelectedCondition(val);
- else setSelectedItem(val);
+ const handleChange = selected => {
+ const values = selected || [];
+ setLocalValues(values);
+ setSelectedItem(values.map(v => v.value));
+ localStorage.setItem(ITEM_KEY, JSON.stringify(values));
};
return (
-