From c3eb66913ec76ab110515d87f532501b43c8006b Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 17 Aug 2022 00:53:08 +0200 Subject: [PATCH] feat: Add search by users, members and labels --- .../BoardMembershipsStep.jsx | 58 +++++++++++++----- .../BoardMembershipsStep.module.scss | 18 +++++- .../BoardMembershipsStep/Item.module.scss | 1 + .../src/components/LabelsStep/LabelsStep.jsx | 60 ++++++++++++++----- .../LabelsStep/LabelsStep.module.scss | 20 +++++++ .../Memberships/AddPopup/AddPopup.jsx | 56 +++++++++++++---- .../Memberships/AddPopup/AddPopup.module.scss | 22 +++++-- .../Memberships/AddPopup/UserItem.module.scss | 1 + client/src/locales/en/core.js | 3 + client/src/locales/ru/core.js | 3 + 10 files changed, 195 insertions(+), 47 deletions(-) diff --git a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.jsx b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.jsx index 86fa6948..af181d73 100755 --- a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.jsx +++ b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.jsx @@ -1,9 +1,10 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { Menu } from 'semantic-ui-react'; -import { Popup } from '../../lib/custom-ui'; +import { Input, Popup } from '../../lib/custom-ui'; +import { useField } from '../../hooks'; import Item from './Item'; import styles from './BoardMembershipsStep.module.scss'; @@ -11,6 +12,21 @@ import styles from './BoardMembershipsStep.module.scss'; const BoardMembershipsStep = React.memo( ({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => { const [t] = useTranslation(); + const [searchValue, handleSearchFieldChange] = useField(''); + const search = useMemo(() => searchValue.trim().toLowerCase(), [searchValue]); + + const filteredItems = useMemo( + () => + items.filter( + ({ user }) => + user.email.includes(search) || + user.name.toLowerCase().includes(search) || + (user.username && user.username.includes(search)), + ), + [items, search], + ); + + const searchField = useRef(null); const handleUserSelect = useCallback( (id) => { @@ -26,22 +42,36 @@ const BoardMembershipsStep = React.memo( [onUserDeselect], ); + useEffect(() => { + searchField.current.focus(); + }, []); + return ( <> {t(title)} - - {items.map((item) => ( - handleUserSelect(item.user.id)} - onUserDeselect={() => handleUserDeselect(item.user.id)} - /> - ))} - + + {filteredItems.length > 0 && ( + + {filteredItems.map((item) => ( + handleUserSelect(item.user.id)} + onUserDeselect={() => handleUserDeselect(item.user.id)} + /> + ))} + + )} ); diff --git a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss index 3b0b7dd1..5617c6f8 100644 --- a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss +++ b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss @@ -1,6 +1,22 @@ :global(#app) { .menu { - margin: -7px auto -5px; + margin: 8px auto 0; + max-height: 320px; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: thin; width: 100%; + + &::-webkit-scrollbar { + width: 5px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + border-radius: 3px; + } } } diff --git a/client/src/components/BoardMembershipsStep/Item.module.scss b/client/src/components/BoardMembershipsStep/Item.module.scss index d59f4110..adb6e345 100644 --- a/client/src/components/BoardMembershipsStep/Item.module.scss +++ b/client/src/components/BoardMembershipsStep/Item.module.scss @@ -2,6 +2,7 @@ .menuItem { display: block; margin: 0; + overflow: hidden; padding: 4px; } diff --git a/client/src/components/LabelsStep/LabelsStep.jsx b/client/src/components/LabelsStep/LabelsStep.jsx index 693c7742..ad39e3c9 100755 --- a/client/src/components/LabelsStep/LabelsStep.jsx +++ b/client/src/components/LabelsStep/LabelsStep.jsx @@ -1,11 +1,11 @@ import pick from 'lodash/pick'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { Button } from 'semantic-ui-react'; -import { Popup } from '../../lib/custom-ui'; +import { Input, Popup } from '../../lib/custom-ui'; -import { useSteps } from '../../hooks'; +import { useField, useSteps } from '../../hooks'; import AddStep from './AddStep'; import EditStep from './EditStep'; import Item from './Item'; @@ -21,6 +21,20 @@ const LabelsStep = React.memo( ({ items, currentIds, title, onSelect, onDeselect, onCreate, onUpdate, onDelete, onBack }) => { const [t] = useTranslation(); const [step, openStep, handleBack] = useSteps(); + const [searchValue, handleSearchFieldChange] = useField(''); + const search = useMemo(() => searchValue.trim().toLowerCase(), [searchValue]); + + const filteredItems = useMemo( + () => + items.filter( + (label) => + (label.name && label.name.toLowerCase().includes(search)) || + label.color.includes(search), + ), + [items, search], + ); + + const searchField = useRef(null); const handleAddClick = useCallback(() => { openStep(StepTypes.ADD); @@ -63,6 +77,10 @@ const LabelsStep = React.memo( [onDelete], ); + useEffect(() => { + searchField.current.focus(); + }, []); + if (step) { switch (step.type) { case StepTypes.ADD: @@ -93,18 +111,30 @@ const LabelsStep = React.memo( <> {t(title)} - {items.map((item) => ( - handleSelect(item.id)} - onDeselect={() => handleDeselect(item.id)} - onEdit={() => handleEdit(item.id)} - /> - ))} + + {filteredItems.length > 0 && ( +
+ {filteredItems.map((item) => ( + handleSelect(item.id)} + onDeselect={() => handleDeselect(item.id)} + onEdit={() => handleEdit(item.id)} + /> + ))} +
+ )}