diff --git a/src/components/EventCalendar/EventHeader.spec.tsx b/src/components/EventCalendar/EventHeader.spec.tsx index be1ba4bd78..84b8ceafec 100644 --- a/src/components/EventCalendar/EventHeader.spec.tsx +++ b/src/components/EventCalendar/EventHeader.spec.tsx @@ -69,7 +69,7 @@ describe('EventHeader Component', () => { fireEvent.click(getByTestId('eventType')); await act(async () => { - fireEvent.click(getByTestId('events')); + fireEvent.click(getByTestId('Events')); }); expect(handleChangeView).toHaveBeenCalledTimes(1); diff --git a/src/components/EventCalendar/EventHeader.tsx b/src/components/EventCalendar/EventHeader.tsx index d338de3b82..9201e8b696 100644 --- a/src/components/EventCalendar/EventHeader.tsx +++ b/src/components/EventCalendar/EventHeader.tsx @@ -1,9 +1,10 @@ import React, { useState } from 'react'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Search } from '@mui/icons-material'; import styles from '../../style/app.module.css'; import { ViewType } from '../../screens/OrganizationEvents/OrganizationEvents'; import { useTranslation } from 'react-i18next'; +import SortingButton from 'subComponents/SortingButton'; /** * Props for the EventHeader component. @@ -63,58 +64,30 @@ function eventHeader({
-
- - - {viewType} - - - - {ViewType.MONTH} - - - {ViewType.DAY} - - - {ViewType.YEAR} - - - -
-
- - - {t('eventType')} - - - - Events - - - Workshops - - - -
+ + console.log(`Selected: ${value}`)} + dataTestIdPrefix="eventType" + className={styles.dropdown} + buttonLabel={t('eventType')} + /> -
-
-
+
+ +
+
- - - Sort - Filter: {filteringBy} - - } - onSelect={(eventKey) => setFilteringBy(eventKey as FilterPeriod)} - > - This Month - This Year - All - - - Sort - Sort - - } - onSelect={ - /*istanbul ignore next*/ - (eventKey) => setSortOrder(eventKey as 'ascending' | 'descending') + setFilteringBy(value as FilterPeriod)} + dataTestIdPrefix="filter-dropdown" + className={`${styles.dropdown} mx-4`} + buttonLabel="Filter" + /> + + setSortOrder(value as 'ascending' | 'descending') } - > - Ascending - Descending - + dataTestIdPrefix="sort-dropdown" + buttonLabel="Sort" + />
- {/*

{totalMembers}

*/} diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx index 27eec94851..90c2a105ce 100644 --- a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx @@ -125,9 +125,9 @@ describe('Testing Organisation Action Item Categories', () => { // Filter by All fireEvent.click(filterBtn); await waitFor(() => { - expect(screen.getByTestId('statusAll')).toBeInTheDocument(); + expect(screen.getByTestId('all')).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('statusAll')); + fireEvent.click(screen.getByTestId('all')); await waitFor(() => { expect(screen.getByText('Category 1')).toBeInTheDocument(); @@ -137,9 +137,9 @@ describe('Testing Organisation Action Item Categories', () => { // Filter by Disabled fireEvent.click(filterBtn); await waitFor(() => { - expect(screen.getByTestId('statusDisabled')).toBeInTheDocument(); + expect(screen.getByTestId('disabled')).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('statusDisabled')); + fireEvent.click(screen.getByTestId('disabled')); await waitFor(() => { expect(screen.queryByText('Category 1')).toBeNull(); expect(screen.getByText('Category 2')).toBeInTheDocument(); @@ -154,9 +154,9 @@ describe('Testing Organisation Action Item Categories', () => { fireEvent.click(filterBtn); await waitFor(() => { - expect(screen.getByTestId('statusActive')).toBeInTheDocument(); + expect(screen.getByTestId('active')).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('statusActive')); + fireEvent.click(screen.getByTestId('active')); await waitFor(() => { expect(screen.getByText('Category 1')).toBeInTheDocument(); expect(screen.queryByText('Category 2')).toBeNull(); diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx index 3f1001c88b..a1f31e6da1 100644 --- a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import styles from '../../../style/app.module.css'; import { useTranslation } from 'react-i18next'; @@ -8,13 +8,7 @@ import { useQuery } from '@apollo/client'; import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; import type { InterfaceActionItemCategoryInfo } from 'utils/interfaces'; import Loader from 'components/Loader/Loader'; -import { - Circle, - Search, - Sort, - WarningAmberRounded, - FilterAltOutlined, -} from '@mui/icons-material'; +import { Circle, Search, WarningAmberRounded } from '@mui/icons-material'; import { DataGrid, type GridCellParams, @@ -23,6 +17,7 @@ import { import dayjs from 'dayjs'; import { Chip, Stack } from '@mui/material'; import CategoryModal from './CategoryModal'; +import SortingButton from 'subComponents/SortingButton'; enum ModalState { SAME = 'same', @@ -311,63 +306,47 @@ const OrgActionItemCategories: FC = ({
- - - - {tCommon('sort')} - - - setSortBy('createdAt_DESC')} - data-testid="createdAt_DESC" - > - {tCommon('createdLatest')} - - setSortBy('createdAt_ASC')} - data-testid="createdAt_ASC" - > - {tCommon('createdEarliest')} - - - - - - - {t('status')} - - - setStatus(null)} - data-testid="statusAll" - > - {tCommon('all')} - - setStatus(CategoryStatus.Active)} - data-testid="statusActive" - > - {tCommon('active')} - - setStatus(CategoryStatus.Disabled)} - data-testid="statusDisabled" - > - {tCommon('disabled')} - - - + + setSortBy(value as 'createdAt_DESC' | 'createdAt_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + className={styles.dropdown} + /> + + setStatus(value === 'all' ? null : (value as CategoryStatus)) + } + dataTestIdPrefix="filter" + buttonLabel={t('status')} + className={styles.dropdown} + />
+
- {/* Dropdown for filtering members */} - - {/* Dropdown for sorting by name */} - + : t('searchByLastName') + } + onSortChange={(value) => + setSearchByFirstName(value === 'searchByFirstName') + } + dataTestIdPrefix="nameFilter" + className={`${styles.createButton} mt-2`} + />
diff --git a/src/screens/EventVolunteers/Requests/Requests.tsx b/src/screens/EventVolunteers/Requests/Requests.tsx index b19be3d2a0..d8efd92a90 100644 --- a/src/screens/EventVolunteers/Requests/Requests.tsx +++ b/src/screens/EventVolunteers/Requests/Requests.tsx @@ -1,9 +1,9 @@ import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Navigate, useParams } from 'react-router-dom'; import { FaXmark } from 'react-icons/fa6'; -import { Search, Sort, WarningAmberRounded } from '@mui/icons-material'; +import { Search, WarningAmberRounded } from '@mui/icons-material'; import { useMutation, useQuery } from '@apollo/client'; import Loader from 'components/Loader/Loader'; @@ -20,6 +20,7 @@ import dayjs from 'dayjs'; import { UPDATE_VOLUNTEER_MEMBERSHIP } from 'GraphQl/Mutations/EventVolunteerMutation'; import { toast } from 'react-toastify'; import { debounce } from '@mui/material'; +import SortingButton from 'subComponents/SortingButton'; const dataGridStyle = { '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { @@ -279,30 +280,18 @@ function requests(): JSX.Element {
- - - - {tCommon('sort')} - - - setSortBy('createdAt_DESC')} - data-testid="createdAt_DESC" - > - {t('latest')} - - setSortBy('createdAt_ASC')} - data-testid="createdAt_ASC" - > - {t('earliest')} - - - + + setSortBy(value as 'createdAt_DESC' | 'createdAt_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + />
diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx index 3c70b1db49..b8577acaac 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx @@ -1,9 +1,9 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Navigate, useParams } from 'react-router-dom'; -import { Search, Sort, WarningAmberRounded } from '@mui/icons-material'; +import { Search, WarningAmberRounded } from '@mui/icons-material'; import { useQuery } from '@apollo/client'; @@ -21,6 +21,7 @@ import { EVENT_VOLUNTEER_GROUP_LIST } from 'GraphQl/Queries/EventVolunteerQuerie import VolunteerGroupModal from './VolunteerGroupModal'; import VolunteerGroupDeleteModal from './VolunteerGroupDeleteModal'; import VolunteerGroupViewModal from './VolunteerGroupViewModal'; +import SortingButton from 'subComponents/SortingButton'; enum ModalState { SAME = 'same', @@ -321,56 +322,29 @@ function volunteerGroups(): JSX.Element {
- - - - {tCommon('searchBy', { item: '' })} - - - setSearchBy('leader')} - data-testid="leader" - > - {t('leader')} - - setSearchBy('group')} - data-testid="group" - > - {t('group')} - - - - - - - {tCommon('sort')} - - - setSortBy('volunteers_DESC')} - data-testid="volunteers_DESC" - > - {t('mostVolunteers')} - - setSortBy('volunteers_ASC')} - data-testid="volunteers_ASC" - > - {t('leastVolunteers')} - - - + setSearchBy(value as 'leader' | 'group')} + dataTestIdPrefix="searchByToggle" + buttonLabel={tCommon('searchBy', { item: '' })} + /> + + setSortBy(value as 'volunteers_DESC' | 'volunteers_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + />
- - - - {tCommon('sort')} - - - setSortBy('hoursVolunteered_DESC')} - data-testid="hoursVolunteered_DESC" - > - {t('mostHoursVolunteered')} - - setSortBy('hoursVolunteered_ASC')} - data-testid="hoursVolunteered_ASC" - > - {t('leastHoursVolunteered')} - - - - - - - {t('status')} - - - setStatus(VolunteerStatus.All)} - data-testid="statusAll" - > - {tCommon('all')} - - setStatus(VolunteerStatus.Pending)} - data-testid="statusPending" - > - {tCommon('pending')} - - setStatus(VolunteerStatus.Accepted)} - data-testid="statusAccepted" - > - {t('accepted')} - - - + + setSortBy( + value as 'hoursVolunteered_DESC' | 'hoursVolunteered_ASC', + ) + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + /> + + setStatus(value as VolunteerStatus)} + dataTestIdPrefix="filter" + buttonLabel={t('status')} + />
- - - - {tCommon('sort')} - - - setSortBy('amount_ASC')} - data-testid="amount_ASC" - > - {t('lowestAmount')} - - setSortBy('amount_DESC')} - data-testid="amount_DESC" - > - {t('highestAmount')} - - setSortBy('endDate_DESC')} - data-testid="endDate_DESC" - > - {t('latestEndDate')} - - setSortBy('endDate_ASC')} - data-testid="endDate_ASC" - > - {t('earliestEndDate')} - - - + + setSortBy( + value as + | 'amount_ASC' + | 'amount_DESC' + | 'endDate_ASC' + | 'endDate_DESC', + ) + } + dataTestIdPrefix="filter" + buttonLabel={tCommon('sort')} + />
- - - - {tCommon('sort')} - - - setSortBy('hours_DESC')} - data-testid="hours_DESC" - > - {t('mostHours')} - - setSortBy('hours_ASC')} - data-testid="hours_ASC" - > - {t('leastHours')} - - - - - - - {t('timeFrame')} - - - setTimeFrame(TimeFrame.All)} - data-testid="timeFrameAll" - > - {t('allTime')} - - setTimeFrame(TimeFrame.Weekly)} - data-testid="timeFrameWeekly" - > - {t('weekly')} - - setTimeFrame(TimeFrame.Monthly)} - data-testid="timeFrameMonthly" - > - {t('monthly')} - - setTimeFrame(TimeFrame.Yearly)} - data-testid="timeFrameYearly" - > - {t('yearly')} - - - + + setSortBy(value as 'hours_DESC' | 'hours_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + /> + setTimeFrame(value as TimeFrame)} + dataTestIdPrefix="timeFrame" + buttonLabel={t('timeFrame')} + type="filter" + />
diff --git a/src/screens/ManageTag/ManageTag.spec.tsx b/src/screens/ManageTag/ManageTag.spec.tsx index 5d86ed3c17..03c7eea393 100644 --- a/src/screens/ManageTag/ManageTag.spec.tsx +++ b/src/screens/ManageTag/ManageTag.spec.tsx @@ -50,7 +50,6 @@ vi.mock('react-toastify', () => ({ }, })); -/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ vi.mock('../../components/AddPeopleToTag/AddPeopleToTag', async () => { return await import('./ManageTagMockComponents/MockAddPeopleToTag'); }); @@ -58,7 +57,6 @@ vi.mock('../../components/AddPeopleToTag/AddPeopleToTag', async () => { vi.mock('../../components/TagActions/TagActions', async () => { return await import('./ManageTagMockComponents/MockTagActions'); }); -/* eslint-enable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ const renderManageTag = (link: ApolloLink): RenderResult => { return render( @@ -372,9 +370,9 @@ describe('Manage Tag Page', () => { userEvent.click(screen.getByTestId('sortPeople')); await waitFor(() => { - expect(screen.getByTestId('oldest')).toBeInTheDocument(); + expect(screen.getByTestId('ASCENDING')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('oldest')); + userEvent.click(screen.getByTestId('ASCENDING')); // returns the tags in reverse order await waitFor(() => { @@ -389,9 +387,9 @@ describe('Manage Tag Page', () => { userEvent.click(screen.getByTestId('sortPeople')); await waitFor(() => { - expect(screen.getByTestId('latest')).toBeInTheDocument(); + expect(screen.getByTestId('DESCENDING')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('latest')); + userEvent.click(screen.getByTestId('DESCENDING')); // reverse the order again await waitFor(() => { diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 38466f6f11..0832b2ab3e 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -2,13 +2,11 @@ import type { FormEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { useMutation, useQuery } from '@apollo/client'; import { Search, WarningAmberRounded } from '@mui/icons-material'; -import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; import IconComponent from 'components/IconComponent/IconComponent'; import { useNavigate, useParams, Link } from 'react-router-dom'; import { Col, Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; -import Dropdown from 'react-bootstrap/Dropdown'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; @@ -39,6 +37,7 @@ import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScroll import EditUserTagModal from './EditUserTagModal'; import RemoveUserTagModal from './RemoveUserTagModal'; import UnassignUserTagModal from './UnassignUserTagModal'; +import SortingButton from 'subComponents/SortingButton'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/manageTag/:tagId'. @@ -378,36 +377,19 @@ function ManageTag(): JSX.Element {
- + sortingOptions={[ + { label: tCommon('Latest'), value: 'DESCENDING' }, + { label: tCommon('Oldest'), value: 'ASCENDING' }, + ]} + selectedOption={assignedMemberSortOrder} + onSortChange={(value) => + setAssignedMemberSortOrder(value as SortedByType) + } + dataTestIdPrefix="sortPeople" + buttonLabel={tCommon('sort')} + />
-
- -
+ {superAdmin && (
- - + sortingOptions={[ + { label: t('Latest'), value: 'latest' }, + { label: t('Oldest'), value: 'oldest' }, + ]} + selectedOption={sortingOption} + onSortChange={handleSorting} + dataTestIdPrefix="sortpost" + dropdownTestId="sort" + className={`${styles.dropdown} `} + buttonLabel={t('sortPost')} + />
))}
- - {/* Dropdown menu for selecting settings category */} - - - {t(tab)} - - - {/* Render dropdown items for each settings category */} - {settingtabs.map((setting, index) => ( - setTab(setting)} - className={tab === setting ? 'text-secondary' : ''} - > - {t(setting)} - - ))} - - diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.spec.tsx b/src/screens/OrganizationActionItems/OrganizationActionItems.spec.tsx index 7ae0fc58eb..89bcc5d824 100644 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.spec.tsx +++ b/src/screens/OrganizationActionItems/OrganizationActionItems.spec.tsx @@ -252,11 +252,11 @@ describe('Testing Organization Action Items Screen', () => { }); await waitFor(() => { - expect(screen.getByTestId('statusAll')).toBeInTheDocument(); + expect(screen.getByTestId('all')).toBeInTheDocument(); }); await act(() => { - fireEvent.click(screen.getByTestId('statusAll')); + fireEvent.click(screen.getByTestId('all')); }); await waitFor(() => { @@ -269,11 +269,11 @@ describe('Testing Organization Action Items Screen', () => { }); await waitFor(() => { - expect(screen.getByTestId('statusPending')).toBeInTheDocument(); + expect(screen.getByTestId('pending')).toBeInTheDocument(); }); await act(() => { - fireEvent.click(screen.getByTestId('statusPending')); + fireEvent.click(screen.getByTestId('pending')); }); await waitFor(() => { @@ -314,11 +314,11 @@ describe('Testing Organization Action Items Screen', () => { }); await waitFor(() => { - expect(screen.getByTestId('statusCompleted')).toBeInTheDocument(); + expect(screen.getByTestId('completed')).toBeInTheDocument(); }); await act(() => { - fireEvent.click(screen.getByTestId('statusCompleted')); + fireEvent.click(screen.getByTestId('completed')); }); await waitFor(() => { diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.tsx b/src/screens/OrganizationActionItems/OrganizationActionItems.tsx index 6061ba7e7d..e3d55648b0 100644 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.tsx +++ b/src/screens/OrganizationActionItems/OrganizationActionItems.tsx @@ -1,15 +1,9 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Navigate, useParams } from 'react-router-dom'; -import { - Circle, - FilterAltOutlined, - Search, - Sort, - WarningAmberRounded, -} from '@mui/icons-material'; +import { Circle, Search, WarningAmberRounded } from '@mui/icons-material'; import dayjs from 'dayjs'; import { useQuery } from '@apollo/client'; @@ -32,6 +26,7 @@ import ItemModal from './ItemModal'; import ItemDeleteModal from './ItemDeleteModal'; import Avatar from 'components/Avatar/Avatar'; import ItemUpdateStatusModal from './ItemUpdateStatusModal'; +import SortingButton from 'subComponents/SortingButton'; enum ItemStatus { Pending = 'pending', @@ -141,6 +136,11 @@ function organizationActionItems(): JSX.Element { [], ); + // Trigger refetch on sortBy or status change + useEffect(() => { + actionItemsRefetch(); + }, [sortBy, status, actionItemsRefetch]); + if (actionItemsLoading) { return ; } @@ -388,89 +388,57 @@ function organizationActionItems(): JSX.Element {
-
- - - - {tCommon('searchBy', { item: '' })} - - - setSearchBy('assignee')} - data-testid="assignee" - > - {t('assignee')} - - setSearchBy('category')} - data-testid="category" - > - {t('category')} - - - - - - - {tCommon('sort')} - - - setSortBy('dueDate_DESC')} - data-testid="dueDate_DESC" - > - {t('latestDueDate')} - - setSortBy('dueDate_ASC')} - data-testid="dueDate_ASC" - > - {t('earliestDueDate')} - - - - - - - {t('status')} - - - setStatus(null)} - data-testid="statusAll" - > - {tCommon('all')} - - setStatus(ItemStatus.Pending)} - data-testid="statusPending" - > - {tCommon('pending')} - - setStatus(ItemStatus.Completed)} - data-testid="statusCompleted" - > - {tCommon('completed')} - - - -
+ + setSearchBy(value as 'assignee' | 'category') + } + dataTestIdPrefix="searchByToggle" + buttonLabel={tCommon('searchBy', { item: '' })} + className={styles.dropdown} // Pass a custom class name if needed + /> + + setSortBy(value as 'dueDate_DESC' | 'dueDate_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + className={styles.dropdown} // Pass a custom class name if needed + /> + + setStatus(value === 'all' ? null : (value as ItemStatus)) + } + dataTestIdPrefix="filter" + buttonLabel={t('status')} + className={styles.dropdown} // Pass a custom class name if needed + />
- - - - {tCommon('sort')} - - - setSortBy('fundingGoal_ASC')} - data-testid="fundingGoal_ASC" - > - {t('lowestGoal')} - - setSortBy('fundingGoal_DESC')} - data-testid="fundingGoal_DESC" - > - {t('highestGoal')} - - setSortBy('endDate_DESC')} - data-testid="endDate_DESC" - > - {t('latestEndDate')} - - setSortBy('endDate_ASC')} - data-testid="endDate_ASC" - > - {t('earliestEndDate')} - - - + + setSortBy( + value as + | 'fundingGoal_ASC' + | 'fundingGoal_DESC' + | 'endDate_ASC' + | 'endDate_DESC', + ) + } + dataTestIdPrefix="filter" + buttonLabel={tCommon('sort')} + />
-
- - - - {tCommon('sort')} - - - setSortBy('createdAt_DESC')} - data-testid="createdAt_DESC" - > - {t('createdLatest')} - - setSortBy('createdAt_ASC')} - data-testid="createdAt_ASC" - > - {t('createdEarliest')} - - - -
+ + setSortBy(value as 'createdAt_DESC' | 'createdAt_ASC') + } + dataTestIdPrefix="filter" + buttonLabel={tCommon('sort')} + />
- - - - {t('sort')} - - - { - setState(2); - }} - > - - {tCommon('users')} - - - { - setState(0); - }} - > - - {tCommon('members')} - - - { - setState(1); - }} - > - - {tCommon('admins')} - - - - +
- +
diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 558eb4eaf8..0b233cfaef 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -1,13 +1,11 @@ import { useMutation, useQuery } from '@apollo/client'; import { WarningAmberRounded } from '@mui/icons-material'; -import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; import { useNavigate, useParams, Link } from 'react-router-dom'; import type { ChangeEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; -import Dropdown from 'react-bootstrap/Dropdown'; import Modal from 'react-bootstrap/Modal'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; @@ -30,7 +28,7 @@ import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; import InfiniteScroll from 'react-infinite-scroll-component'; import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; - +import SortingButton from 'subComponents/SortingButton'; /** * Component that renders the Organization Tags screen when the app navigates to '/orgtags/:orgId'. * @@ -294,6 +292,10 @@ function OrganizationTags(): JSX.Element { }, ]; + const handleSortChange = (value: string): void => { + setTagSortOrder(value === 'latest' ? 'DESCENDING' : 'ASCENDING'); + }; + return ( <> @@ -312,40 +314,24 @@ function OrganizationTags(): JSX.Element { />
- + : tCommon('Oldest') + } + onSortChange={handleSortChange} + dataTestIdPrefix="sortTags" + className={styles.dropdown} + />
-
-
- - -
+
+ +
- - - - {tagSortOrder === 'DESCENDING' - ? tCommon('Latest') - : tCommon('Oldest')} - - - setTagSortOrder('DESCENDING')} - > - {tCommon('Latest')} - - setTagSortOrder('ASCENDING')} - > - {tCommon('Oldest')} - - - + setTagSortOrder(value as SortedByType)} + dataTestIdPrefix="sortTags" + buttonLabel={tCommon('sort')} + />
- {/* Dropdown menu for sorting campaigns */} - - - - {tCommon('sort')} - - - setSortBy('fundingGoal_ASC')} - data-testid="fundingGoal_ASC" - > - {t('lowestGoal')} - - setSortBy('fundingGoal_DESC')} - data-testid="fundingGoal_DESC" - > - {t('highestGoal')} - - setSortBy('endDate_DESC')} - data-testid="endDate_DESC" - > - {t('latestEndDate')} - - setSortBy('endDate_ASC')} - data-testid="endDate_ASC" - > - {t('earliestEndDate')} - - - + + setSortBy( + value as + | 'fundingGoal_ASC' + | 'fundingGoal_DESC' + | 'endDate_ASC' + | 'endDate_DESC', + ) + } + dataTestIdPrefix="filter" + buttonLabel={tCommon('sort')} + />
{/* Button to navigate to the user's pledges */} diff --git a/src/screens/UserPortal/Pledges/Pledges.tsx b/src/screens/UserPortal/Pledges/Pledges.tsx index 33e8bf63c2..2ab8214265 100644 --- a/src/screens/UserPortal/Pledges/Pledges.tsx +++ b/src/screens/UserPortal/Pledges/Pledges.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Dropdown, Form, Button, ProgressBar } from 'react-bootstrap'; +import { Form, Button, ProgressBar } from 'react-bootstrap'; import styles from './Pledges.module.css'; import { useTranslation } from 'react-i18next'; -import { Search, Sort, WarningAmberRounded } from '@mui/icons-material'; +import { Search, WarningAmberRounded } from '@mui/icons-material'; import useLocalStorage from 'utils/useLocalstorage'; import type { InterfacePledgeInfo, InterfaceUserInfo } from 'utils/interfaces'; import { Unstable_Popup as BasePopup } from '@mui/base/Unstable_Popup'; @@ -21,6 +21,7 @@ import { currencySymbols } from 'utils/currency'; import PledgeDeleteModal from 'screens/FundCampaignPledge/PledgeDeleteModal'; import { Navigate, useParams } from 'react-router-dom'; import PledgeModal from '../Campaigns/PledgeModal'; +import SortingButton from 'subComponents/SortingButton'; const dataGridStyle = { '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { @@ -393,75 +394,40 @@ const Pledges = (): JSX.Element => {
-
- +
+ + setSearchBy(value as 'pledgers' | 'campaigns') + } + dataTestIdPrefix="searchByDrpdwn" + buttonLabel={t('searchBy')} + /> - - - - {tCommon('sort')} - - - setSortBy('amount_ASC')} - data-testid="amount_ASC" - > - {t('lowestAmount')} - - setSortBy('amount_DESC')} - data-testid="amount_DESC" - > - {t('highestAmount')} - - setSortBy('endDate_DESC')} - data-testid="endDate_DESC" - > - {t('latestEndDate')} - - setSortBy('endDate_ASC')} - data-testid="endDate_ASC" - > - {t('earliestEndDate')} - - - + + setSortBy( + value as + | 'amount_ASC' + | 'amount_DESC' + | 'endDate_ASC' + | 'endDate_DESC', + ) + } + dataTestIdPrefix="filter" + buttonLabel={tCommon('sort')} + />
diff --git a/src/screens/UserPortal/Volunteer/Actions/Actions.tsx b/src/screens/UserPortal/Volunteer/Actions/Actions.tsx index 9bc23969c2..9fc2c44884 100644 --- a/src/screens/UserPortal/Volunteer/Actions/Actions.tsx +++ b/src/screens/UserPortal/Volunteer/Actions/Actions.tsx @@ -1,9 +1,9 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Navigate, useParams } from 'react-router-dom'; -import { Circle, Search, Sort, WarningAmberRounded } from '@mui/icons-material'; +import { Circle, Search, WarningAmberRounded } from '@mui/icons-material'; import dayjs from 'dayjs'; import { useQuery } from '@apollo/client'; @@ -22,6 +22,7 @@ import Avatar from 'components/Avatar/Avatar'; import ItemUpdateStatusModal from 'screens/OrganizationActionItems/ItemUpdateStatusModal'; import { ACTION_ITEMS_BY_USER } from 'GraphQl/Queries/ActionItemQueries'; import useLocalStorage from 'utils/useLocalstorage'; +import SortingButton from 'subComponents/SortingButton'; enum ModalState { VIEW = 'view', @@ -373,54 +374,29 @@ function actions(): JSX.Element {
- - - - {tCommon('searchBy', { item: '' })} - - - setSearchBy('assignee')} - data-testid="assignee" - > - {t('assignee')} - - setSearchBy('category')} - data-testid="category" - > - {t('category')} - - - - - - - {tCommon('sort')} - - - setSortBy('dueDate_DESC')} - data-testid="dueDate_DESC" - > - {t('latestDueDate')} - - setSortBy('dueDate_ASC')} - data-testid="dueDate_ASC" - > - {t('earliestDueDate')} - - - + + setSearchBy(value as 'assignee' | 'category') + } + dataTestIdPrefix="searchByToggle" + buttonLabel={tCommon('searchBy', { item: '' })} + /> + + setSortBy(value as 'dueDate_DESC' | 'dueDate_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + />
diff --git a/src/screens/UserPortal/Volunteer/Groups/Groups.tsx b/src/screens/UserPortal/Volunteer/Groups/Groups.tsx index 160dc0b23a..4cd2470010 100644 --- a/src/screens/UserPortal/Volunteer/Groups/Groups.tsx +++ b/src/screens/UserPortal/Volunteer/Groups/Groups.tsx @@ -1,11 +1,10 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Dropdown, Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import { Navigate, useParams } from 'react-router-dom'; - -import { Search, Sort, WarningAmberRounded } from '@mui/icons-material'; - +import { Search, WarningAmberRounded } from '@mui/icons-material'; import { useQuery } from '@apollo/client'; +import { debounce, Stack } from '@mui/material'; import type { InterfaceVolunteerGroupInfo } from 'utils/interfaces'; import Loader from 'components/Loader/Loader'; @@ -14,13 +13,13 @@ import { type GridCellParams, type GridColDef, } from '@mui/x-data-grid'; -import { debounce, Stack } from '@mui/material'; import Avatar from 'components/Avatar/Avatar'; import styles from '../../../../style/app.module.css'; import { EVENT_VOLUNTEER_GROUP_LIST } from 'GraphQl/Queries/EventVolunteerQueries'; import VolunteerGroupViewModal from 'screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal'; import useLocalStorage from 'utils/useLocalstorage'; import GroupModal from './GroupModal'; +import SortingButton from 'subComponents/SortingButton'; enum ModalState { EDIT = 'edit', @@ -313,56 +312,27 @@ function groups(): JSX.Element {
- - - - {tCommon('searchBy', { item: '' })} - - - setSearchBy('leader')} - data-testid="leader" - > - {t('leader')} - - setSearchBy('group')} - data-testid="group" - > - {t('group')} - - - - - - - {tCommon('sort')} - - - setSortBy('volunteers_DESC')} - data-testid="volunteers_DESC" - > - {t('mostVolunteers')} - - setSortBy('volunteers_ASC')} - data-testid="volunteers_ASC" - > - {t('leastVolunteers')} - - - + setSearchBy(value as 'leader' | 'group')} + dataTestIdPrefix="searchByToggle" + buttonLabel={tCommon('searchBy', { item: '' })} + /> + + setSortBy(value as 'volunteers_DESC' | 'volunteers_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + />
diff --git a/src/screens/UserPortal/Volunteer/Invitations/Invitations.spec.tsx b/src/screens/UserPortal/Volunteer/Invitations/Invitations.spec.tsx index 867f95c1aa..2c8d0835ca 100644 --- a/src/screens/UserPortal/Volunteer/Invitations/Invitations.spec.tsx +++ b/src/screens/UserPortal/Volunteer/Invitations/Invitations.spec.tsx @@ -171,7 +171,7 @@ describe('Testing Invvitations Screen', () => { expect(filter).toBeInTheDocument(); fireEvent.click(filter); - const filterAll = await screen.findByTestId('filterAll'); + const filterAll = await screen.findByTestId('all'); expect(filterAll).toBeInTheDocument(); fireEvent.click(filterAll); @@ -189,7 +189,7 @@ describe('Testing Invvitations Screen', () => { expect(filter).toBeInTheDocument(); fireEvent.click(filter); - const filterGroup = await screen.findByTestId('filterGroup'); + const filterGroup = await screen.findByTestId('group'); expect(filterGroup).toBeInTheDocument(); fireEvent.click(filterGroup); @@ -210,7 +210,7 @@ describe('Testing Invvitations Screen', () => { expect(filter).toBeInTheDocument(); fireEvent.click(filter); - const filterIndividual = await screen.findByTestId('filterIndividual'); + const filterIndividual = await screen.findByTestId('individual'); expect(filterIndividual).toBeInTheDocument(); fireEvent.click(filterIndividual); diff --git a/src/screens/UserPortal/Volunteer/Invitations/Invitations.tsx b/src/screens/UserPortal/Volunteer/Invitations/Invitations.tsx index a79b64251d..35dbe67264 100644 --- a/src/screens/UserPortal/Volunteer/Invitations/Invitations.tsx +++ b/src/screens/UserPortal/Volunteer/Invitations/Invitations.tsx @@ -1,14 +1,9 @@ import React, { useMemo, useState } from 'react'; -import { Dropdown, Form, Button } from 'react-bootstrap'; +import { Form, Button } from 'react-bootstrap'; import styles from '../VolunteerManagement.module.css'; import { useTranslation } from 'react-i18next'; import { Navigate, useParams } from 'react-router-dom'; -import { - FilterAltOutlined, - Search, - Sort, - WarningAmberRounded, -} from '@mui/icons-material'; +import { Search, WarningAmberRounded } from '@mui/icons-material'; import { TbCalendarEvent } from 'react-icons/tb'; import { FaUserGroup } from 'react-icons/fa6'; import { debounce, Stack } from '@mui/material'; @@ -21,6 +16,7 @@ import Loader from 'components/Loader/Loader'; import { USER_VOLUNTEER_MEMBERSHIP } from 'GraphQl/Queries/EventVolunteerQueries'; import { UPDATE_VOLUNTEER_MEMBERSHIP } from 'GraphQl/Mutations/EventVolunteerMutation'; import { toast } from 'react-toastify'; +import SortingButton from 'subComponents/SortingButton'; enum ItemFilter { Group = 'group', @@ -120,7 +116,7 @@ const Invitations = (): JSX.Element => { // loads the invitations when the component mounts if (invitationLoading) return ; if (invitationError) { - // Displays an error message if there is an issue loading the invvitations + // Displays an error message if there is an issue loading the invitations return (
@@ -162,63 +158,30 @@ const Invitations = (): JSX.Element => {
- {/* Dropdown menu for sorting invitations */} - - - - {tCommon('sort')} - - - setSortBy('createdAt_DESC')} - data-testid="createdAt_DESC" - > - {t('receivedLatest')} - - setSortBy('createdAt_ASC')} - data-testid="createdAt_ASC" - > - {t('receivedEarliest')} - - - - - - - - {t('filter')} - - - setFilter(null)} - data-testid="filterAll" - > - {tCommon('all')} - - setFilter(ItemFilter.Group)} - data-testid="filterGroup" - > - {t('groupInvite')} - - setFilter(ItemFilter.Individual)} - data-testid="filterIndividual" - > - {t('individualInvite')} - - - + + setSortBy(value as 'createdAt_DESC' | 'createdAt_ASC') + } + dataTestIdPrefix="sort" + buttonLabel={tCommon('sort')} + /> + + setFilter(value === 'all' ? null : (value as ItemFilter)) + } + dataTestIdPrefix="filter" + buttonLabel={t('filter')} + type="filter" + />
diff --git a/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.tsx b/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.tsx index bd61ca97e0..eecb874210 100644 --- a/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.tsx +++ b/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { Dropdown, Form, Button } from 'react-bootstrap'; +import { Form, Button } from 'react-bootstrap'; import styles from '../VolunteerManagement.module.css'; import { useTranslation } from 'react-i18next'; import { Navigate, useParams } from 'react-router-dom'; @@ -19,7 +19,7 @@ import { Stack, debounce, } from '@mui/material'; -import { Circle, Search, Sort, WarningAmberRounded } from '@mui/icons-material'; +import { Circle, Search, WarningAmberRounded } from '@mui/icons-material'; import { GridExpandMoreIcon } from '@mui/x-data-grid'; import useLocalStorage from 'utils/useLocalstorage'; @@ -31,6 +31,7 @@ import { USER_EVENTS_VOLUNTEER } from 'GraphQl/Queries/PlugInQueries'; import { CREATE_VOLUNTEER_MEMBERSHIP } from 'GraphQl/Mutations/EventVolunteerMutation'; import { toast } from 'react-toastify'; import { FaCheck } from 'react-icons/fa'; +import SortingButton from 'subComponents/SortingButton'; /** * The `UpcomingEvents` component displays list of upcoming events for the user to volunteer. @@ -90,7 +91,7 @@ const UpcomingEvents = (): JSX.Element => { } }; - // Fetches upcomin events based on the organization ID, search term, and sorting order + // Fetches upcoming events based on the organization ID, search term, and sorting order const { data: eventsData, loading: eventsLoading, @@ -169,31 +170,18 @@ const UpcomingEvents = (): JSX.Element => {
- - - - {tCommon('searchBy', { item: '' })} - - - setSearchBy('title')} - data-testid="title" - > - {t('name')} - - setSearchBy('location')} - data-testid="location" - > - {tCommon('location')} - - - + + setSearchBy(value as 'title' | 'location') + } + dataTestIdPrefix="searchByToggle" + buttonLabel={tCommon('searchBy', { item: '' })} + />
diff --git a/src/screens/Users/Users.tsx b/src/screens/Users/Users.tsx index 936807f1ec..ef9f001f4d 100644 --- a/src/screens/Users/Users.tsx +++ b/src/screens/Users/Users.tsx @@ -1,13 +1,11 @@ import { useQuery } from '@apollo/client'; import React, { useEffect, useState } from 'react'; -import { Dropdown, Form, Table } from 'react-bootstrap'; +import { Form, Table } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import { Search } from '@mui/icons-material'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import SortIcon from '@mui/icons-material/Sort'; import { ORGANIZATION_CONNECTION_LIST, USER_LIST, @@ -19,6 +17,8 @@ import type { InterfaceQueryUserListItem } from 'utils/interfaces'; import styles from '../../style/app.module.css'; import useLocalStorage from 'utils/useLocalstorage'; import type { ApolloError } from '@apollo/client'; +import SortingButton from 'subComponents/SortingButton'; + /** * The `Users` component is responsible for displaying a list of users in a paginated and sortable format. * It supports search functionality, filtering, and sorting of users. The component integrates with GraphQL @@ -372,74 +372,29 @@ const Users = (): JSX.Element => {
- - + +
diff --git a/src/style/app.module.css b/src/style/app.module.css index b016e9aaaf..54c6324bcb 100644 --- a/src/style/app.module.css +++ b/src/style/app.module.css @@ -273,13 +273,13 @@ } .dropdown { - background-color: var(--bs-white); + background-color: var(--bs-white) !important; border: 1px solid var(--brown-color); - color: var(--brown-color); + color: var(--brown-color) !important; position: relative; display: inline-block; - margin-top: 10px; - margin-bottom: 10px; + /* margin-top: 10px; + margin-bottom: 10px; */ } .dropdown:is(:hover, :focus, :active, :focus-visible, .show) { @@ -1769,14 +1769,6 @@ input[type='radio']:checked + label:hover { box-shadow: 0 1px 1px var(--brand-primary); } -.dropdowns { - background-color: var(--bs-white); - border: 1px solid var(--light-green); - position: relative; - display: inline-block; - color: var(--light-green); -} - .chipIcon { height: 0.9rem !important; } diff --git a/src/subComponents/SortingButton.tsx b/src/subComponents/SortingButton.tsx new file mode 100644 index 0000000000..7ce7703d39 --- /dev/null +++ b/src/subComponents/SortingButton.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { Dropdown } from 'react-bootstrap'; +import SortIcon from '@mui/icons-material/Sort'; +import FilterAltOutlined from '@mui/icons-material/FilterAltOutlined'; +import PropTypes from 'prop-types'; +import styles from '../style/app.module.css'; + +interface InterfaceSortingOption { + /** The label to display for the sorting option */ + label: string; + /** The value associated with the sorting option */ + value: string; +} + +interface InterfaceSortingButtonProps { + /** The title attribute for the Dropdown */ + title?: string; + /** The list of sorting options to display in the Dropdown */ + sortingOptions: InterfaceSortingOption[]; + /** The currently selected sorting option */ + selectedOption?: string; + /** Callback function to handle sorting option change */ + onSortChange: (value: string) => void; + /** The prefix for data-testid attributes for testing */ + dataTestIdPrefix: string; + /** The data-testid attribute for the Dropdown */ + dropdownTestId?: string; + /** Custom class name for the Dropdown */ + className?: string; + /** Optional prop for custom button label */ + buttonLabel?: string; + /** Type to determine the icon to display: 'sort' or 'filter' */ + type?: 'sort' | 'filter'; +} + +/** + * SortingButton component renders a Dropdown with sorting options. + * It allows users to select a sorting option and triggers a callback on selection. + * + * @param props - The properties for the SortingButton component. + * @returns The rendered SortingButton component. + */ +const SortingButton: React.FC = ({ + title, + sortingOptions, + selectedOption, + onSortChange, + dataTestIdPrefix, + dropdownTestId, + className = styles.dropdown, + buttonLabel, + type = 'sort', +}) => { + // Determine the icon based on the type + const IconComponent = type === 'filter' ? FilterAltOutlined : SortIcon; + + return ( + + ); +}; + +SortingButton.propTypes = { + title: PropTypes.string, + sortingOptions: PropTypes.arrayOf( + PropTypes.exact({ + label: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + }).isRequired, + ).isRequired, + selectedOption: PropTypes.string, + onSortChange: PropTypes.func.isRequired, + dataTestIdPrefix: PropTypes.string.isRequired, + dropdownTestId: PropTypes.string, + buttonLabel: PropTypes.string, // Optional prop for custom button label + type: PropTypes.oneOf(['sort', 'filter']), // Type to determine the icon +}; + +export default SortingButton;