diff --git a/src/components/BMDashboard/Equipment/List/EquipmentList.jsx b/src/components/BMDashboard/Equipment/List/EquipmentList.jsx index fd4f7fbd32..15b8e53b0b 100644 --- a/src/components/BMDashboard/Equipment/List/EquipmentList.jsx +++ b/src/components/BMDashboard/Equipment/List/EquipmentList.jsx @@ -1,21 +1,43 @@ import { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { + FaCubes, + FaShoppingCart, + FaRecycle, + FaWrench, + FaTools, + FaRulerCombined, +} from 'react-icons/fa'; import useTheme from '../../../../hooks/useTheme'; import EquipmentsTable from './EquipmentsTable'; import EquipmentsInputs from './EquipmentsInputs'; +import InventoryNavBar from '../../InventoryTypesList/InventoryNavBar'; import styles from './Equipments.module.css'; +const siblingCategories = [ + { label: 'Materials', route: '/bmdashboard/materials', icon: }, + { label: 'Consumables', route: '/bmdashboard/consumables', icon: }, + { label: 'Reusables', route: '/bmdashboard/reusables', icon: }, + { label: 'Tools', route: '/bmdashboard/tools', icon: }, + { label: 'Units', route: '/bmdashboard/units', icon: }, +]; + function EquipmentList() { const [equipment, setEquipment] = useState({ label: 'All Equipments', value: '0' }); const [project, setProject] = useState({ label: 'All Projects', value: '0' }); - - // Use the custom hook to handle dark mode + const darkMode = useSelector(state => state.theme.darkMode); useTheme(); return (
-
EQUIPMENTS
+
+ + EQUIPMENT +
+ {/* Inventory Navigation Bar */} + + + ← All Inventory Types + +
+ {categories.map(cat => ( + + {cat.icon} + + ))} +
+
+ ); +} + +InventoryNavBar.propTypes = { + categories: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string.isRequired, + route: PropTypes.string.isRequired, + icon: PropTypes.node.isRequired, + }), + ).isRequired, + styles: PropTypes.objectOf(PropTypes.string).isRequired, +}; + +export default InventoryNavBar; diff --git a/src/components/BMDashboard/InventoryTypesList/InventoryTypesList.jsx b/src/components/BMDashboard/InventoryTypesList/InventoryTypesList.jsx index bc1bc415e2..bd08080e8b 100644 --- a/src/components/BMDashboard/InventoryTypesList/InventoryTypesList.jsx +++ b/src/components/BMDashboard/InventoryTypesList/InventoryTypesList.jsx @@ -1,32 +1,46 @@ import { useState, useEffect } from 'react'; import { connect } from 'react-redux'; -import { useHistory } from 'react-router-dom'; - +import { useHistory, Link } from 'react-router-dom'; import { fetchInvTypeByType } from '~/actions/bmdashboard/invTypeActions'; import { fetchInvUnits } from '~/actions/bmdashboard/invUnitActions'; import { Accordion, Card, Button } from 'react-bootstrap'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; - +import { + FaCubes, + FaShoppingCart, + FaTools, + FaRecycle, + FaWrench, + FaRulerCombined, +} from 'react-icons/fa'; import BMError from '../shared/BMError'; import TypesTable from './TypesTable'; import UnitsTable from './invUnitsTable'; import AccordionToggle from './AccordionToggle'; import styles from './TypesList.module.css'; +const categoryIcons = { + Materials: , + Consumables: , + Equipment: , + Reusables: , + Tools: , +}; + export function InventoryTypesList(props) { const { invUnits, errors, dispatch } = props; - const history = useHistory(); - - const categories = ['Materials', 'Consumables', 'Equipments', 'Reusables', 'Tools']; + const categories = [ + { label: 'Materials', route: '/bmdashboard/materials' }, + { label: 'Consumables', route: '/bmdashboard/consumables' }, + { label: 'Equipment', route: '/bmdashboard/equipment' }, + { label: 'Reusables', route: '/bmdashboard/reusables' }, + { label: 'Tools', route: '/bmdashboard/tools' }, + ]; const [isError, setIsError] = useState(false); const [currentTime, setCurrentTime] = useState(new Date()); - const handleBack = () => { - history.goBack(); - }; - useEffect(() => { dispatch(fetchInvTypeByType('Materials')); dispatch(fetchInvTypeByType('Consumables')); @@ -52,37 +66,53 @@ export function InventoryTypesList(props) { return (

All Inventory Types

-
Time: setCurrentTime(date)} - dateFormat="MM-dd-yyyy hh:mm:ss" + dateFormat="MM-dd-yyyy HH:mm:ss" id="timestamp" showTimeInput + timeFormat="HH:mm" + portalId="root" + popperPlacement="bottom-start" />
- {categories?.map((category, index) => { return ( - + - {category} + {categoryIcons[category.label]} + e.stopPropagation()} + > + {category.label} + - + ); })} - - Unit of Measurement + + + + e.stopPropagation()} + > + Unit of Measurement + @@ -91,12 +121,6 @@ export function InventoryTypesList(props) { - -
- -
); } diff --git a/src/components/BMDashboard/InventoryTypesList/TypesList.module.css b/src/components/BMDashboard/InventoryTypesList/TypesList.module.css index a2ea65628f..3702830512 100644 --- a/src/components/BMDashboard/InventoryTypesList/TypesList.module.css +++ b/src/components/BMDashboard/InventoryTypesList/TypesList.module.css @@ -259,6 +259,35 @@ font-size: 10px; } +/* ── Datepicker time input alignment fix ── */ +:global(.react-datepicker__input-time-container) { + display: flex !important; + align-items: center !important; + padding: 8px !important; + width: 100% !important; + box-sizing: border-box !important; + margin: 0 !important; +} + +:global(.react-datepicker-time__caption) { + flex-shrink: 0; +} + +:global(.react-datepicker-time__input-container) { + flex: 1 !important; + width: calc(100% - 60px) !important; +} + +:global(.react-datepicker-time__input) { + width: 100% !important; + margin: 0 !important; +} + +:global(.react-datepicker-time__input) input[type="time"] { + width: 100% !important; + min-width: 0 !important; +} + /* Dark mode table styling */ :global(.dark-mode) .typesListContainer table, :global(.bm-dashboard-dark) .typesListContainer table { @@ -391,9 +420,26 @@ background-color: #2a2a2a !important; } +/* ── Dark mode time input container ── */ :global(.dark-mode) :global(.react-datepicker__input-time-container), :global(.bm-dashboard-dark) :global(.react-datepicker__input-time-container) { background-color: #3a3a3a !important; + color: #fff !important; +} + +/* Native time input dark mode — color-scheme forces browser UI to dark */ +:global(.dark-mode) :global(.react-datepicker-time__input) input[type="time"], +:global(.bm-dashboard-dark) :global(.react-datepicker-time__input) input[type="time"] { + background-color: #2a2a2a !important; + color: #fff !important; + border: 1px solid rgb(255 255 255 / 20%) !important; + border-radius: 4px; + color-scheme: dark; +} + +/* Light mode navigation arrow visibility fix */ +:global(.react-datepicker__navigation-icon::before) { + border-color: #333 !important; } :global(.dark-mode) :global(.react-datepicker__navigation-icon::before), @@ -615,6 +661,7 @@ } :global(.bm-dashboard-dark) .typesListContainer :global(.react-datepicker__input-time-container) { + background-color: #1c2541 !important; color: #fff !important; } @@ -622,4 +669,125 @@ background-color: #1c2541 !important; color: #fff !important; border-color: #3a506b !important; + color-scheme: dark; +} + +/* Category icons */ +.categoryIcon { + display: inline-flex; + align-items: center; + margin-right: 8px; + font-size: 1rem; + vertical-align: middle; + color: var(--color-h500); +} + +:global(.dark-mode) .categoryIcon, +:global(.bm-dashboard-dark) .categoryIcon { + color: #5eb3e4; +} + +/* Category hyperlinks */ +.categoryLink { + color: var(--color-text); + text-decoration: none; + font-weight: bold; +} + +.categoryLink:hover { + color: #fff; + text-decoration: underline; +} + +:global(.dark-mode) .categoryLink, +:global(.bm-dashboard-dark) .categoryLink { + color: #fff; +} + +:global(.dark-mode) .categoryLink:hover, +:global(.bm-dashboard-dark) .categoryLink:hover { + color: #5eb3e4; +} + +/* ── Inventory Navigation Bar ── */ +.inventoryNav { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.returnBtn { + display: inline-flex; + align-items: center; + padding: 7px 16px; + background-color: var(--color-h500); + color: #fff; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + font-size: 0.9rem; + white-space: nowrap; +} + +.returnBtn:hover { + background-color: var(--color-h100); + color: #fff; +} + +.categoryIcons { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.categoryIconLink { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 3px; + padding: 6px 10px; + border: 1px solid var(--color-h500); + border-radius: 8px; + color: var(--color-h500); + text-decoration: none; + font-size: 0.75rem; + transition: background-color 0.2s, color 0.2s; +} + +.categoryIconLink:hover { + background-color: var(--color-h500); + color: #fff; + border-color: var(--color-h500); +} + +.iconWrapper { + font-size: 1.2rem; +} + +.iconLabel { + font-size: 0.7rem; + font-weight: 500; +} + +:global(.bm-dashboard-dark) .returnBtn { + background-color: #2e5061; + color: #fff; } + +:global(.bm-dashboard-dark) .returnBtn:hover { + background-color: #3a506b; +} + +:global(.bm-dashboard-dark) .categoryIconLink { + border-color: #4dabf7; + color: #4dabf7; +} + +:global(.bm-dashboard-dark) .categoryIconLink:hover { + background-color: #4dabf7; + color: #0f172a; + border-color: #4dabf7; +} \ No newline at end of file diff --git a/src/components/BMDashboard/ItemList/ItemListView.jsx b/src/components/BMDashboard/ItemList/ItemListView.jsx index 677ae90af4..33b82b8afd 100644 --- a/src/components/BMDashboard/ItemList/ItemListView.jsx +++ b/src/components/BMDashboard/ItemList/ItemListView.jsx @@ -3,13 +3,38 @@ import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; - +import { + FaCubes, + FaShoppingCart, + FaTools, + FaRecycle, + FaWrench, + FaRulerCombined, +} from 'react-icons/fa'; import BMError from '../shared/BMError'; import SelectForm from './SelectForm'; import SelectItem from './SelectItem'; import ItemsTable from './ItemsTable'; +import InventoryNavBar from '../InventoryTypesList/InventoryNavBar'; import styles from './ItemListView.module.css'; +const allCategories = [ + { label: 'Materials', route: '/bmdashboard/materials', icon: }, + { label: 'Consumables', route: '/bmdashboard/consumables', icon: }, + { label: 'Equipment', route: '/bmdashboard/equipment', icon: }, + { label: 'Reusables', route: '/bmdashboard/reusables', icon: }, + { label: 'Tools', route: '/bmdashboard/tools', icon: }, + { label: 'Units', route: '/bmdashboard/units', icon: }, +]; + +const categoryIcons = { + Materials: , + Consumables: , + Equipment: , + Reusables: , + Tools: , +}; + export function ItemListView({ itemType, items, @@ -19,9 +44,10 @@ export function ItemListView({ children, }) { const darkMode = useSelector(state => state.theme.darkMode); - const [filteredItems, setFilteredItems] = useState(items || []); - const [selectedProject, setSelectedProject] = useState('all'); - const [selectedItem, setSelectedItem] = useState('all'); + const [filteredItems, setFilteredItems] = useState([]); + const [selectedProject, setSelectedProject] = useState([]); // Array of strings + const [selectedItem, setSelectedItem] = useState([]); // Array of strings + const [localValues, setLocalValues] = useState([]); const [isError, setIsError] = useState(false); const [selectedTime, setSelectedTime] = useState(new Date()); @@ -30,32 +56,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 +183,7 @@ export function ItemListView({ if (isError) { return (
-

- {itemType} - {' List'} -

+

{itemType} List

); @@ -163,7 +191,16 @@ export function ItemListView({ return (
-

{itemType}

+

+ {categoryIcons[itemType]} + {itemType} +

+ + {/* Inventory Navigation Bar */} + cat.label !== itemType)} + styles={styles} + />
@@ -189,7 +226,9 @@ export function ItemListView({
)} diff --git a/src/components/BMDashboard/ItemList/ItemListView.module.css b/src/components/BMDashboard/ItemList/ItemListView.module.css index 47d65ea217..5f4dd8803d 100644 --- a/src/components/BMDashboard/ItemList/ItemListView.module.css +++ b/src/components/BMDashboard/ItemList/ItemListView.module.css @@ -12,6 +12,7 @@ table td { } .itemsTableContainer { + position: relative; overflow: auto; max-height: 600px; border: 1px solid #e5e7eb; @@ -53,7 +54,6 @@ table td { color: black; } - .searchRow { display: flex; align-items: center; @@ -122,15 +122,17 @@ table td { text-decoration: underline; } +/* Changed from grid to flexbox to handle variable number of filter items + without overflowing — grid with fixed columns caused SelectItem to push + off-screen when there were fewer items than columns */ .selectInput { - display: grid; - grid-template-columns: auto 1fr auto 1fr auto 1fr; + display: flex; align-items: center; + flex-wrap: wrap; gap: 15px; width: 100%; max-width: 1200px; margin: 0 auto 10px; - overflow: visible; } .selectInput label { @@ -244,8 +246,6 @@ table td { .darkMode { color: #e8edf4; background-color: #0d1117 !important; - -/* color: #fff !important; */ min-height: 100vh; padding-bottom: 2rem; } @@ -553,7 +553,8 @@ table td { @media (width <= 900px) { .selectInput { - grid-template-columns: 1fr; + flex-direction: column; + align-items: flex-start; max-width: 100%; } @@ -565,5 +566,214 @@ table td { .selectInput select { max-width: 100%; min-width: 0; + width: 100%; + } +} + +/* ── Inventory Navigation Bar ── */ +.inventoryNav { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.returnBtn { + display: inline-flex; + align-items: center; + padding: 7px 16px; + background-color: #0d5675; + color: #fff; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + font-size: 0.9rem; + white-space: nowrap; +} + +.returnBtn:hover { + background-color: #1a73c0; + color: #fff; +} + +.categoryIcons { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.categoryIconLink { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 3px; + padding: 6px 10px; + border: 1px solid #78bdda; + border-radius: 8px; + color: #0d5675; + text-decoration: none; + font-size: 0.75rem; + transition: background-color 0.2s, color 0.2s; +} + +.categoryIconLink:hover { + background-color: #0d5675; + color: #fff; + border-color: #0d5675; +} + +.iconWrapper { + font-size: 1.2rem; +} + +.iconLabel { + font-size: 0.7rem; + font-weight: 500; +} + +.darkMode .returnBtn { + background-color: #2e5061; + color: #fff; +} + +.darkMode .returnBtn:hover { + background-color: #1a73c0; +} + +.darkMode .categoryIconLink { + border-color: #4a9bc2; + color: #5eb3e4; +} + +.darkMode .categoryIconLink:hover { + background-color: #2e5061; + color: #fff; + border-color: #4a9bc2; +} + +/* ── Page Title with Icon ── */ +.pageTitle { + display: flex; + align-items: center; + gap: 8px; +} + +.pageTitleIcon { + display: inline-flex; + align-items: center; + font-size: 1.2rem; + color: #0d5675; +} + +.darkMode .pageTitleIcon { + color: #5eb3e4; +} + +/* ── Mobile responsive improvements ── */ +@media (width <= 768px) { + .itemsListContainer { + padding: 0 0.5rem; + } + + .itemsTableContainer { + max-height: 70vh; + } + + .itemsTableContainer::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 24px; + background: linear-gradient(to right, transparent, rgb(0 0 0 / 8%)); + pointer-events: none; + } + + .darkMode .itemsTableContainer::after { + background: linear-gradient(to right, transparent, rgb(255 255 255 / 8%)); + } + + .inventoryNav { + gap: 10px; + } + + .returnBtn { + flex: 1 1 100%; + justify-content: center; + text-align: center; + } + + .categoryIcons { + width: 100%; + justify-content: flex-start; + } + + .pageTitle { + font-size: 1.4rem; + } + + .searchRow { + flex-direction: column; + align-items: stretch; + } + + .searchBox { + flex-direction: column; + align-items: flex-start; + width: 100%; + } + + .searchBox input { + width: 100%; + max-width: 100%; + } + + .foundCount { + text-align: left; + } + + .buttonsRow { + flex-direction: column; + gap: 8px; + } + + .btnPrimary { + width: 100%; + } + + .paginationBar { + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .pageButtons { + justify-content: center; + flex-wrap: wrap; + } + + .rowsPerPage { + justify-content: space-between; } } + +@media (width <= 480px) { + .pageButtons button { + min-width: 28px; + height: 28px; + font-size: 0.8rem; + padding: 0 5px; + } + + .categoryIconLink { + padding: 5px 8px; + font-size: 0.7rem; + } + + .iconWrapper { + font-size: 1rem; + } +} \ No newline at end of file diff --git a/src/components/BMDashboard/ItemList/SelectForm.jsx b/src/components/BMDashboard/ItemList/SelectForm.jsx index b5f18ec0a3..8a05c5c732 100644 --- a/src/components/BMDashboard/ItemList/SelectForm.jsx +++ b/src/components/BMDashboard/ItemList/SelectForm.jsx @@ -1,49 +1,72 @@ -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); +export default function SelectForm({ + items, + setSelectedProject, + localValues, + setLocalValues, + itemType, +}) { + const darkMode = useSelector(state => state.theme?.darkMode || false); + const projectKey = `${itemType}_selected_projects`; - let projectsSet = []; - if (items.length) { - projectsSet = [...new Set(items.map(el => el.project?.name))]; - } + 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]); - const handleChange = event => { - setSelectedItem('all'); - setSelectedProject(event.target.value); + useEffect(() => { + try { + const saved = JSON.parse(localStorage.getItem(projectKey)); + 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(projectKey, JSON.stringify(values)); }; return ( -
- - - e.preventDefault()}> + + + - {itemSet.length > 0 ? ( - <> - - {itemSet.map(name => ( - - ))} - - ) : ( - - )} - + e.preventDefault()}> + + +