diff --git a/packages/webapp/public/locales/en/translation.json b/packages/webapp/public/locales/en/translation.json index 790d697afa..b10441791c 100644 --- a/packages/webapp/public/locales/en/translation.json +++ b/packages/webapp/public/locales/en/translation.json @@ -170,7 +170,10 @@ "INDIVIDUALS": "Individuals", "MALE": "Male", "TITLE": "Filter animals" - } + }, + "SEARCH_INVENTORY_PLACEHOLDER": "Search your inventory", + "SHOWING_RESULTS_WITH_COUNT_one": "Showing {{count}} result", + "SHOWING_RESULTS_WITH_COUNT_other": "Showing {{count}} results" }, "BED_PLAN": { "LENGTH_OF_BED": "Length of beds", diff --git a/packages/webapp/public/locales/es/translation.json b/packages/webapp/public/locales/es/translation.json index e0d8350563..1bbdd4267f 100644 --- a/packages/webapp/public/locales/es/translation.json +++ b/packages/webapp/public/locales/es/translation.json @@ -170,7 +170,10 @@ "INDIVIDUALS": "MISSING", "MALE": "MISSING", "TITLE": "MISSING" - } + }, + "SEARCH_INVENTORY_PLACEHOLDER": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_one": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_other": "MISSING" }, "BED_PLAN": { "LENGTH_OF_BED": "Largo de camas", diff --git a/packages/webapp/public/locales/fr/translation.json b/packages/webapp/public/locales/fr/translation.json index a6f999cfe4..a15faed2ba 100644 --- a/packages/webapp/public/locales/fr/translation.json +++ b/packages/webapp/public/locales/fr/translation.json @@ -170,7 +170,10 @@ "INDIVIDUALS": "MISSING", "MALE": "MISSING", "TITLE": "MISSING" - } + }, + "SEARCH_INVENTORY_PLACEHOLDER": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_one": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_other": "MISSING" }, "BED_PLAN": { "LENGTH_OF_BED": "Longueur des plates-bandes", diff --git a/packages/webapp/public/locales/pt/translation.json b/packages/webapp/public/locales/pt/translation.json index b04c66bec0..b02b7c52b0 100644 --- a/packages/webapp/public/locales/pt/translation.json +++ b/packages/webapp/public/locales/pt/translation.json @@ -170,7 +170,10 @@ "INDIVIDUALS": "MISSING", "MALE": "MISSING", "TITLE": "MISSING" - } + }, + "SEARCH_INVENTORY_PLACEHOLDER": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_one": "MISSING", + "SHOWING_RESULTS_WITH_COUNT_other": "MISSING" }, "BED_PLAN": { "LENGTH_OF_BED": "Comprimento dos canteiros ", diff --git a/packages/webapp/src/components/Animals/Inventory/index.tsx b/packages/webapp/src/components/Animals/Inventory/index.tsx index e51607023f..21b6dce8c9 100644 --- a/packages/webapp/src/components/Animals/Inventory/index.tsx +++ b/packages/webapp/src/components/Animals/Inventory/index.tsx @@ -14,44 +14,85 @@ */ import Table from '../../../components/Table'; import Layout from '../../../components/Layout'; +import PureSearchBarWithBackdrop from '../../PopupFilter/PureSearchWithBackdrop'; +import NoSearchResults from '../../../components/Card/NoSearchResults'; import type { AnimalInventory } from '../../../containers/Animals/Inventory/useAnimalInventory'; import { TableV2Column, TableKind } from '../../Table/types'; -import type { DefaultTheme } from '@mui/styles'; +import type { Dispatch, SetStateAction } from 'react'; +import styles from './styles.module.scss'; +import clsx from 'clsx'; + +export type SearchProps = { + searchString: string | null | undefined; + setSearchString: Dispatch>; + placeHolderText: string; + searchResultsText: string; +}; const PureAnimalInventory = ({ - tableData, + filteredInventory, animalsColumns, - theme, - isMobile, + zIndexBase, + backgroundColor, + isDesktop, + searchProps, }: { - tableData: AnimalInventory[]; + filteredInventory: AnimalInventory[]; animalsColumns: TableV2Column[]; - theme: DefaultTheme; - isMobile: boolean; + zIndexBase: number; + backgroundColor: string; + isDesktop: boolean; + searchProps: SearchProps; }) => { + const { searchString, setSearchString, placeHolderText, searchResultsText } = searchProps; + const hasSearchResults = filteredInventory.length !== 0; + return ( - +
+ setSearchString(e.target.value)} + isSearchActive={!!searchString} + placeholderText={placeHolderText} + zIndexBase={zIndexBase} + isDesktop={isDesktop} + className={clsx(isDesktop ? styles.searchBarDesktop : styles.searchBar)} + /> +
+ {searchResultsText} +
+
+ {hasSearchResults ? ( +
+ ) : ( + + )} ); }; diff --git a/packages/webapp/src/components/Animals/Inventory/styles.module.scss b/packages/webapp/src/components/Animals/Inventory/styles.module.scss new file mode 100644 index 0000000000..1974e0306f --- /dev/null +++ b/packages/webapp/src/components/Animals/Inventory/styles.module.scss @@ -0,0 +1,52 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +.noSearchResults { + margin-top: 24px; +} + +.noSearchResultsDesktop { + margin-top: 40px; +} + +.searchBarDesktop { + width: 240px; +} + +.searchBar { + width: 100%; +} + +.searchAndFilterDesktop { + display: inline-flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; +} + +.searchAndFilter { + display: inline-grid; + gap: 4px; + padding: 8px 16px 8px 16px; + background-color: var(--Colors-Primary-Primary-teal-50); +} + +.searchResultsDesktop { + margin-left: 8px; +} + +.searchResults { + text-align: center; +} \ No newline at end of file diff --git a/packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/index.jsx b/packages/webapp/src/components/PopupFilter/PureCollapsingSearch/index.jsx similarity index 83% rename from packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/index.jsx rename to packages/webapp/src/components/PopupFilter/PureCollapsingSearch/index.jsx index ddf4f41282..269164e0f5 100644 --- a/packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/index.jsx +++ b/packages/webapp/src/components/PopupFilter/PureCollapsingSearch/index.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2023 LiteFarm.org + * Copyright 2023-2024 LiteFarm.org * This file is part of LiteFarm. * * LiteFarm is free software: you can redistribute it and/or modify @@ -22,13 +22,14 @@ import Input from '../../Form/Input'; import TextButton from '../../Form/Button/TextButton'; import { Modal } from '../../Modals'; -export default function PureCollapsibleSearch({ +export default function PureCollapsingSearch({ value, isSearchActive, onChange, placeholderText, className, containerRef, + isDesktop, }) { const searchRef = useRef(null); const [modalStyle, setModalStyle] = useState({}); @@ -82,7 +83,10 @@ export default function PureCollapsibleSearch({ }, [searchOverlayOpen]); return ( -
+
{isSearchActive && ( -
-
+
+
)}
); } -PureCollapsibleSearch.propTypes = { +PureCollapsingSearch.propTypes = { value: PropTypes.string, isSearchActive: PropTypes.bool, onChange: PropTypes.func, @@ -132,9 +140,10 @@ PureCollapsibleSearch.propTypes = { className: PropTypes.string, overlayModalOnButton: PropTypes.bool, containerRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + isDesktop: PropTypes.bool, }; -PureCollapsibleSearch.defaultProps = { +PureCollapsingSearch.defaultProps = { placeholderText: '', isSearchActive: false, className: '', diff --git a/packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/styles.module.scss b/packages/webapp/src/components/PopupFilter/PureCollapsingSearch/styles.module.scss similarity index 84% rename from packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/styles.module.scss rename to packages/webapp/src/components/PopupFilter/PureCollapsingSearch/styles.module.scss index 83ae62e836..53f51df266 100644 --- a/packages/webapp/src/components/PopupFilter/PureCollapsibleSearch/styles.module.scss +++ b/packages/webapp/src/components/PopupFilter/PureCollapsingSearch/styles.module.scss @@ -1,5 +1,5 @@ /* - * Copyright 2023 LiteFarm.org + * Copyright 2023-2024 LiteFarm.org * This file is part of LiteFarm. * * LiteFarm is free software: you can redistribute it and/or modify @@ -96,24 +96,15 @@ } } -/* Desktop view */ -@media only screen and (min-width: 768px) { - .container { - .searchBar { - display: block; - min-width: 240px; - flex-grow: 1; - } - } - - .searchButton, - .circleContainer, - .circle { - display: none; +.desktopContainer { + .searchBar { + display: block; + min-width: 240px; + flex-grow: 1; } +} - // Only relevant if resizing screen beyond breakpoint with modal open - .modalContent { - display: none; - } +.displayNone { + display: none; } + diff --git a/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/index.jsx b/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/index.jsx new file mode 100644 index 0000000000..8277b507dc --- /dev/null +++ b/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/index.jsx @@ -0,0 +1,97 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +import { useState } from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import styles from './styles.module.scss'; +import Input from '../../Form/Input'; +import { Backdrop } from '@mui/material'; + +/** + * A search bar component that display backdrop when on medium and small screens for a more immersive search experience. + * + * @component + * @param {Object} props - The component props. + * @param {string | null | undefined} props.value - The current value of the search bar. + * @param {Function} props.onChange - The callback function to handle changes in the search bar value. + * @param {string} props.placeholderText - The placeholder text to be displayed in the search bar. + * @param {string} props.className - Additional CSS classes for styling purposes. + * @param {boolean} props.isDesktop - Flag indicating if the component is being used in a desktop environment. + * @param {number} props.zIndexBase - The base z-index value for positioning the search bar and backdrop. + * @returns {JSX.Element} Returns the PureSearchBarWithBackdrop component. + * + * @example + * // Example usage of PureSearchBarWithBackdrop + * + */ +export default function PureSearchBarWithBackdrop({ + value, + onChange, + placeholderText, + className, + isDesktop, + zIndexBase, +}) { + const [searchOverlayOpen, setSearchOverlayOpen] = useState(false); + const onSearchOpen = () => { + isDesktop ? null : setSearchOverlayOpen(true); + }; + const onSearchClose = () => { + setSearchOverlayOpen(false); + }; + + return ( + <> + + + + ); +} + +PureSearchBarWithBackdrop.propTypes = { + value: PropTypes.string, + onChange: PropTypes.func, + placeholderText: PropTypes.string, + className: PropTypes.string, + isDesktop: PropTypes.bool, +}; + +PureSearchBarWithBackdrop.defaultProps = { + placeholderText: '', + isSearchActive: false, + className: '', + isDesktop: false, + zIndexBase: 1201, +}; diff --git a/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/styles.module.scss b/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/styles.module.scss new file mode 100644 index 0000000000..328d9889dc --- /dev/null +++ b/packages/webapp/src/components/PopupFilter/PureSearchWithBackdrop/styles.module.scss @@ -0,0 +1,22 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +.searchInput { + min-width: 200px; +} + +.searchInputDesktop { + display: block; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Animals/Inventory/index.tsx b/packages/webapp/src/containers/Animals/Inventory/index.tsx index 6d3190144e..956a517a23 100644 --- a/packages/webapp/src/containers/Animals/Inventory/index.tsx +++ b/packages/webapp/src/containers/Animals/Inventory/index.tsx @@ -13,7 +13,7 @@ * GNU General Public License for more details, see . */ import { useCallback, useMemo, useState } from 'react'; -import PureAnimalInventory from '../../../components/Animals/Inventory'; +import PureAnimalInventory, { SearchProps } from '../../../components/Animals/Inventory'; import { useTranslation } from 'react-i18next'; import { useTheme } from '@mui/styles'; import { useMediaQuery } from '@mui/material'; @@ -23,6 +23,7 @@ import useAnimalInventory from './useAnimalInventory'; import type { AnimalInventory } from './useAnimalInventory'; import ActionMenu from '../../../components/ActionMenu'; import KPI from './KPI'; +import useSearchFilter from '../../../containers/hooks/useSearchFilter'; import styles from './styles.module.scss'; import AnimalsFilter from '../AnimalsFilter'; import { useFilteredInventory } from './useFilteredInventory'; @@ -44,7 +45,9 @@ function AnimalInventory({ isCompactSideMenu }: AnimalInventoryProps) { const { t } = useTranslation(['translation', 'animal', 'common']); const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); + const zIndexBase = theme.zIndex.drawer; + const backgroundColor = theme.palette.background.paper; const { inventory, isLoading } = useAnimalInventory(); @@ -75,23 +78,23 @@ function AnimalInventory({ isCompactSideMenu }: AnimalInventoryProps) { text={d.identification} icon={d.icon} iconBorder={!d.batch} - subtext={isMobile ? `${d.type} / ${d.breed}` : null} + subtext={isDesktop ? null : `${d.type} / ${d.breed}`} highlightedText={d.batch ? d.count : null} /> ), }, { - id: isMobile ? null : 'type', + id: isDesktop ? 'type' : null, label: t('ANIMAL.ANIMAL_TYPE').toLocaleUpperCase(), format: (d: AnimalInventory) => , }, { - id: isMobile ? null : 'breed', + id: isDesktop ? 'breed' : null, label: t('ANIMAL.ANIMAL_BREED').toLocaleUpperCase(), format: (d: AnimalInventory) => , }, { - id: isMobile ? null : 'groups', + id: isDesktop ? 'groups' : null, label: t('ANIMAL.ANIMAL_GROUPS').toLocaleUpperCase(), format: (d: AnimalInventory) => ( , columnProps: { - style: { width: '40px', padding: `0 ${isMobile ? 8 : 12}px` }, + style: { width: '40px', padding: `0 ${isDesktop ? 12 : 8}px` }, }, sortable: false, }, ], - [t, isMobile], + [t, isDesktop], ); + const makeAnimalsSearchableString = (animal: AnimalInventory) => { + return [animal.identification, animal.type, animal.breed, ...animal.groups, animal.count] + .filter(Boolean) + .join(' '); + }; + + const [searchAndFilteredInventory, searchString, setSearchString] = useSearchFilter( + filteredInventory, + makeAnimalsSearchableString, + ); + + const searchProps: SearchProps = { + searchString, + setSearchString, + placeHolderText: t('ANIMAL.SEARCH_INVENTORY_PLACEHOLDER'), + searchResultsText: t('ANIMAL.SHOWING_RESULTS_WITH_COUNT', { + count: searchAndFilteredInventory?.length, + }), + }; + return ( !isLoading && ( <> @@ -126,10 +149,12 @@ function AnimalInventory({ isCompactSideMenu }: AnimalInventoryProps) {
{ const { t } = useTranslation(); + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); const managementPlans = useSelector(managementPlansSelector); const { EXPENSE_TYPE: expenseTypeFilter, REVENUE_TYPE: revenueTypeFilter } = useSelector( transactionsFilterSelector, @@ -127,11 +131,12 @@ const Finances = ({ history }) => {
- setSearchString(e.target.value)} isSearchActive={!!searchString} containerRef={overlayRef} + isDesktop={isDesktop} />
diff --git a/packages/webapp/src/containers/hooks/useSearchFilter.js b/packages/webapp/src/containers/hooks/useSearchFilter.js index caecc93604..d28cdf614c 100644 --- a/packages/webapp/src/containers/hooks/useSearchFilter.js +++ b/packages/webapp/src/containers/hooks/useSearchFilter.js @@ -1,5 +1,5 @@ /* - * Copyright 2023 LiteFarm.org + * Copyright 2023-2024 LiteFarm.org * This file is part of LiteFarm. * * LiteFarm is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ import { useState, useMemo } from 'react'; * @param {Array} items - The items to filter. * @param {function} getSearchableString - Function that takes in a array item and constructs the string to search on (e.g. concatenates the properties of interest) * @param {string} providedFilter - the search string -- provide if the search string state is handled outside the component calling the hook - * @returns {Object} - An object containing the current filter, a setter for the filter, and the filtered items. + * @returns {Array} - An object containing the current filter, a setter for the filter, and the filtered items. */ export const useSearchFilter = ( diff --git a/packages/webapp/src/stories/PureCollapsibleSearch/PureCollapsibleSearch.stories.jsx b/packages/webapp/src/stories/PureCollapsingSearch/PureCollapsibleSearch.stories.jsx similarity index 76% rename from packages/webapp/src/stories/PureCollapsibleSearch/PureCollapsibleSearch.stories.jsx rename to packages/webapp/src/stories/PureCollapsingSearch/PureCollapsibleSearch.stories.jsx index 5138df5c18..39b76de2e1 100644 --- a/packages/webapp/src/stories/PureCollapsibleSearch/PureCollapsibleSearch.stories.jsx +++ b/packages/webapp/src/stories/PureCollapsingSearch/PureCollapsibleSearch.stories.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2023 LiteFarm.org + * Copyright 2023-2024 LiteFarm.org * This file is part of LiteFarm. * * LiteFarm is free software: you can redistribute it and/or modify @@ -14,18 +14,22 @@ */ import { Suspense, useRef } from 'react'; -import PureCollapsibleSearch from '../../components/PopupFilter/PureCollapsibleSearch'; +import PureCollapsingSearch from '../../components/PopupFilter/PureCollapsingSearch'; import { componentDecorators } from '../Pages/config/Decorators'; import { Main, Info } from '../../components/Typography'; +import { useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/styles'; export default { - title: 'Components/PureCollapsibleSearch', - component: PureCollapsibleSearch, + title: 'Components/PureCollapsingSearch', + component: PureCollapsingSearch, decorators: componentDecorators, }; -const CollapsibleSearchContainer = (props) => { +const CollapsingSearchContainer = (props) => { const containerRef = useRef(null); + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); return ( @@ -41,8 +45,9 @@ const CollapsibleSearchContainer = (props) => { }} > Container -
@@ -50,7 +55,7 @@ const CollapsibleSearchContainer = (props) => { ); }; -const Template = (args) => ; +const Template = (args) => ; export const Inactive = Template.bind({}); Inactive.args = {}; diff --git a/packages/webapp/src/stories/PureSearchBarWithBackdrop/PureSearchBarWithBackdrop.stories.jsx b/packages/webapp/src/stories/PureSearchBarWithBackdrop/PureSearchBarWithBackdrop.stories.jsx new file mode 100644 index 0000000000..2ab0b10281 --- /dev/null +++ b/packages/webapp/src/stories/PureSearchBarWithBackdrop/PureSearchBarWithBackdrop.stories.jsx @@ -0,0 +1,52 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +import { Suspense, useRef } from 'react'; +import PureSearchBarWithBackdrop from '../../components/PopupFilter/PureSearchWithBackdrop'; +import { componentDecorators } from '../Pages/config/Decorators'; +import { Main, Info } from '../../components/Typography'; +import { useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/styles'; + +export default { + title: 'Components/PureSearchBarWithBackdrop', + component: PureSearchBarWithBackdrop, + decorators: componentDecorators, +}; + +const SearchContainer = (props) => { + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('lg')); + const zIndexBase = theme.zIndex.drawer; + + return ( + +
+ Touch screens show backdrop. Desktop does not. + +
+
+ ); +}; + +const Template = (args) => ; + +export const Plain = Template.bind({}); +Plain.args = {}; + +export const CustomPlaceholder = Template.bind({}); +CustomPlaceholder.args = { + placeholderText: 'Search transactions', +};