= ({
-
-
-
- {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 */}
-
-
-
- {showBlockedMembers ? t('blockedUsers') : t('allMembers')}
-
-
- setShowBlockedMembers(false)}
- >
- {t('allMembers')}
-
- setShowBlockedMembers(true)}
- >
- {t('blockedUsers')}
-
-
-
- {/* Dropdown for sorting by name */}
-
-
-
- {searchByFirstName
+
+ setShowBlockedMembers(value === 'blockedUsers')
+ }
+ dataTestIdPrefix="userFilter"
+ className={`${styles.createButton} mt-2`}
+ />
+
+
-
- setSearchByFirstName(true)}
- >
- {t('searchByFirstName')}
-
- setSearchByFirstName(false)}
- >
- {t('searchByLastName')}
-
-
-
+ : 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 {
-
-
-
- {assignedMemberSortOrder === 'DESCENDING'
- ? tCommon('Latest')
- : tCommon('Oldest')}
-
-
- setAssignedMemberSortOrder('DESCENDING')}
- >
- {tCommon('Latest')}
-
- setAssignedMemberSortOrder('ASCENDING')}
- >
- {tCommon('Oldest')}
-
-
-
+ sortingOptions={[
+ { label: tCommon('Latest'), value: 'DESCENDING' },
+ { label: tCommon('Oldest'), value: 'ASCENDING' },
+ ]}
+ selectedOption={assignedMemberSortOrder}
+ onSortChange={(value) =>
+ setAssignedMemberSortOrder(value as SortedByType)
+ }
+ dataTestIdPrefix="sortPeople"
+ buttonLabel={tCommon('sort')}
+ />
-
-
-
-
- {sortingState.selectedOption}
-
-
- handleSorting('Latest')}
- data-testid="latest"
- >
- {t('Latest')}
-
- handleSorting('Earliest')}
- data-testid="oldest"
- >
- {t('Earliest')}
-
-
-
-
+
{superAdmin && (
{
await act(async () => {
fireEvent.click(inputText);
});
- const toggleTite = screen.getByTestId('searchTitle');
+ const toggleTite = screen.getByTestId('Title');
await act(async () => {
fireEvent.click(toggleTite);
});
diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx
index e9cb4d4ca2..8ccdb47692 100644
--- a/src/screens/OrgPost/OrgPost.tsx
+++ b/src/screens/OrgPost/OrgPost.tsx
@@ -1,6 +1,5 @@
import { useMutation, useQuery, type ApolloError } from '@apollo/client';
import { Search } from '@mui/icons-material';
-import SortIcon from '@mui/icons-material/Sort';
import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations';
import { ORGANIZATION_POST_LIST } from 'GraphQl/Queries/Queries';
import Loader from 'components/Loader/Loader';
@@ -11,7 +10,6 @@ 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';
@@ -20,6 +18,7 @@ import convertToBase64 from 'utils/convertToBase64';
import { errorHandler } from 'utils/errorHandler';
import type { InterfaceQueryOrganizationPostListItem } from 'utils/interfaces';
import styles from '../../style/app.module.css';
+import SortingButton from '../../subComponents/SortingButton';
interface InterfaceOrgPost {
_id: string;
@@ -303,69 +302,31 @@ function orgPost(): JSX.Element {
-
-
-
- {t('searchBy')}
-
-
- {
- setShowTitle(false);
- e.preventDefault();
- }}
- data-testid="Text"
- >
- {t('Text')}
-
- {
- setShowTitle(true);
- e.preventDefault();
- }}
- data-testid="searchTitle"
- >
- {t('Title')}
-
-
-
- setShowTitle(value === 'Title')}
+ dataTestIdPrefix="searchBy"
+ buttonLabel={t('searchBy')}
+ className={`${styles.dropdown} `}
+ />
+
-
-
- {t('sortPost')}
-
-
- handleSorting('latest')}
- data-testid="latest"
- >
- {t('Latest')}
-
- handleSorting('oldest')}
- data-testid="oldest"
- >
- {t('Oldest')}
-
-
-
+ 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')}
+ />
{
});
});
- it('should handle dropdown item selection correctly', async () => {
- renderOrganisationSettings();
-
- await waitFor(() => {
- expect(
- screen.getByTestId('settingsDropdownContainer'),
- ).toBeInTheDocument();
- });
-
- const dropdownToggle = screen.getByTestId('settingsDropdownToggle');
- userEvent.click(dropdownToggle);
-
- // Find all dropdown items
- const dropdownItems = screen.getAllByRole('menuitem');
- expect(dropdownItems).toHaveLength(3);
-
- for (const item of dropdownItems) {
- userEvent.click(item);
-
- if (item.textContent?.includes('general')) {
- await waitFor(() => {
- expect(screen.getByTestId('generalTab')).toBeInTheDocument();
- });
- } else if (item.textContent?.includes('actionItemCategories')) {
- await waitFor(() => {
- expect(
- screen.getByTestId('actionItemCategoriesTab'),
- ).toBeInTheDocument();
- });
- } else if (item.textContent?.includes('agendaItemCategories')) {
- await waitFor(() => {
- expect(
- screen.getByTestId('agendaItemCategoriesTab'),
- ).toBeInTheDocument();
- });
- }
-
- if (item !== dropdownItems[dropdownItems.length - 1]) {
- userEvent.click(dropdownToggle);
- }
- }
-
- expect(dropdownToggle).toHaveTextContent(
- screen.getByTestId('agendaItemCategoriesSettings').textContent || '',
- );
- });
+ // it('should handle dropdown item selection correctly', async () => {
+ // renderOrganisationSettings();
+
+ // await waitFor(() => {
+ // expect(
+ // screen.getByTestId('settingsDropdownContainer'),
+ // ).toBeInTheDocument();
+ // });
+
+ // const dropdownToggle = screen.getByTestId('settingsDropdownToggle');
+ // userEvent.click(dropdownToggle);
+
+ // // Find all dropdown items
+ // const dropdownItems = screen.getAllByRole('menuitem');
+ // expect(dropdownItems).toHaveLength(3);
+
+ // for (const item of dropdownItems) {
+ // userEvent.click(item);
+
+ // if (item.textContent?.includes('general')) {
+ // await waitFor(() => {
+ // expect(screen.getByTestId('generalTab')).toBeInTheDocument();
+ // });
+ // } else if (item.textContent?.includes('actionItemCategories')) {
+ // await waitFor(() => {
+ // expect(
+ // screen.getByTestId('actionItemCategoriesTab'),
+ // ).toBeInTheDocument();
+ // });
+ // } else if (item.textContent?.includes('agendaItemCategories')) {
+ // await waitFor(() => {
+ // expect(
+ // screen.getByTestId('agendaItemCategoriesTab'),
+ // ).toBeInTheDocument();
+ // });
+ // }
+
+ // if (item !== dropdownItems[dropdownItems.length - 1]) {
+ // userEvent.click(dropdownToggle);
+ // }
+ // }
+
+ // expect(dropdownToggle).toHaveTextContent(
+ // screen.getByTestId('agendaItemCategoriesSettings').textContent || '',
+ // );
+ // });
});
diff --git a/src/screens/OrgSettings/OrgSettings.tsx b/src/screens/OrgSettings/OrgSettings.tsx
index 641bc27c7d..57758d2867 100644
--- a/src/screens/OrgSettings/OrgSettings.tsx
+++ b/src/screens/OrgSettings/OrgSettings.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { Button, Dropdown, Row, Col } from 'react-bootstrap';
+import { Button, Row, Col } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import styles from 'style/app.module.css';
import OrgActionItemCategories from 'components/OrgSettings/ActionItemCategories/OrgActionItemCategories';
@@ -62,34 +62,6 @@ function OrgSettings(): JSX.Element {
))}
-
- {/* 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')}
+ />
({
[`&.${tableCellClasses.head}`]: {
@@ -270,44 +271,27 @@ function AddMember(): JSX.Element {
});
};
+ const handleSortChange = (value: string): void => {
+ if (value === 'existingUser') {
+ openAddUserModal();
+ } else if (value === 'newUser') {
+ openCreateNewUserModal();
+ }
+ };
+
return (
<>
-
-
- {translateOrgPeople('addMembers')}
-
-
- {
- openAddUserModal();
- }}
- >
-
- {translateOrgPeople('existingUser')}
-
-
- {
- openCreateNewUserModal();
- }}
- >
-
-
-
-
+
{/* Existing User Modal */}
();
+
const toggleRemoveModal = (): void => {
setShowRemoveModal((prev) => !prev);
};
@@ -139,7 +140,6 @@ function organizationPeople(): JSX.Element {
const handleFullNameSearchChange = (e: React.FormEvent): void => {
e.preventDefault();
- /* istanbul ignore next */
const [firstName, lastName] = userName.split(' ');
const newFilterData = {
firstName_contains: firstName || '',
@@ -309,6 +309,11 @@ function organizationPeople(): JSX.Element {
},
},
];
+
+ const handleSortChange = (value: string): void => {
+ setState(value === 'users' ? 2 : value === 'members' ? 0 : 1);
+ };
+
return (
<>
@@ -329,7 +334,7 @@ function organizationPeople(): JSX.Element {
/>
@@ -337,70 +342,27 @@ function organizationPeople(): JSX.Element {
-
-
-
- {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 {
/>
-
-
-
- {tagSortOrder === 'DESCENDING'
+ sortingOptions={[
+ { label: tCommon('Latest'), value: 'latest' },
+ { label: tCommon('Oldest'), value: 'oldest' },
+ ]}
+ selectedOption={
+ tagSortOrder === 'DESCENDING'
? tCommon('Latest')
- : tCommon('Oldest')}
-
-
- setTagSortOrder('DESCENDING')}
- >
- {tCommon('Latest')}
-
- setTagSortOrder('ASCENDING')}
- >
- {tCommon('Oldest')}
-
-
-
+ : tCommon('Oldest')
+ }
+ onSortChange={handleSortChange}
+ dataTestIdPrefix="sortTags"
+ className={styles.dropdown}
+ />
{
+ setSearchBy(value as 'name' | 'desc');
+ };
+
/**
* Updates the sort order state when the user selects a sort option.
- * @param order - The order to sort venues by (highest or lowest capacity).
+ * @param value - The order to sort venues by (highest or lowest capacity).
*/
- const handleSortChange = (order: 'highest' | 'lowest'): void => {
- setSortOrder(order);
+ const handleSortChange = (value: string): void => {
+ setSortOrder(value as 'highest' | 'lowest');
};
/**
@@ -157,73 +162,31 @@ function organizationVenues(): JSX.Element {
-
-
-
-
-
- {t('searchBy')}
-
-
- {
- setSearchBy('name');
- e.preventDefault();
- }}
- data-testid="name"
- >
- {tCommon('name')}
-
- {
- setSearchBy('desc');
- e.preventDefault();
- }}
- data-testid="desc"
- >
- {tCommon('description')}
-
-
-
-
-
-
- {t('sort')}
-
-
- handleSortChange('highest')}
- data-testid="highest"
- >
- {t('highestCapacity')}
-
- handleSortChange('lowest')}
- data-testid="lowest"
- >
- {t('lowestCapacity')}
-
-
-
-
+
+
+
{
userEvent.click(screen.getByTestId('sortTags'));
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(() => {
@@ -301,9 +301,9 @@ describe('Organisation Tags Page', () => {
userEvent.click(screen.getByTestId('sortTags'));
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/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx
index 6a20e875ec..034c7dfea9 100644
--- a/src/screens/SubTags/SubTags.tsx
+++ b/src/screens/SubTags/SubTags.tsx
@@ -1,6 +1,5 @@
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';
@@ -8,7 +7,6 @@ import type { ChangeEvent } from 'react';
import React, { 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,6 +28,7 @@ import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations';
import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries';
import InfiniteScroll from 'react-infinite-scroll-component';
import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader';
+import SortingButton from 'subComponents/SortingButton';
/**
* Component that renders the SubTags screen when the app navigates to '/orgtags/:orgId/subtags/:tagId'.
@@ -301,37 +300,16 @@ function SubTags(): JSX.Element {
-
-
-
- {tagSortOrder === 'DESCENDING'
- ? tCommon('Latest')
- : tCommon('Oldest')}
-
-
- setTagSortOrder('DESCENDING')}
- >
- {tCommon('Latest')}
-
- setTagSortOrder('ASCENDING')}
- >
- {tCommon('Oldest')}
-
-
-
+
setTagSortOrder(value as SortedByType)}
+ dataTestIdPrefix="sortTags"
+ buttonLabel={tCommon('sort')}
+ />
redirectToManageTag(parentTagId as string)}
diff --git a/src/screens/UserPortal/Campaigns/Campaigns.tsx b/src/screens/UserPortal/Campaigns/Campaigns.tsx
index e4483f87fe..cf6af795d3 100644
--- a/src/screens/UserPortal/Campaigns/Campaigns.tsx
+++ b/src/screens/UserPortal/Campaigns/Campaigns.tsx
@@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react';
-import { Dropdown, Form, Button, ProgressBar } from 'react-bootstrap';
+import { Form, Button, ProgressBar } from 'react-bootstrap';
import styles from './Campaigns.module.css';
import { useTranslation } from 'react-i18next';
import { Navigate, useNavigate, useParams } from 'react-router-dom';
-import { Circle, Search, Sort, WarningAmberRounded } from '@mui/icons-material';
+import { Circle, Search, WarningAmberRounded } from '@mui/icons-material';
import {
Accordion,
AccordionDetails,
@@ -19,6 +19,7 @@ import { useQuery } from '@apollo/client';
import type { InterfaceUserCampaign } from 'utils/interfaces';
import { currencySymbols } from 'utils/currency';
import Loader from 'components/Loader/Loader';
+import SortingButton from 'subComponents/SortingButton';
/**
* The `Campaigns` component displays a list of fundraising campaigns for a specific organization.
@@ -150,44 +151,26 @@ const Campaigns = (): JSX.Element => {
- {/* 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 => {
-
-
-
-
- {t('searchBy')}
-
-
- setSearchBy('pledgers')}
- data-testid="pledgers"
- >
- {t('pledgers')}
-
- setSearchBy('campaigns')}
- data-testid="campaigns"
- >
- {t('campaigns')}
-
-
-
+
+
+ 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 => {
-
-
-
- {sortingOption === 'newest' ? t('Newest') : t('Oldest')}
-
-
- {
- handleSorting('newest');
- }}
- data-testid="newest"
- >
- {t('Newest')}
-
- {
- handleSorting('oldest');
- }}
- data-testid="oldest"
- >
- {t('Oldest')}
-
-
-
-
-
-
- {tCommon('filter')}
-
-
- handleFiltering('admin')}
- >
- {tCommon('admin')}
-
- handleFiltering('superAdmin')}
- >
- {tCommon('superAdmin')}
-
-
- handleFiltering('user')}
- >
- {tCommon('user')}
-
- handleFiltering('cancel')}
- >
- {tCommon('cancel')}
-
-
-
+
+
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 (
+
+
+ {/* Use the appropriate icon */}
+ {buttonLabel || selectedOption}
+ {/* Use buttonLabel if provided, otherwise use selectedOption */}
+
+
+ {sortingOptions.map((option) => (
+ onSortChange(option.value)}
+ data-testid={`${option.value}`}
+ className={styles.dropdownItem}
+ >
+ {option.label}
+
+ ))}
+
+
+ );
+};
+
+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;