diff --git a/pages/accountLists/[accountListId]/reports/designationAccounts.page.tsx b/pages/accountLists/[accountListId]/reports/designationAccounts.page.tsx index 008bffea4..abee35aaa 100644 --- a/pages/accountLists/[accountListId]/reports/designationAccounts.page.tsx +++ b/pages/accountLists/[accountListId]/reports/designationAccounts.page.tsx @@ -3,13 +3,16 @@ import Head from 'next/head'; import { useTranslation } from 'react-i18next'; import Box from '@mui/material/Box'; import { styled } from '@mui/material/styles'; -import { DesignationAccountsReport } from 'src/components/Reports/DesignationAccountsReport/DesignationAccountsReport'; -import Loading from 'src/components/Loading'; -import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; import { suggestArticles } from 'src/lib/helpScout'; +import Loading from 'src/components/Loading'; +import { DesignationAccountsReport } from 'src/components/Reports/DesignationAccountsReport/DesignationAccountsReport'; +import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; const DesignationAccountsReportPageWrapper = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.common.white, @@ -42,12 +45,13 @@ const DesignationAccountsReportPage: React.FC = () => { } leftOpen={isNavListOpen} diff --git a/pages/accountLists/[accountListId]/reports/donations/[[...contactId]].page.tsx b/pages/accountLists/[accountListId]/reports/donations/[[...contactId]].page.tsx index 71e863f2b..43731f1fb 100644 --- a/pages/accountLists/[accountListId]/reports/donations/[[...contactId]].page.tsx +++ b/pages/accountLists/[accountListId]/reports/donations/[[...contactId]].page.tsx @@ -4,16 +4,19 @@ import { useRouter } from 'next/router'; import { useTranslation } from 'react-i18next'; import Box from '@mui/material/Box'; import { styled } from '@mui/material/styles'; -import { DonationsReport } from 'src/components/Reports/DonationsReport/DonationsReport'; -import Loading from 'src/components/Loading'; -import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; import { getQueryParam } from 'src/utils/queryParam'; -import { ContactsPage } from '../../contacts/ContactsPage'; -import { ContactsRightPanel } from 'src/components/Contacts/ContactsRightPanel/ContactsRightPanel'; import { suggestArticles } from 'src/lib/helpScout'; +import { DonationsReport } from 'src/components/Reports/DonationsReport/DonationsReport'; +import Loading from 'src/components/Loading'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; +import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; +import { ContactsRightPanel } from 'src/components/Contacts/ContactsRightPanel/ContactsRightPanel'; +import { ContactsPage } from '../../contacts/ContactsPage'; const DonationsReportPageWrapper = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.common.white, @@ -55,12 +58,13 @@ const DonationsReportPage: React.FC = () => { } leftOpen={isNavListOpen} diff --git a/pages/accountLists/[accountListId]/reports/expectedMonthlyTotal.page.tsx b/pages/accountLists/[accountListId]/reports/expectedMonthlyTotal.page.tsx index f55bfc603..d7b561319 100644 --- a/pages/accountLists/[accountListId]/reports/expectedMonthlyTotal.page.tsx +++ b/pages/accountLists/[accountListId]/reports/expectedMonthlyTotal.page.tsx @@ -3,14 +3,17 @@ import Head from 'next/head'; import { useTranslation } from 'react-i18next'; import { Box } from '@mui/material'; import { styled } from '@mui/material/styles'; -import { ExpectedMonthlyTotalReportHeader } from '../../../../src/components/Reports/ExpectedMonthlyTotalReport/Header/ExpectedMonthlyTotalReportHeader'; -import Loading from '../../../../src/components/Loading'; -import { useAccountListId } from '../../../../src/hooks/useAccountListId'; +import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { ExpectedMonthlyTotalReport } from '../../../../src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport'; import { suggestArticles } from 'src/lib/helpScout'; +import Loading from 'src/components/Loading'; +import { ExpectedMonthlyTotalReportHeader } from 'src/components/Reports/ExpectedMonthlyTotalReport/Header/ExpectedMonthlyTotalReportHeader'; +import { ExpectedMonthlyTotalReport } from 'src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport'; import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; const ExpectedMonthlyTotalReportPageWrapper = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.common.white, @@ -43,12 +46,13 @@ const ExpectedMonthlyTotalReportPage = (): ReactElement => { } leftOpen={isNavListOpen} diff --git a/pages/accountLists/[accountListId]/reports/partnerCurrency/[[...contactId]].page.tsx b/pages/accountLists/[accountListId]/reports/partnerCurrency/[[...contactId]].page.tsx index 63c6206e9..3e3bf26e7 100644 --- a/pages/accountLists/[accountListId]/reports/partnerCurrency/[[...contactId]].page.tsx +++ b/pages/accountLists/[accountListId]/reports/partnerCurrency/[[...contactId]].page.tsx @@ -6,13 +6,16 @@ import Box from '@mui/material/Box'; import { styled } from '@mui/material/styles'; import { FourteenMonthReportCurrencyType } from '../../../../../graphql/types.generated'; import { FourteenMonthReport } from 'src/components/Reports/FourteenMonthReports/FourteenMonthReport'; +import { suggestArticles } from 'src/lib/helpScout'; +import { getQueryParam } from 'src/utils/queryParam'; import Loading from 'src/components/Loading'; -import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; -import { suggestArticles } from 'src/lib/helpScout'; -import { getQueryParam } from 'src/utils/queryParam'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; +import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { ContactsRightPanel } from 'src/components/Contacts/ContactsRightPanel/ContactsRightPanel'; import { ContactsPage } from '../../contacts/ContactsPage'; @@ -55,12 +58,13 @@ const PartnerCurrencyReportPage: React.FC = () => { } leftOpen={isNavListOpen} diff --git a/pages/accountLists/[accountListId]/reports/responsibilityCenters.page.tsx b/pages/accountLists/[accountListId]/reports/responsibilityCenters.page.tsx index cdf15802d..3565702ed 100644 --- a/pages/accountLists/[accountListId]/reports/responsibilityCenters.page.tsx +++ b/pages/accountLists/[accountListId]/reports/responsibilityCenters.page.tsx @@ -8,8 +8,11 @@ import Loading from 'src/components/Loading'; import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; import { suggestArticles } from 'src/lib/helpScout'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; const ResponsibilityCentersReportPageWrapper = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.common.white, @@ -42,12 +45,13 @@ const ResponsibilityCentersReportPage: React.FC = () => { } leftOpen={isNavListOpen} diff --git a/pages/accountLists/[accountListId]/reports/salaryCurrency/[[...contactId]].page.tsx b/pages/accountLists/[accountListId]/reports/salaryCurrency/[[...contactId]].page.tsx index 321ff5d99..0e4c5a907 100644 --- a/pages/accountLists/[accountListId]/reports/salaryCurrency/[[...contactId]].page.tsx +++ b/pages/accountLists/[accountListId]/reports/salaryCurrency/[[...contactId]].page.tsx @@ -10,7 +10,10 @@ import Loading from 'src/components/Loading'; import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout'; import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; -import { NavReportsList } from 'src/components/Reports/NavReportsList/NavReportsList'; +import { + MultiPageMenu, + NavTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu'; import { suggestArticles } from 'src/lib/helpScout'; import { getQueryParam } from 'src/utils/queryParam'; import { ContactsRightPanel } from 'src/components/Contacts/ContactsRightPanel/ContactsRightPanel'; @@ -55,12 +58,13 @@ const SalaryCurrencyReportPage: React.FC = () => { } leftOpen={isNavListOpen} diff --git a/src/components/Layouts/Primary/NavBar/NavBar.tsx b/src/components/Layouts/Primary/NavBar/NavBar.tsx index 547d740e1..1fab21adf 100644 --- a/src/components/Layouts/Primary/NavBar/NavBar.tsx +++ b/src/components/Layouts/Primary/NavBar/NavBar.tsx @@ -5,10 +5,8 @@ import { makeStyles } from 'tss-react/mui'; import { useRouter } from 'next/router'; import NextLink, { LinkProps } from 'next/link'; import { useTranslation } from 'react-i18next'; -import { - filteredReportNavItems, - toolsRedirectLinks, -} from '../TopBar/Items/NavMenu/NavMenu'; +import { toolsRedirectLinks } from '../TopBar/Items/NavMenu/NavMenu'; +import { reportNavItems } from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems'; import { NavItem } from './NavItem/NavItem'; import { NavTools } from './NavTools/NavTools'; import { ToolsList } from 'src/components/Tool/Home/ToolList'; @@ -133,7 +131,7 @@ export const NavBar: FC = ({ onMobileClose, openMobile }) => { }, { title: t('Reports'), - items: filteredReportNavItems.map((item) => ({ + items: reportNavItems.map((item) => ({ ...item, title: item.title, href: `/accountLists/${accountListId}/reports/${item.id}`, diff --git a/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.tsx b/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.tsx index 327536b1d..1ae314d25 100644 --- a/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/NavMenu/NavMenu.tsx @@ -18,7 +18,7 @@ import NextLink from 'next/link'; import { useTranslation } from 'react-i18next'; import Icon from '@mdi/react'; import { useRouter } from 'next/router'; -import { ReportNavItems } from '../../../../../Reports/NavReportsList/ReportNavItems'; +import { reportNavItems } from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems'; import { ToolsList } from '../../../../../Tool/Home/ToolList'; import { useCurrentToolId } from '../../../../../../hooks/useCurrentToolId'; import theme from '../../../../../../theme'; @@ -27,8 +27,6 @@ import { useGetToolNotificationsQuery } from './GetToolNotifcations.generated'; import HandoffLink from 'src/components/HandoffLink'; import { ReportLink } from './ReportLink'; -export const filteredReportNavItems = ReportNavItems; - const useStyles = makeStyles()(() => ({ navListItem: { order: 2, @@ -252,7 +250,7 @@ const NavMenu: React.FC = () => { - {filteredReportNavItems.map(({ id, title }) => ( + {reportNavItems.map(({ id, title }) => ( void; - title: string; - rightExtra?: ReactNode; -} - -const StickyHeader = styled(Box)(({}) => ({ - position: 'sticky', - top: 0, -})); - -const NavListButton = styled(IconButton, { - shouldForwardProp: (prop) => prop !== 'panelOpen', -})(({ panelOpen }: { panelOpen: boolean }) => ({ - display: 'inline-block', - width: 48, - height: 48, - borderradius: 24, - margin: theme.spacing(1), - backgroundColor: panelOpen ? theme.palette.secondary.dark : 'transparent', -})); - -const NavListIcon = styled(FilterList)(({ theme }) => ({ - width: 24, - height: 24, - color: theme.palette.primary.dark, -})); - -export const AccountsListHeader: FC = ({ - title, - rightExtra, - isNavListOpen, - onNavListToggle, - showNavListButton = true, -}) => { - const { t } = useTranslation(); - - return ( - - - {showNavListButton && ( - - - - )} - - {title} - - {rightExtra} - - - ); -}; diff --git a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx index 9b276bdbc..2b694912f 100644 --- a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx +++ b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { ThemeProvider } from '@mui/material/styles'; import { DesignationAccountsDocument, @@ -154,4 +155,26 @@ describe('DesignationAccountsReport', () => { expect(getByText(title)).toBeInTheDocument(); expect(queryByTestId('EmptyReport')).toBeInTheDocument(); }); + + it('renders nav list icon and onclick triggers onNavListToggle', async () => { + onNavListToggle.mockClear(); + const { getByTestId } = render( + + + mocks={mocks} + > + + + , + ); + + expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument(); + userEvent.click(getByTestId('ReportsFilterIcon')); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); + }); }); diff --git a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.tsx b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.tsx index b0ebaaaae..9a01fe39d 100644 --- a/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.tsx +++ b/src/components/Reports/DesignationAccountsReport/DesignationAccountsReport.tsx @@ -2,15 +2,18 @@ import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, CircularProgress, Divider, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; -import { AccountsList as List } from '../AccountsListLayout/List/List'; -import { AccountsListHeader as Header } from '../AccountsListLayout/Header/Header'; -import type { Account } from '../AccountsListLayout/List/ListItem/ListItem'; +import { currencyFormat } from 'src/lib/intlFormat'; +import { useLocale } from 'src/hooks/useLocale'; import { useDesignationAccountsQuery } from './GetDesignationAccounts.generated'; import { useSetActiveDesignationAccountMutation } from './SetActiveDesignationAccount.generated'; +import { + MultiPageHeader, + HeaderTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageHeader'; import { Notification } from 'src/components/Notification/Notification'; import { EmptyReport } from 'src/components/Reports/EmptyReport/EmptyReport'; -import { currencyFormat } from 'src/lib/intlFormat'; -import { useLocale } from 'src/hooks/useLocale'; +import type { Account } from '../AccountsListLayout/List/ListItem/ListItem'; +import { AccountsList as List } from '../AccountsListLayout/List/List'; interface Props { accountListId: string; @@ -85,11 +88,12 @@ export const DesignationAccountsReport: React.FC = ({ return ( -
{loading ? ( { }), ); }); + it('renders nav list icon and onclick triggers onNavListToggle', async () => { + onNavListToggle.mockClear(); + const { getByTestId } = render( + + + mocks={mocks}> + + + + , + ); + + expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument(); + userEvent.click(getByTestId('ReportsFilterIcon')); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); + }); }); diff --git a/src/components/Reports/DonationsReport/DonationsReport.tsx b/src/components/Reports/DonationsReport/DonationsReport.tsx index f6c6f4440..fcce5e71b 100644 --- a/src/components/Reports/DonationsReport/DonationsReport.tsx +++ b/src/components/Reports/DonationsReport/DonationsReport.tsx @@ -2,10 +2,13 @@ import React, { useEffect, useState } from 'react'; import { Container, Box } from '@mui/material'; import { DateTime } from 'luxon'; import { useRouter } from 'next/router'; -import { AccountsListHeader as Header } from '../AccountsListLayout/Header/Header'; -import { DonationsReportTable } from './Table/DonationsReportTable'; -import DonationHistories from 'src/components/Dashboard/DonationHistories'; import { useGetDonationGraphQuery } from './GetDonationGraph.generated'; +import DonationHistories from 'src/components/Dashboard/DonationHistories'; +import { + MultiPageHeader, + HeaderTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageHeader'; +import { DonationsReportTable } from './Table/DonationsReportTable'; interface DonationReportsProps { accountListId: string; @@ -51,10 +54,11 @@ export const DonationsReport: React.FC = ({ return ( -
{ }), ); }); + it('renders nav list icon and onclick triggers onNavListToggle', async () => { + onNavListToggle.mockClear(); + const { getByTestId } = render( + + + + + + + , + ); + + expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument(); + userEvent.click(getByTestId('ReportsFilterIcon')); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); + }); }); diff --git a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.tsx b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.tsx index 4bc5edb3d..36dcd14f6 100644 --- a/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.tsx +++ b/src/components/Reports/ExpectedMonthlyTotalReport/ExpectedMonthlyTotalReport.tsx @@ -1,9 +1,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Box, CircularProgress } from '@mui/material'; -import { AccountsListHeader as Header } from '../AccountsListLayout/Header/Header'; import { useGetExpectedMonthlyTotalsQuery } from '../../../../pages/accountLists/[accountListId]/reports/GetExpectedMonthlyTotals.generated'; -import { EmptyDonationsTable } from '../../../../src/components/common/EmptyDonationsTable/EmptyDonationsTable'; +import { EmptyDonationsTable } from 'src/components/common/EmptyDonationsTable/EmptyDonationsTable'; +import { + MultiPageHeader, + HeaderTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageHeader'; import { ExpectedMonthlyTotalReportHeader } from './Header/ExpectedMonthlyTotalReportHeader'; import { ExpectedMonthlyTotalReportTable } from './Table/ExpectedMonthlyTotalReportTable'; @@ -62,10 +65,11 @@ export const ExpectedMonthlyTotalReport: React.FC = ({ return ( -
{ expect(queryByText(title)).toBeInTheDocument(); expect(getByTestId('FourteenMonthReport')).toBeInTheDocument(); - expect(queryByTestId('ReportNavList')).toBeNull(); + expect(queryByTestId('MultiPageMenu')).toBeNull(); }); it('filters report by designation account', async () => { diff --git a/src/components/Reports/NavReportsList/Item/Item.test.tsx b/src/components/Reports/NavReportsList/Item/Item.test.tsx deleted file mode 100644 index 697bc044f..000000000 --- a/src/components/Reports/NavReportsList/Item/Item.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Item } from './Item'; -import { render } from '__tests__/util/testingLibraryReactMock'; -import TestWrapper from '__tests__/util/TestWrapper'; - -const item = { - id: 'testItem', - title: 'test title', - subTitle: 'test subTitle', -}; - -describe('Item', () => { - it('default', () => { - const { queryByText } = render( - - - , - ); - expect(queryByText(item.title)).toBeInTheDocument(); - expect(queryByText(item.subTitle)).toBeInTheDocument(); - }); -}); diff --git a/src/components/Reports/NavReportsList/Item/Item.tsx b/src/components/Reports/NavReportsList/Item/Item.tsx deleted file mode 100644 index fbe403672..000000000 --- a/src/components/Reports/NavReportsList/Item/Item.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import ArrowForwardIos from '@mui/icons-material/ArrowForwardIos'; -import NextLink from 'next/link'; -import { useTranslation } from 'react-i18next'; -import { useAccountListId } from 'src/hooks/useAccountListId'; -import HandoffLink from 'src/components/HandoffLink'; - -interface ReportOption { - id: string; - title: string; - subTitle?: string; -} - -interface Props { - item: ReportOption; - isSelected: boolean; -} - -export const Item: React.FC = ({ item, isSelected, ...rest }) => { - const accountListId = useAccountListId(); - const { t } = useTranslation(); - - const children = ( - - - - - ); - - if (item.id === 'coaching') { - return {children}; - } else { - return ( - - {children} - - ); - } -}; diff --git a/src/components/Reports/NavReportsList/NavReportsList.tsx b/src/components/Reports/NavReportsList/NavReportsList.tsx deleted file mode 100644 index 63146de85..000000000 --- a/src/components/Reports/NavReportsList/NavReportsList.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import { - Box, - BoxProps, - IconButton, - List, - Slide, - Typography, -} from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { makeStyles } from 'tss-react/mui'; -import Close from '@mui/icons-material/Close'; -import { useTranslation } from 'react-i18next'; -import { Item } from './Item/Item'; -import { ReportNavItems } from './ReportNavItems'; -import { MultiselectFilter } from '../../../../graphql/types.generated'; -import { FilterListItemMultiselect } from 'src/components/Shared/Filters/FilterListItemMultiselect'; -import { useGetDesignationAccountsQuery } from '../DonationsReport/Table/Modal/EditDonation.generated'; -import { useAccountListId } from 'src/hooks/useAccountListId'; - -interface Props { - selectedId: string; - isOpen: boolean; - onClose: () => void; - designationAccounts: string[]; - setDesignationAccounts: (designationAccounts: string[]) => void; -} - -const useStyles = makeStyles()(() => ({ - root: { - overflow: 'hidden', - }, -})); - -const FilterHeader = styled(Box)(({ theme }) => ({ - padding: theme.spacing(2), - borderBottom: '1px solid', - borderBottomColor: theme.palette.grey[200], -})); - -const FilterList = styled(List)(({ theme }) => ({ - '& .MuiListItemIcon-root': { - minWidth: '37px', - }, - '& .FilterListItemMultiselect-root': { - marginBottom: theme.spacing(2), - }, -})); - -export const NavReportsList: React.FC = ({ - selectedId, - isOpen, - onClose, - designationAccounts, - setDesignationAccounts, - ...BoxProps -}) => { - const { classes } = useStyles(); - const { t } = useTranslation(); - const accountListId = useAccountListId(); - - const { data } = useGetDesignationAccountsQuery({ - variables: { - accountListId: accountListId ?? '', - }, - }); - const accounts = - data?.designationAccounts - .flatMap((group) => group.designationAccounts) - .map((account) => ({ - name: account.name, - value: account.id, - placeholder: null, - })) ?? []; - - const filter: MultiselectFilter = { - filterKey: 'designation_account_id', - title: 'Designation Account', - options: accounts, - }; - - return ( - -
- - - - - {t('Reports')} - - - - - - - {accounts.length > 1 && ( - { - setDesignationAccounts(value ?? []); - }} - /> - )} - {ReportNavItems.map((item) => ( - - ))} - - - -
-
- ); -}; diff --git a/src/components/Reports/NavReportsList/ReportNavItems.ts b/src/components/Reports/NavReportsList/ReportNavItems.ts deleted file mode 100644 index 3c21790b4..000000000 --- a/src/components/Reports/NavReportsList/ReportNavItems.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const ReportNavItems = [ - { - id: 'donations', - title: 'Donations', - }, - { - id: 'partnerCurrency', - title: '14 Month Partner Report', - subTitle: 'Partner Currency', - }, - { - id: 'salaryCurrency', - title: '14 Month Salary Report', - subTitle: 'Salary Currency', - }, - { - id: 'designationAccounts', - title: 'Designation Accounts', - }, - { - id: 'responsibilityCenters', - title: 'Responsibility Centers', - }, - { - id: 'expectedMonthlyTotal', - title: 'Expected Monthly Total', - }, - { - id: 'partnerGivingAnalysis', - title: 'Partner Giving Analysis', - }, - { - id: 'coaching', - title: 'Coaching', - }, -]; diff --git a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx index 1f40037ad..211953ea8 100644 --- a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx +++ b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.test.tsx @@ -213,6 +213,9 @@ const mocks: Mocks = { }; describe('PartnerGivingAnalysisReport', () => { + beforeEach(() => { + onNavListToggle.mockClear(); + }); it('loading', async () => { const { queryByTestId, queryByText } = render( @@ -279,7 +282,7 @@ describe('PartnerGivingAnalysisReport', () => { expect(queryByText(title)).toBeInTheDocument(); expect(getByTestId('PartnerGivingAnalysisReport')).toBeInTheDocument(); - expect(queryByTestId('ReportNavList')).toBeNull(); + expect(queryByTestId('MultiPageMenu')).toBeNull(); }); it('shows a placeholder when there are zero contacts', async () => { @@ -581,4 +584,26 @@ describe('PartnerGivingAnalysisReport', () => { // Test that it rounds to two decimal points expect(getByText('CA$86.47')).toBeInTheDocument(); }); + + it('renders nav list icon and onclick triggers onNavListToggle', async () => { + const { getByTestId } = render( + + + mocks={mocks} + > + + + , + ); + + expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument(); + userEvent.click(getByTestId('ReportsFilterIcon')); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); + }); }); diff --git a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.tsx b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.tsx index e9fa403f5..49241201d 100644 --- a/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.tsx +++ b/src/components/Reports/PartnerGivingAnalysisReport/PartnerGivingAnalysisReport.tsx @@ -5,17 +5,20 @@ import { useDebouncedValue } from 'src/hooks/useDebounce'; import { useMassSelection } from 'src/hooks/useMassSelection'; import { sanitizeFilters } from 'src/lib/sanitizeFilters'; import { useGetPartnerGivingAnalysisIdsForMassSelectionQuery } from 'src/hooks/GetIdsForMassSelection.generated'; +import { useGetPartnerGivingAnalysisReportQuery } from './PartnerGivingAnalysisReport.generated'; import { ReportContactFilterSetInput, PartnerGivingAnalysisReportContact, SortDirection, } from '../../../../graphql/types.generated'; import type { Order } from '../Reports.type'; -import { useGetPartnerGivingAnalysisReportQuery } from './PartnerGivingAnalysisReport.generated'; -import { PartnerGivingAnalysisReportTable as Table } from './Table/Table'; -import { AccountsListHeader as Header } from '../AccountsListLayout/Header/Header'; import { EmptyReport } from 'src/components/Reports/EmptyReport/EmptyReport'; import { ListHeader } from 'src/components/Shared/Header/ListHeader'; +import { + MultiPageHeader, + HeaderTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageHeader'; +import { PartnerGivingAnalysisReportTable as Table } from './Table/Table'; interface Props { accountListId: string; @@ -130,11 +133,11 @@ export const PartnerGivingAnalysisReport: React.FC = ({ return ( -
{ + beforeEach(() => { + onNavListToggle.mockClear(); + }); it('default', async () => { const { getByText, getByTestId, queryByTestId } = render( @@ -93,6 +97,27 @@ describe('ResponsibilityCentersReport', () => { expect(getByTestId('ResponsibilityCentersScrollBox')).toBeInTheDocument(); }); + it('renders nav list icon and onclick triggers onNavListToggle', async () => { + const { getByTestId } = render( + + + mocks={mocks} + > + + + , + ); + + expect(getByTestId('ReportsFilterIcon')).toBeInTheDocument(); + userEvent.click(getByTestId('ReportsFilterIcon')); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); + }); + it('loading', async () => { const { queryByTestId, getByText } = render( diff --git a/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.tsx b/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.tsx index 95a9115de..38ee24d26 100644 --- a/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.tsx +++ b/src/components/Reports/ResponsibilityCentersReport/ResponsibilityCentersReport.tsx @@ -2,9 +2,10 @@ import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, CircularProgress, Divider, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; -import { AccountsListHeader as Header } from '../AccountsListLayout/Header/Header'; -import { AccountsList as List } from '../AccountsListLayout/List/List'; -import type { Account } from '../AccountsListLayout/List/ListItem/ListItem'; +import { currencyFormat } from 'src/lib/intlFormat'; +import { useLocale } from 'src/hooks/useLocale'; +import { useSetActiveFinancialAccountMutation } from './SetActiveFinancialAccount.generated'; +import { useEntryHistoriesQuery } from './GetEntryHistories.generated'; import { FinancialAccountsDocument, FinancialAccountsQuery, @@ -14,12 +15,14 @@ import type { FinancialAccountsGroup, PreFinancialAccountsGroup, } from './ResponsibilityCentersReport.type'; -import { useSetActiveFinancialAccountMutation } from './SetActiveFinancialAccount.generated'; -import { useEntryHistoriesQuery } from './GetEntryHistories.generated'; import { Notification } from 'src/components/Notification/Notification'; import { EmptyReport } from 'src/components/Reports/EmptyReport/EmptyReport'; -import { currencyFormat } from 'src/lib/intlFormat'; -import { useLocale } from 'src/hooks/useLocale'; +import { + MultiPageHeader, + HeaderTypeEnum, +} from 'src/components/Shared/MultiPageLayout/MultiPageHeader'; +import { AccountsList as List } from '../AccountsListLayout/List/List'; +import type { Account } from '../AccountsListLayout/List/ListItem/ListItem'; interface Props { accountListId: string; @@ -170,10 +173,11 @@ export const ResponsibilityCentersReport: React.FC = ({ return ( -
{loading ? ( diff --git a/src/components/Reports/AccountsListLayout/Header/Header.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx similarity index 55% rename from src/components/Reports/AccountsListLayout/Header/Header.test.tsx rename to src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx index 8885184d1..9ef1d1b04 100644 --- a/src/components/Reports/AccountsListLayout/Header/Header.test.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.test.tsx @@ -1,23 +1,24 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { ThemeProvider } from '@mui/material/styles'; import userEvent from '@testing-library/user-event'; -import { AccountsListHeader as Header } from './Header'; +import { MultiPageHeader, HeaderTypeEnum } from './MultiPageHeader'; import theme from 'src/theme'; const totalBalance = 'CA111'; const title = 'test title'; const onNavListToggle = jest.fn(); -describe('AccountsListHeader', () => { +describe('MultiPageHeader', () => { it('default', async () => { const { getByRole, getByText } = render( -
, ); @@ -27,20 +28,39 @@ describe('AccountsListHeader', () => { userEvent.click( getByRole('button', { hidden: true, name: 'Toggle Filter Panel' }), ); + await waitFor(() => expect(onNavListToggle).toHaveBeenCalled()); }); it('should not render rightExtra if undefined', async () => { const { queryByText } = render( -
, ); expect(queryByText('CA111')).toBeNull(); }); + + it('should render the Settings menu', async () => { + const { getByTestId, getByText } = render( + + + , + ); + + expect(getByText('Toggle Preferences Menu')).toBeInTheDocument(); + expect(getByTestId('SettingsMenuIcon')).toBeInTheDocument(); + }); }); diff --git a/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx new file mode 100644 index 000000000..8693299d3 --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageHeader.tsx @@ -0,0 +1,106 @@ +import React, { FC, ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Box, IconButton, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import FilterList from '@mui/icons-material/FilterList'; +import MenuIcon from '@mui/icons-material/Menu'; +import theme from 'src/theme'; + +export enum HeaderTypeEnum { + Report = 'reports', + Settings = 'settings', +} + +interface MultiPageHeaderProps { + isNavListOpen: boolean; + onNavListToggle: () => void; + title: string; + headerType: HeaderTypeEnum; + rightExtra?: ReactNode; +} + +const StickyHeader = styled(Box, { + shouldForwardProp: (prop) => prop !== 'headerType', +})(({ headerType }: { headerType: HeaderTypeEnum }) => ({ + position: 'sticky', + top: 0, + height: 96, + color: + headerType === HeaderTypeEnum.Settings ? theme.palette.common.white : '', + backgroundColor: + headerType === HeaderTypeEnum.Settings ? theme.palette.primary.main : '', + paddingTop: headerType === HeaderTypeEnum.Settings ? theme.spacing(3) : '', + paddingBottom: headerType === HeaderTypeEnum.Settings ? theme.spacing(3) : '', +})); + +const NavListButton = styled(IconButton, { + shouldForwardProp: (prop) => prop !== 'panelOpen', +})(({ panelOpen }: { panelOpen: boolean }) => ({ + display: 'inline-block', + width: 48, + height: 48, + borderradius: 24, + margin: theme.spacing(0), + backgroundColor: panelOpen ? theme.palette.secondary.dark : 'transparent', + marginRight: '8px', + padding: '11px', +})); + +const NavFilterIcon = styled(FilterList)(() => ({ + width: 24, + height: 24, + color: theme.palette.primary.dark, +})); + +const NavMenuIcon = styled(MenuIcon)(() => ({ + width: 24, + height: 24, + color: theme.palette.common.white, +})); + +export const MultiPageHeader: FC = ({ + title, + rightExtra, + isNavListOpen, + onNavListToggle, + headerType, +}) => { + const { t } = useTranslation(); + + let titleAccess; + if (headerType === HeaderTypeEnum.Report) { + titleAccess = t('Toggle Filter Panel'); + } else if (headerType === HeaderTypeEnum.Settings) { + titleAccess = t('Toggle Preferences Menu'); + } + + return ( + + + + {headerType === HeaderTypeEnum.Report && ( + + )} + {headerType === HeaderTypeEnum.Settings && ( + + )} + + + {title} + + {rightExtra} + + + ); +}; diff --git a/src/components/Reports/NavReportsList/Item/Item.stories.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.stories.tsx similarity index 60% rename from src/components/Reports/NavReportsList/Item/Item.stories.tsx rename to src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.stories.tsx index 4b1a1d97f..88fde8f7c 100644 --- a/src/components/Reports/NavReportsList/Item/Item.stories.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.stories.tsx @@ -1,5 +1,6 @@ import React, { ReactElement } from 'react'; import { Item } from './Item'; +import { NavTypeEnum } from '../MultiPageMenu'; export default { title: 'Reports/ReportLayout/NavReportsList/Item', @@ -12,9 +13,13 @@ const item = { }; export const Default = (): ReactElement => ( - + ); export const Selected = (): ReactElement => ( - + ); diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx new file mode 100644 index 000000000..c1f587efa --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.test.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { Item } from './Item'; +import { render, waitFor } from '__tests__/util/testingLibraryReactMock'; +import TestWrapper from '__tests__/util/TestWrapper'; +import { NavTypeEnum } from '../MultiPageMenu'; +import userEvent from '@testing-library/user-event'; + +const item = { + id: 'testItem', + title: 'test title', + subTitle: 'test subTitle', +}; + +const itemWithSubitems = { + id: 'testItem', + title: 'test title', + subTitle: 'test subTitle', + subItems: [ + { + id: 'organizations', + title: 'Impersonate & Share', + grantedAccess: ['admin'], + }, + { + id: 'organizations/accountLists', + title: 'Account Lists', + grantedAccess: ['admin'], + }, + ], +}; + +describe('Item', () => { + it('default', () => { + const { queryByText, queryByTestId } = render( + + + , + ); + expect(queryByText(item.title)).toBeInTheDocument(); + expect(queryByText(item.subTitle)).toBeInTheDocument(); + expect(queryByTestId('multiPageMenuCollapser')).not.toBeInTheDocument(); + }); + + it('should render subItems', async () => { + const { getByTestId, queryByText } = render( + + + , + ); + + const arrowForwardIosIcon = getByTestId('ArrowForwardIosIcon'); + + expect(arrowForwardIosIcon).toBeInTheDocument(); + + expect(queryByText('Impersonate & Share')).not.toBeInTheDocument(); + expect(queryByText('Account Lists')).not.toBeInTheDocument(); + + userEvent.click(arrowForwardIosIcon); + + await waitFor(() => { + expect(queryByText('Impersonate & Share')).toBeInTheDocument(); + expect(queryByText('Account Lists')).toBeInTheDocument(); + }); + }); + + it('should show subItems as one if selected', async () => { + const { getByText } = render( + + + , + ); + + expect(getByText('Impersonate & Share')).toBeInTheDocument(); + expect(getByText('Account Lists')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.tsx new file mode 100644 index 000000000..877ad201c --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/Item/Item.tsx @@ -0,0 +1,100 @@ +import React, { useState, useMemo } from 'react'; +import { Collapse, ListItem, ListItemText } from '@mui/material'; +import { ArrowForwardIos } from '@mui/icons-material'; +import NextLink from 'next/link'; +import { useTranslation } from 'react-i18next'; +import { useAccountListId } from 'src/hooks/useAccountListId'; +import HandoffLink from 'src/components/HandoffLink'; +import { NavTypeEnum } from '../MultiPageMenu'; +import { NavItems } from '../MultiPageMenuItems'; +import theme from 'src/theme'; + +interface Props { + item: NavItems; + selectedId: string; + navType: NavTypeEnum; +} + +export const Item: React.FC = ({ + item, + selectedId, + navType, + ...rest +}) => { + const accountListId = useAccountListId(); + const [openSubMenu, setOpenSubMenu] = useState(false); + const { t } = useTranslation(); + + const isSelected = useMemo(() => { + if (item.id === selectedId) return true; + if (!item?.subItems?.length) return false; + return !!item.subItems.find((item) => item.id === selectedId)?.id; + }, [item]); + + const handleClick = () => { + if (isSelected) return; + if (!item?.subItems?.length) return; + setOpenSubMenu(!openSubMenu); + }; + + const children = ( + + + + + ); + + if (item.id === 'coaching') { + return {children}; + } else { + return ( + <> + + {children} + + {item?.subItems?.length && ( + + {item.subItems.map((subItem) => { + return ( + + ); + })} + + )} + + ); + } +}; diff --git a/src/components/Reports/NavReportsList/NavReportsList.stories.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.stories.tsx similarity index 75% rename from src/components/Reports/NavReportsList/NavReportsList.stories.tsx rename to src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.stories.tsx index 6cd4c0b21..6e7d9dfe4 100644 --- a/src/components/Reports/NavReportsList/NavReportsList.stories.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.stories.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { NavReportsList } from './NavReportsList'; +import { MultiPageMenu, NavTypeEnum } from './MultiPageMenu'; const selected = 'salaryCurrency'; @@ -9,12 +9,13 @@ export default { export const Default = (): ReactElement => { return ( - {}} designationAccounts={[]} setDesignationAccounts={() => {}} + navType={NavTypeEnum.Reports} /> ); }; diff --git a/src/components/Reports/NavReportsList/NavReportsList.test.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx similarity index 58% rename from src/components/Reports/NavReportsList/NavReportsList.test.tsx rename to src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx index f90c83cfb..1ef0863ab 100644 --- a/src/components/Reports/NavReportsList/NavReportsList.test.tsx +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.test.tsx @@ -2,11 +2,12 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ThemeProvider } from '@mui/material/styles'; -import { NavReportsList } from './NavReportsList'; +import { MultiPageMenu, NavTypeEnum } from './MultiPageMenu'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import theme from 'src/theme'; -import { GetDesignationAccountsQuery } from '../DonationsReport/Table/Modal/EditDonation.generated'; +import { GetDesignationAccountsQuery } from 'src/components/Reports/DonationsReport/Table/Modal/EditDonation.generated'; +import { GetUserAccessQuery } from './MultiPageMenuItems.generated'; const accountListId = 'account-list-1'; const selected = 'salaryCurrency'; @@ -16,18 +17,19 @@ const router = { isReady: true, }; -describe('NavReportsList', () => { +describe('MultiPageMenu', () => { it('default', async () => { const { getByText } = render( - {}} designationAccounts={[]} setDesignationAccounts={() => {}} + navType={NavTypeEnum.Reports} /> @@ -72,12 +74,13 @@ describe('NavReportsList', () => { mocks={mocks} onCall={mutationSpy} > - {}} designationAccounts={designationAccounts} setDesignationAccounts={setDesignationAccounts} + navType={NavTypeEnum.Reports} /> @@ -121,12 +124,13 @@ describe('NavReportsList', () => { mocks={mocks} onCall={mutationSpy} > - {}} designationAccounts={[]} setDesignationAccounts={jest.fn()} + navType={NavTypeEnum.Reports} /> @@ -139,4 +143,92 @@ describe('NavReportsList', () => { queryByRole('combobox', { name: 'Designation Account' }), ).not.toBeInTheDocument(); }); + + it('shows the developer tools', async () => { + const mutationSpy = jest.fn(); + const { queryByText, getByText } = render( + + + + mocks={{ + GetUserAccess: { + user: { + admin: false, + developer: true, + }, + }, + }} + onCall={mutationSpy} + > + {}} + designationAccounts={[]} + setDesignationAccounts={jest.fn()} + navType={NavTypeEnum.Settings} + /> + + + , + ); + + await waitFor(() => expect(mutationSpy).toHaveBeenCalled()); + + await waitFor(() => { + expect(queryByText('Manage Organizations')).not.toBeInTheDocument(); + }); + + await waitFor(() => { + expect(getByText('Admin Console')).toBeInTheDocument(); + expect(getByText('Backend Admin')).toBeInTheDocument(); + expect(getByText('Sidekiq')).toBeInTheDocument(); + }); + }); + + it('shows the admin tools', async () => { + const mutationSpy = jest.fn(); + const { queryByText, getByText } = render( + + + + mocks={{ + GetUserAccess: { + user: { + admin: true, + developer: false, + }, + }, + }} + onCall={mutationSpy} + > + {}} + designationAccounts={[]} + setDesignationAccounts={jest.fn()} + navType={NavTypeEnum.Settings} + /> + + + , + ); + + await waitFor(() => expect(mutationSpy).toHaveBeenCalled()); + + await waitFor(() => { + expect(queryByText('Sidekiq')).not.toBeInTheDocument(); + expect(queryByText('Backend Admin')).not.toBeInTheDocument(); + }); + + await waitFor(() => { + expect(getByText('Manage Organizations')).toBeInTheDocument(); + expect(getByText('Admin Console')).toBeInTheDocument(); + }); + }); }); diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx new file mode 100644 index 000000000..17b8f4e2e --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu.tsx @@ -0,0 +1,160 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Box, + BoxProps, + IconButton, + List, + Slide, + Typography, +} from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { makeStyles } from 'tss-react/mui'; +import Close from '@mui/icons-material/Close'; +import { useAccountListId } from 'src/hooks/useAccountListId'; +import { Item } from './Item/Item'; +import { MultiselectFilter } from '../../../../../graphql/types.generated'; +import { FilterListItemMultiselect } from 'src/components/Shared/Filters/FilterListItemMultiselect'; +import { useGetDesignationAccountsQuery } from 'src/components/Reports/DonationsReport/Table/Modal/EditDonation.generated'; +import { useGetUserAccessQuery } from './MultiPageMenuItems.generated'; +import { reportNavItems, settingsNavItems } from './MultiPageMenuItems'; + +export enum NavTypeEnum { + Reports = 'reports', + Settings = 'settings', +} + +interface Props { + selectedId: string; + isOpen: boolean; + onClose: () => void; + navType: NavTypeEnum; + designationAccounts?: string[]; + setDesignationAccounts?: (designationAccounts: string[]) => void; +} + +const useStyles = makeStyles()(() => ({ + root: { + overflow: 'hidden', + }, +})); + +const FilterHeader = styled(Box)(({ theme }) => ({ + padding: theme.spacing(2), + borderBottom: '1px solid', + borderBottomColor: theme.palette.grey[200], +})); + +const FilterList = styled(List)(({ theme }) => ({ + '& .MuiListItemIcon-root': { + minWidth: '37px', + }, + '& .FilterListItemMultiselect-root': { + marginBottom: theme.spacing(2), + }, +})); + +export const MultiPageMenu: React.FC = ({ + selectedId, + isOpen, + onClose, + navType, + designationAccounts, + setDesignationAccounts, + ...BoxProps +}) => { + const { classes } = useStyles(); + const { t } = useTranslation(); + const accountListId = useAccountListId(); + const { data: userPrivileges } = useGetUserAccessQuery(); + const navItems = + navType === NavTypeEnum.Reports ? reportNavItems : settingsNavItems; + const navTitle = + navType === NavTypeEnum.Reports ? t('Reports') : t('Settings'); + + const { data } = useGetDesignationAccountsQuery({ + variables: { + accountListId: accountListId ?? '', + }, + skip: !designationAccounts && !setDesignationAccounts, + }); + const accounts = + data?.designationAccounts + .flatMap((group) => group.designationAccounts) + .map((account) => ({ + name: account.name, + value: account.id, + placeholder: null, + })) ?? []; + + const filter: MultiselectFilter = { + filterKey: 'designation_account_id', + title: 'Designation Account', + options: accounts, + }; + + return ( + +
+ + + + + {navTitle} + + + + + + + {designationAccounts && + setDesignationAccounts && + accounts.length > 1 && ( + { + setDesignationAccounts(value ?? []); + }} + /> + )} + {navItems.map((item) => { + const showItem = useMemo(() => { + if (item?.grantedAccess?.length) { + if ( + item.grantedAccess.indexOf('admin') !== -1 && + userPrivileges?.user.admin + ) { + return true; + } + if ( + item.grantedAccess.indexOf('developer') !== -1 && + userPrivileges?.user.developer + ) { + return true; + } + } else return true; + return false; + }, [item, userPrivileges]); + + if (!showItem) return null; + return ( + + ); + })} + + + +
+
+ ); +}; diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.graphql b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.graphql new file mode 100644 index 000000000..e4e6a401c --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.graphql @@ -0,0 +1,6 @@ +query GetUserAccess { + user { + admin + developer + } +} diff --git a/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.ts b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.ts new file mode 100644 index 000000000..5d2691db5 --- /dev/null +++ b/src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenuItems.ts @@ -0,0 +1,104 @@ +export type NavItems = { + id: string; + title: string; + subTitle?: string; + grantedAccess?: string[]; + subItems?: NavItems[]; +}; + +export const reportNavItems: NavItems[] = [ + { + id: 'donations', + title: 'Donations', + }, + { + id: 'partnerCurrency', + title: '14 Month Partner Report', + subTitle: 'Partner Currency', + }, + { + id: 'salaryCurrency', + title: '14 Month Salary Report', + subTitle: 'Salary Currency', + }, + { + id: 'designationAccounts', + title: 'Designation Accounts', + }, + { + id: 'responsibilityCenters', + title: 'Responsibility Centers', + }, + { + id: 'expectedMonthlyTotal', + title: 'Expected Monthly Total', + }, + { + id: 'partnerGivingAnalysis', + title: 'Partner Giving Analysis', + }, + { + id: 'coaching', + title: 'Coaching', + }, +]; + +export const settingsNavItems: NavItems[] = [ + { + id: 'preferences', + title: 'Preferences', + }, + { + id: 'notifications', + title: 'Notifications', + }, + { + id: 'integrations', + title: 'Connect Services', + }, + { + id: 'manageAccounts', + title: 'Manage Accounts', + }, + { + id: 'manageCoaches', + title: 'Manage Coaches', + }, + { + id: 'organizations', + title: 'Manage Organizations', + grantedAccess: ['admin'], + subItems: [ + { + id: 'organizations', + title: 'Impersonate & Share', + grantedAccess: ['admin'], + }, + { + id: 'organizations/accountLists', + title: 'Account Lists', + grantedAccess: ['admin'], + }, + { + id: 'organizations/contacts', + title: 'Contacts', + grantedAccess: ['admin'], + }, + ], + }, + { + id: 'adminConsole', + title: 'Admin Console', + grantedAccess: ['admin', 'developer'], + }, + { + id: 'backendAdmin', + title: 'Backend Admin', + grantedAccess: ['developer'], + }, + { + id: 'sidekiq', + title: 'Sidekiq', + grantedAccess: ['developer'], + }, +];