diff --git a/packages/webapp/src/components/lists/ReportList/hooks.ts b/packages/webapp/src/components/lists/ReportList/hooks.ts index fd9eb8db4..60dda00d9 100644 --- a/packages/webapp/src/components/lists/ReportList/hooks.ts +++ b/packages/webapp/src/components/lists/ReportList/hooks.ts @@ -69,3 +69,35 @@ export function useTopRowFilterOptions(reportType: ReportType): [Service, Filter return [selectedService, reportFilterOptions] } + +export function useInitializeFilters(filters, setFilters, buildFilters) { + useEffect(() => { + const defaultFilters = buildFilters?.() ?? [] + + // If filters have been set locally + if (filters.length > 0) { + const _f = filters.map((f) => f.id).sort() + const _df = defaultFilters.map((f) => f.id).sort() + + // Hacky way to check if report type has changed + if (!(_f.join(',') === _df.join(','))) { + setFilters?.(defaultFilters) + } + } + + // If filters have not been set + else if (!filters?.length) setFilters?.(defaultFilters) + }, [filters, setFilters, buildFilters]) +} + +export function useGetValue(filters) { + const getSelectedValue = (key: string): string | number | string[] | number[] | undefined => { + return filters.find((f) => f.id === key)?.value + } + + const getStringValue = (key: string): string | undefined => { + return filters.find((f) => f.id === key)?.value?.[0] + } + + return { getSelectedValue, getStringValue } +} diff --git a/packages/webapp/src/components/lists/ReportList/index.module.scss b/packages/webapp/src/components/lists/ReportList/index.module.scss index bb6c10044..89f1cd387 100644 --- a/packages/webapp/src/components/lists/ReportList/index.module.scss +++ b/packages/webapp/src/components/lists/ReportList/index.module.scss @@ -64,7 +64,6 @@ .actionItemHeader { position: sticky; right: -1px; - background-color: color('white'); } } } diff --git a/packages/webapp/src/components/lists/ReportList/index.tsx b/packages/webapp/src/components/lists/ReportList/index.tsx index f8c6ab14c..70c3b54f5 100644 --- a/packages/webapp/src/components/lists/ReportList/index.tsx +++ b/packages/webapp/src/components/lists/ReportList/index.tsx @@ -29,7 +29,17 @@ export const ReportList: StandardFC = wrap(function ReportList( const [unfilteredData, setUnfilteredData] = useState(empty) const [filteredData, setFilteredData] = useState(empty) const [hiddenFields, setHiddenFields] = useRecoilState(hiddenReportFieldsState) - const { clearFilters, ...filterUtilities } = useFilteredData(unfilteredData, setFilteredData) + const { + clearFilters, + clearFilter, + filterColumns, + filterColumnTextValue, + filterRangedValues, + getDemographicValue, + headerFilters, + setHeaderFilters, + setFilterHelper + } = useFilteredData(unfilteredData, setFilteredData) // Exporting const { downloadCSV, setCsvFields } = useCsvExport(filteredData) @@ -54,15 +64,13 @@ export const ReportList: StandardFC = wrap(function ReportList( if (!fieldOption.selected) { const _hiddenFields = { ...hiddenFields, [fieldOption.key]: true } setHiddenFields(_hiddenFields) + clearFilter(fieldOption.key as string) } else { const _hiddenFields = { ...hiddenFields, [fieldOption.key]: undefined } setHiddenFields(_hiddenFields) } - - clearFilters() }, - [setHiddenFields, hiddenFields, clearFilters] - // [setHiddenFields, hiddenFields] + [setHiddenFields, hiddenFields, clearFilter] ) return ( @@ -91,7 +99,15 @@ export const ReportList: StandardFC = wrap(function ReportList( setFilteredData={setFilteredData} setUnfilteredData={setUnfilteredData} setCsvFields={setCsvFields} - {...filterUtilities} + {...{ + filterColumns, + filterColumnTextValue, + filterRangedValues, + getDemographicValue, + headerFilters, + setHeaderFilters, + setFilterHelper + }} /> diff --git a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/index.tsx b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/index.tsx index 355550439..d4619882b 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/index.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/index.tsx @@ -11,7 +11,6 @@ import { useClientReportFilterHelper } from './useClientReportFilterHelper' import { useClientReportCsvFields } from './useClientReportCsvFields' import { useClientReportFilters } from './useClientReportFilters' import { useClientReportData } from './useClientReportData' - export const ClientReport: FC = memo(function ClientReport({ data, filterColumnTextValue, @@ -22,7 +21,8 @@ export const ClientReport: FC = memo(function ClientReport({ setFilteredData, setFilterHelper, setCsvFields, - setFieldFilters, + headerFilters, + setHeaderFilters, hiddenFields }) { const columns = useClientReportColumns( @@ -32,9 +32,8 @@ export const ClientReport: FC = memo(function ClientReport({ getDemographicValue, hiddenFields ) - useClientReportData(setUnfilteredData, setFilteredData) - useClientReportFilters(setFieldFilters) + useClientReportFilters(headerFilters, setHeaderFilters) useClientReportCsvFields(setCsvFields, getDemographicValue, hiddenFields) useClientReportFilterHelper(setFilterHelper) diff --git a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportColumns.tsx b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportColumns.tsx index 2f52b083d..90a573ae5 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportColumns.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportColumns.tsx @@ -14,8 +14,9 @@ import { useLocale } from '~hooks/useLocale' import { Namespace, useTranslation } from '~hooks/useTranslation' import { TagBadgeList } from '~ui/TagBadgeList' import { useRecoilValue } from 'recoil' -import { fieldFiltersState, organizationState } from '~store' +import { headerFiltersState, organizationState } from '~store' import styles from '../../index.module.scss' +import { useGetValue } from '../../hooks' export function useClientReportColumns( filterColumns: (columnId: string, option: IDropdownOption) => void, @@ -27,7 +28,8 @@ export function useClientReportColumns( const { t } = useTranslation(Namespace.Reporting, Namespace.Clients) const [locale] = useLocale() const org = useRecoilValue(organizationState) - const fieldFilters = useRecoilValue(fieldFiltersState) + const headerFilters = useRecoilValue(headerFiltersState) + const { getSelectedValue, getStringValue } = useGetValue(headerFilters) return useMemo((): IPaginatedTableColumn[] => { const _pageColumns: IPaginatedTableColumn[] = [ @@ -36,9 +38,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('clientList.columns.name'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> @@ -56,7 +59,7 @@ export function useClientReportColumns( onRenderColumnHeader(key, name) { return ( f.id === key)?.value as string[]} + defaultSelectedKeys={getSelectedValue(key) as string[]} filterLabel={name} placeholder={name} options={org?.tags?.map((tag) => { @@ -78,9 +81,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.gender.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( { @@ -93,7 +97,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('gender', item) } }, @@ -102,9 +106,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.birthdate'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( { const sDate = startDate ? startDate.toISOString() : '' @@ -114,7 +119,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item.dateOfBirth ? new Date(item.dateOfBirth).toLocaleDateString(locale) : '' } }, @@ -123,15 +128,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.race.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('race', item) } }, @@ -140,9 +146,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.ethnicity.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -153,7 +160,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('ethnicity', item) } }, @@ -162,9 +169,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredLanguage.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -175,7 +183,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('preferredLanguage', item) } }, @@ -184,9 +192,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredContactMethod.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -197,7 +206,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('preferredContactMethod', item) } }, @@ -206,9 +215,10 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredContactTime.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -219,7 +229,7 @@ export function useClientReportColumns( /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return getDemographicValue('preferredContactTime', item) } }, @@ -228,15 +238,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.street'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.street ?? '' } }, @@ -245,15 +256,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.unit'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.unit ?? '' } }, @@ -262,15 +274,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.city'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.city ?? '' } }, @@ -279,15 +292,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.county'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.county ?? '' } }, @@ -296,15 +310,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.state'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.state ?? '' } }, @@ -313,15 +328,16 @@ export function useClientReportColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.zip'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Contact, index: number) { + onRenderColumnItem(item: Contact) { return item?.address?.zip } } @@ -331,12 +347,14 @@ export function useClientReportColumns( return returnColumns }, [ t, - locale, - org, filterColumnTextValue, - filterRangedValues, - getDemographicValue, + getSelectedValue, + org?.tags, filterColumns, + getDemographicValue, + filterRangedValues, + locale, + getStringValue, hiddenFields ]) } diff --git a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilterHelper.ts b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilterHelper.ts index 5618b9729..fae5f8588 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilterHelper.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilterHelper.ts @@ -5,7 +5,11 @@ import { Contact } from '@cbosuite/schema/dist/client-types' import { useEffect } from 'react' -import { applyDateFilter, applyStringFilterValue } from '~utils/filters' +import { + applyStringFilterValue, + applyMultipleChoiceFilterValues, + applyDateFilter +} from '~utils/filters' import { IFieldFilter } from '../../types' import { FilterHelper } from '../types' @@ -30,32 +34,24 @@ function clientFilterHelper(data: Contact[], filter: IFieldFilter, utils: any): (contact) => `${contact.name.first} ${contact.name.last}` ) } else if (id === TAGS) { - return data.filter((contact) => { - const tagIds = contact.tags.map((tag) => tag.id) - for (const v of value as any[]) { - if (tagIds.includes(v)) { - return true - } - } - return false - }) + return applyMultipleChoiceFilterValues(value as string[], data, (contact) => + contact.tags.map((tag) => tag.id) + ) + } else if (id === DATE_OF_BIRTH) { + const [from, to] = value as string[] + return applyDateFilter(from, to, data, (contact) => contact.dateOfBirth) + } else if (ADDRESS_FIELDS.includes(id)) { + return applyStringFilterValue(value[0], data, (contact) => contact?.address?.[id] || '') } else if (id === RACE) { return applyStringFilterValue(value[0], data, (contact) => { return utils.getDemographicValue('race', contact) }) - } else if (id === DATE_OF_BIRTH) { - const [_from, _to] = value as string[] - const from = _from ? new Date(_from) : undefined - const to = _to ? new Date(_to) : undefined - return applyDateFilter(from, to, data, (c) => { - const birthdate = c.dateOfBirth ? new Date(c.dateOfBirth) : null - birthdate?.setHours(0, 0, 0, 0) - return birthdate - }) - } else if (ADDRESS_FIELDS.includes(id)) { - return applyStringFilterValue(value[0], data, (contact) => contact?.address?.[id] || '') - } else if (DEMOGRAPHICS_FIELDS.includes(id)) { - return applyStringFilterValue(value[0], data, (contact) => contact.demographics[id] || '') + } else if (MULTI_CHOICE_DEMOGRAPHICS_FIELDS.includes(id)) { + return applyMultipleChoiceFilterValues( + value as string[], + data, + (contact) => contact.demographics[id] || '' + ) } else { return data.filter((contact) => (value as any[]).includes(contact[id])) } @@ -66,9 +62,8 @@ const NAME = 'name' const RACE = 'race' const TAGS = 'tags' const ADDRESS_FIELDS = ['city', 'county', 'state', 'zip', 'street', 'unit'] -const DEMOGRAPHICS_FIELDS = [ +const MULTI_CHOICE_DEMOGRAPHICS_FIELDS = [ 'gender', - 'race', 'ethnicity', 'preferredLanguage', 'preferredContactMethod', diff --git a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilters.ts b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilters.ts index 603d2341f..959c121b4 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilters.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/ClientReport/useClientReportFilters.ts @@ -2,13 +2,14 @@ * Copyright (c) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE file in the project. */ -import { useEffect } from 'react' +import { useInitializeFilters } from '../../hooks' import { IFieldFilter } from '../../types' -export function useClientReportFilters(setFieldFilters: (filters: IFieldFilter[]) => void) { - useEffect(() => { - setFieldFilters?.(buildClientFilters()) - }, [setFieldFilters]) +export function useClientReportFilters( + filters: IFieldFilter[], + setFilters: (filters: IFieldFilter[]) => void +) { + useInitializeFilters(filters, setFilters, buildClientFilters) } function buildClientFilters(): IFieldFilter[] { diff --git a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/index.tsx b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/index.tsx index e5be6e242..0ebcf6171 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/index.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/index.tsx @@ -22,7 +22,8 @@ export const RequestReport: FC = memo(function RequestReport( setFilteredData, setFilterHelper, setCsvFields, - setFieldFilters, + headerFilters, + setHeaderFilters, hiddenFields }) { const columns = useRequestReportColumns( @@ -34,7 +35,7 @@ export const RequestReport: FC = memo(function RequestReport( ) useRequestReportData(setUnfilteredData, setFilteredData) - useRequestReportFilters(setFieldFilters) + useRequestReportFilters(headerFilters, setHeaderFilters) useRequestReportCsvFields(setCsvFields, getDemographicValue, hiddenFields) useRequestReportFilterHelper(setFilterHelper) diff --git a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useContactFormColumns.tsx b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useContactFormColumns.tsx index 2d22715c0..1f2d82e55 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useContactFormColumns.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useContactFormColumns.tsx @@ -2,7 +2,7 @@ * Copyright (c) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE file in the project. */ -import { Contact, Engagement } from '@cbosuite/schema/dist/client-types' +import { Contact } from '@cbosuite/schema/dist/client-types' import { useMemo } from 'react' import { CustomOptionsFilter } from '~components/ui/CustomOptionsFilter' import { CustomTextFieldFilter } from '~components/ui/CustomTextFieldFilter' @@ -14,7 +14,8 @@ import { CustomDateRangeFilter } from '~components/ui/CustomDateRangeFilter' import { TagBadgeList } from '~ui/TagBadgeList' import { useLocale } from '~hooks/useLocale' import { useRecoilValue } from 'recoil' -import { organizationState } from '~store' +import { headerFiltersState, organizationState } from '~store' +import { useGetValue } from '~components/lists/ReportList/hooks' export function useContactFormColumns( filterColumns: (columnId: string, option: IDropdownOption) => void, @@ -26,8 +27,9 @@ export function useContactFormColumns( const { t } = useTranslation(Namespace.Reporting, Namespace.Clients, Namespace.Services) const [locale] = useLocale() const org = useRecoilValue(organizationState) + const headerFilters = useRecoilValue(headerFiltersState) + const { getSelectedValue, getStringValue } = useGetValue(headerFilters) - // Celar return useMemo(() => { const columns = [ { @@ -35,15 +37,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('clientList.columns.name'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return `${item?.contacts[0]?.name?.first} ${item?.contacts[0]?.name?.last}` } }, @@ -55,6 +58,7 @@ export function useContactFormColumns( onRenderColumnHeader(key, name) { return ( { @@ -67,7 +71,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement) { + onRenderColumnItem(item) { return } }, @@ -76,9 +80,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.gender.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -89,7 +94,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('gender', item?.contacts[0]) } }, @@ -98,9 +103,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.birthdate'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( { const sDate = startDate ? startDate.toISOString() : '' @@ -110,7 +116,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.dateOfBirth ? new Date(item?.contacts[0]?.dateOfBirth).toLocaleDateString(locale) : '' @@ -121,15 +127,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.race.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('race', item?.contacts[0]) } }, @@ -138,9 +145,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.ethnicity.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -151,7 +159,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('ethnicity', item?.contacts[0]) } }, @@ -160,9 +168,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredLanguage.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -173,7 +182,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('preferredLanguage', item?.contacts[0]) } }, @@ -182,9 +191,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredContactMethod.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -195,7 +205,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('preferredContactMethod', item?.contacts[0]) } }, @@ -204,9 +214,10 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('demographics.preferredContactTime.label'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( ({ @@ -217,7 +228,7 @@ export function useContactFormColumns( /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return getDemographicValue('preferredContactTime', item?.contacts[0]) } }, @@ -226,15 +237,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.street'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.street ?? '' } }, @@ -243,15 +255,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.unit'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.unit ?? '' } }, @@ -260,15 +273,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.city'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.city ?? '' } }, @@ -277,15 +291,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.county'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.county ?? '' } }, @@ -294,15 +309,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.state'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.state ?? '' } }, @@ -311,15 +327,16 @@ export function useContactFormColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('customFilters.zip'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> ) }, - onRenderColumnItem(item: Engagement, index: number) { + onRenderColumnItem(item) { return item?.contacts[0]?.address?.zip } } @@ -335,6 +352,8 @@ export function useContactFormColumns( filterColumns, filterRangedValues, getDemographicValue, + getSelectedValue, + getStringValue, t, hiddenFields, locale, diff --git a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useRequestFieldColumns.tsx b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useRequestFieldColumns.tsx index 783b1dc54..92f211a8f 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useRequestFieldColumns.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportColumns/useRequestFieldColumns.tsx @@ -15,7 +15,8 @@ import { CustomOptionsFilter } from '~components/ui/CustomOptionsFilter' import { ShortString } from '~components/ui/ShortString' import { TagBadgeList } from '~ui/TagBadgeList' import { useRecoilValue } from 'recoil' -import { organizationState } from '~store' +import { headerFiltersState, organizationState } from '~store' +import { useGetValue } from '~components/lists/ReportList/hooks' export function useRequestFieldColumns( filterColumns: (columnId: string, option: IDropdownOption) => void, @@ -26,6 +27,8 @@ export function useRequestFieldColumns( const { t } = useTranslation(Namespace.Reporting, Namespace.Clients, Namespace.Requests) const [locale] = useLocale() const org = useRecoilValue(organizationState) + const headerFilters = useRecoilValue(headerFiltersState) + const { getSelectedValue, getStringValue } = useGetValue(headerFilters) const statusList = useMemo( () => [ @@ -56,9 +59,10 @@ export function useRequestFieldColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('requestListColumns.title'), - onRenderColumnHeader(key, name, indexd) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> @@ -76,6 +80,7 @@ export function useRequestFieldColumns( onRenderColumnHeader(key, name) { return ( { @@ -97,9 +102,10 @@ export function useRequestFieldColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('requestListColumns.description'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> @@ -114,9 +120,10 @@ export function useRequestFieldColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('requestListColumns.startDate'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( { const sDate = startDate ? startDate.toISOString() : '' @@ -135,9 +142,10 @@ export function useRequestFieldColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('requestListColumns.endDate'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( { const sDate = startDate ? startDate.toISOString() : '' @@ -156,9 +164,10 @@ export function useRequestFieldColumns( headerClassName: styles.headerItemCell, itemClassName: styles.itemCell, name: t('requestListColumns.status'), - onRenderColumnHeader(key, name, index) { + onRenderColumnHeader(key, name) { return ( filterColumnTextValue(key, value)} /> @@ -194,6 +204,8 @@ export function useRequestFieldColumns( }, [ filterColumnTextValue, filterRangedValues, + getSelectedValue, + getStringValue, locale, t, org, diff --git a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilterHelper.ts b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilterHelper.ts index 2b0375949..775c06e6b 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilterHelper.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilterHelper.ts @@ -6,9 +6,10 @@ import { Engagement } from '@cbosuite/schema/dist/client-types' import { useEffect } from 'react' import { - applyDateFilter, applyStringFilterValue, - applyMultiStringFilterValue + applyMultiStringFilterValue, + applyMultipleChoiceFilterValues, + applyDateFilter } from '~utils/filters' import { IFieldFilter } from '../../types' import { FilterHelper } from '../types' @@ -28,43 +29,19 @@ function requestFilterHelper(data: Engagement[], filter: IFieldFilter, utils: an const { id, value } = filter // Contact filters - if (id === DATE_OF_BIRTH) { - const [_from, _to] = value as string[] - const from = _from ? new Date(_from) : undefined - const to = _to ? new Date(_to) : undefined - return applyDateFilter(from, to, data, (request) => { - const birthdate = request?.contacts[0]?.dateOfBirth - ? new Date(request.contacts[0].dateOfBirth) - : null - birthdate?.setHours(0, 0, 0, 0) - return birthdate - }) - } else if (id === NAME) { + if (id === NAME) { return applyStringFilterValue( value[0], data, (request) => `${request?.contacts[0]?.name.first} ${request?.contacts[0]?.name.last}` ) } else if (id === CLIENT_TAGS) { - return data.filter((request) => { - const tagIds = request?.contacts[0]?.tags.map((tag) => tag.id) - for (const v of value as any[]) { - if (tagIds.includes(v)) { - return true - } - } - return false - }) - } else if (id === REQUEST_TAGS) { - return data.filter((request) => { - const tagIds = request?.tags.map((tag) => tag.id) - for (const v of value as any[]) { - if (tagIds.includes(v)) { - return true - } - } - return false - }) + return applyMultipleChoiceFilterValues(value as string[], data, (request) => + request?.contacts[0]?.tags.map((tag) => tag.id) + ) + } else if (id === DATE_OF_BIRTH) { + const [from, to] = value as string[] + return applyDateFilter(from, to, data, (request) => request?.contacts[0]?.dateOfBirth) } else if (id === RACE) { return applyStringFilterValue(value[0], data, (request) => { return utils.getDemographicValue('race', request?.contacts[0]) @@ -75,22 +52,22 @@ function requestFilterHelper(data: Engagement[], filter: IFieldFilter, utils: an data, (request) => request?.contacts[0]?.address?.[id] || '' ) - } else if (DEMOGRAPHICS_FIELDS.includes(id)) { - return applyStringFilterValue( - value[0], + } else if (MULTI_CHOICE_DEMOGRAPHICS_FIELDS.includes(id)) { + return applyMultipleChoiceFilterValues( + value as string[], data, - (request) => request?.contacts[0]?.demographics?.[id] || '' + (request) => request?.contacts[0]?.demographics[id] || '' ) } // Request filters - else if (id === START_DATE || id === END_DATE) { - const [_from, _to] = value as string[] - const from = _from ? new Date(_from) : undefined - const to = _to ? new Date(_to) : undefined - return applyDateFilter(from, to, data, (request) => { - return request?.[id] ? new Date(request[id]) : null - }) + else if (id === REQUEST_TAGS) { + return applyMultipleChoiceFilterValues(value as string[], data, (request) => + request?.tags.map((tag) => tag.id) + ) + } else if (id === START_DATE || id === END_DATE) { + const [from, to] = value as string[] + return applyDateFilter(from, to, data, (request) => request?.[id]) } else if (id === SPECIALIST) { return applyMultiStringFilterValue(value[0], data, (request: Engagement) => { const user = request?.user @@ -113,9 +90,8 @@ const RACE = 'race' const CLIENT_TAGS = 'clientTags' const REQUEST_TAGS = 'requestTags' const ADDRESS_FIELDS = ['city', 'county', 'state', 'zip', 'street', 'unit'] -const DEMOGRAPHICS_FIELDS = [ +const MULTI_CHOICE_DEMOGRAPHICS_FIELDS = [ 'gender', - 'race', 'ethnicity', 'preferredLanguage', 'preferredContactMethod', diff --git a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilters.ts b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilters.ts index d721ac1f7..b55e205cd 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilters.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/RequestReport/useRequestReportFilters.ts @@ -2,15 +2,9 @@ * Copyright (c) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE file in the project. */ -import { useEffect } from 'react' +import { useInitializeFilters } from '../../hooks' import { IFieldFilter } from '../../types' -export function useRequestReportFilters(setFieldFilters: (filters: IFieldFilter[]) => void) { - useEffect(() => { - setFieldFilters(buildReportFilters()) - }, [setFieldFilters]) -} - function buildReportFilters(): IFieldFilter[] { const clientFilters = [ 'name', @@ -58,3 +52,7 @@ function buildReportFilters(): IFieldFilter[] { return [...clientFilters, ...requestFilters] } + +export function useRequestReportFilters(filters, setFilters: (filters: IFieldFilter[]) => void) { + useInitializeFilters(filters, setFilters, buildReportFilters) +} diff --git a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/index.tsx b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/index.tsx index b7e435892..6c2aef84e 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/index.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/index.tsx @@ -28,7 +28,8 @@ export const ServiceReport: FC = memo(function ClientReport({ setFilterHelper, setUnfilteredData, setCsvFields, - setFieldFilters, + headerFilters, + setHeaderFilters, hiddenFields }) { const { loading, deleteServiceAnswer, updateServiceAnswer } = useServiceReportData( @@ -36,7 +37,7 @@ export const ServiceReport: FC = memo(function ClientReport({ setUnfilteredData, setFilteredData ) - useServiceReportFilters(service, setFieldFilters) + useServiceReportFilters(headerFilters, setHeaderFilters, service) useServiceReportCsvFields(service, setCsvFields, getDemographicValue, hiddenFields) useServiceReportFilterHelper(setFilterHelper) diff --git a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useContactFormColumns.tsx b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useContactFormColumns.tsx index de309b570..7eb39b71b 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useContactFormColumns.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useContactFormColumns.tsx @@ -2,19 +2,10 @@ * Copyright (c) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE file in the project. */ -import { Contact, ServiceAnswer } from '@cbosuite/schema/dist/client-types' +import { Contact } from '@cbosuite/schema/dist/client-types' import { useMemo } from 'react' -import { CustomOptionsFilter } from '~components/ui/CustomOptionsFilter' -import { CustomTextFieldFilter } from '~components/ui/CustomTextFieldFilter' -import { CLIENT_DEMOGRAPHICS } from '~constants' -import { Namespace, useTranslation } from '~hooks/useTranslation' -import styles from '../../../index.module.scss' import { IDropdownOption } from '@fluentui/react' -import { CustomDateRangeFilter } from '~components/ui/CustomDateRangeFilter' -import { useLocale } from '~hooks/useLocale' -import { useRecoilValue } from 'recoil' -import { organizationState } from '~store' -import { TagBadgeList } from '~ui/TagBadgeList' +import { useContactFormColumns as useContactFormColumnsHelper } from '../../RequestReport/useRequestReportColumns/useContactFormColumns' export function useContactFormColumns( enabled: boolean, @@ -24,321 +15,15 @@ export function useContactFormColumns( getDemographicValue: (demographicKey: string, contact: Contact) => string, hiddenFields: Record ) { - const { t } = useTranslation(Namespace.Reporting, Namespace.Clients, Namespace.Services) - const [locale] = useLocale() - const org = useRecoilValue(organizationState) - - return useMemo(() => { - if (!enabled) { - return [] - } else { - const columns = [ - { - key: 'name', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('clientList.columns.name'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return `${item?.contacts[0]?.name?.first} ${item?.contacts[0]?.name?.last}` - } - }, - { - key: 'tags', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.tags'), - onRenderColumnHeader(key, name) { - return ( - { - return { - key: tag.id, - text: tag.label - } - })} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer) { - return - } - }, - { - key: 'gender', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.gender.label'), - onRenderColumnHeader(key, name, index) { - return ( - ({ - key: o.key, - text: t(`demographics.${key}.options.${o.key}`) - }))} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('gender', item?.contacts[0]) - } - }, - { - key: 'dateOfBirth', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.birthdate'), - onRenderColumnHeader(key, name, index) { - return ( - { - const sDate = startDate ? startDate.toISOString() : '' - const eDate = endDate ? endDate.toISOString() : '' - filterRangedValues(key, [sDate, eDate]) - }} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.dateOfBirth - ? new Date(item?.contacts[0]?.dateOfBirth).toLocaleDateString(locale) - : '' - } - }, - { - key: 'race', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.race.label'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('race', item?.contacts[0]) - } - }, - { - key: 'ethnicity', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.ethnicity.label'), - onRenderColumnHeader(key, name, index) { - return ( - ({ - key: o.key, - text: t(`demographics.${key}.options.${o.key}`) - }))} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('ethnicity', item?.contacts[0]) - } - }, - { - key: 'preferredLanguage', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.preferredLanguage.label'), - onRenderColumnHeader(key, name, index) { - return ( - ({ - key: o.key, - text: t(`demographics.${key}.options.${o.key}`) - }))} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('preferredLanguage', item?.contacts[0]) - } - }, - { - key: 'preferredContactMethod', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.preferredContactMethod.label'), - onRenderColumnHeader(key, name, index) { - return ( - ({ - key: o.key, - text: t(`demographics.${key}.options.${o.key}`) - }))} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('preferredContactMethod', item?.contacts[0]) - } - }, - { - key: 'preferredContactTime', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('demographics.preferredContactTime.label'), - onRenderColumnHeader(key, name, index) { - return ( - ({ - key: o.key, - text: t(`demographics.${key}.options.${o.key}`) - }))} - onFilterChanged={(option) => filterColumns(key, option)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return getDemographicValue('preferredContactTime', item?.contacts[0]) - } - }, - { - key: 'street', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.street'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.address?.street ?? '' - } - }, - { - key: 'unit', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.unit'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.address?.unit ?? '' - } - }, - { - key: 'city', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.city'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.address?.city ?? '' - } - }, - { - key: 'county', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.county'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.address?.county ?? '' - } - }, - { - key: 'state', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.state'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer, index: number) { - return item?.contacts[0]?.address?.state ?? '' - } - }, - { - key: 'zip', - headerClassName: styles.headerItemCell, - itemClassName: styles.itemCell, - name: t('customFilters.zip'), - onRenderColumnHeader(key, name, index) { - return ( - filterColumnTextValue(key, value)} - /> - ) - }, - onRenderColumnItem(item: ServiceAnswer) { - return item?.contacts[0]?.address?.zip - } - } - ] - - return columns.filter((col) => !hiddenFields?.[col.key]) - } - }, [ - t, - locale, - org, - enabled, - filterColumnTextValue, + const columns = useContactFormColumnsHelper( filterColumns, + filterColumnTextValue, filterRangedValues, getDemographicValue, hiddenFields - ]) + ) + + return useMemo(() => { + return enabled ? columns : [] + }, [enabled, columns]) } diff --git a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useServiceFieldColumns.tsx b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useServiceFieldColumns.tsx index faac21432..902e5cedb 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useServiceFieldColumns.tsx +++ b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportColumns/useServiceFieldColumns.tsx @@ -14,6 +14,9 @@ import { CustomNumberRangeFilter } from '~components/ui/CustomNumberRangeFilter' import { ShortString } from '~components/ui/ShortString' import { useLocale } from '~hooks/useLocale' import { getRecordedFieldValue } from '~utils/forms' +import { useRecoilValue } from 'recoil' +import { headerFiltersState } from '~store' +import { useGetValue } from '~components/lists/ReportList/hooks' const DROPDOWN_FIELD_TYPES = [ServiceFieldType.SingleChoice, ServiceFieldType.MultiChoice] const TEXT_FIELD_TYPES = [ServiceFieldType.SingleText, ServiceFieldType.MultilineText] @@ -27,6 +30,9 @@ export function useServiceFieldColumns( hiddenFields: Record ): IPaginatedTableColumn[] { const [locale] = useLocale() + const headerFilters = useRecoilValue(headerFiltersState) + const { getSelectedValue, getStringValue } = useGetValue(headerFilters) + return useMemo( () => fields @@ -40,6 +46,7 @@ export function useServiceFieldColumns( if (DROPDOWN_FIELD_TYPES.includes(field.type)) { return ( ({ key: value.id, text: value.label }))} @@ -51,6 +58,7 @@ export function useServiceFieldColumns( if (TEXT_FIELD_TYPES.includes(field.type)) { return ( filterColumnTextValue(key, value)} /> @@ -60,6 +68,7 @@ export function useServiceFieldColumns( if (field.type === ServiceFieldType.Date) { return ( { const sDate = startDate ? startDate.toISOString() : '' @@ -89,6 +98,7 @@ export function useServiceFieldColumns( }) return ( { if (item?.contacts[0]?.tags?.length > 0) { diff --git a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilterHelper.ts b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilterHelper.ts index ae0532435..d1163d4ee 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilterHelper.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilterHelper.ts @@ -7,6 +7,7 @@ import { ServiceAnswer, ServiceFieldType } from '@cbosuite/schema/dist/client-ty import { useEffect } from 'react' import { applyDateFilter, + applyMultipleChoiceFilterValues, applyNumberFilter, applyOptionsFilter, applyStringFilterValue @@ -31,46 +32,38 @@ function serviceFilterHelper( { id, value: filterValue, type }: IFieldFilter, utils: any ): ServiceAnswer[] { + // Contact filters if (id === NAME) { return applyStringFilterValue( filterValue[0], data, (a) => `${a.contacts[0].name.first} ${a.contacts[0].name.last}` ) - } else if (id === TAGS) { - return data.filter((answer) => { - const tagIds = answer.contacts?.[0]?.tags?.map((tag) => tag.id) ?? [] - for (const v of filterValue as any[]) { - if (tagIds.includes(v)) { - return true - } - } - return false - }) + } else if (id === CLIENT_TAGS) { + return applyMultipleChoiceFilterValues(filterValue as string[], data, (request) => + request?.contacts[0]?.tags.map((tag) => tag.id) + ) + } else if (id === DATE_OF_BIRTH) { + const [from, to] = filterValue as string[] + return applyDateFilter(from, to, data, (request) => request?.contacts[0]?.dateOfBirth) } else if (id === RACE) { - return applyStringFilterValue(filterValue[0], data, (answer) => { - return utils.getDemographicValue('race', answer.contacts[0]) + return applyStringFilterValue(filterValue[0], data, (request) => { + return utils.getDemographicValue('race', request?.contacts[0]) }) } else if (ADDRESS_FIELDS.includes(id)) { return applyStringFilterValue(filterValue[0], data, (a) => a?.contacts[0]?.address?.[id] || '') - } else if (DEMOGRAPHICS_FIELDS.includes(id)) { - return applyStringFilterValue( - filterValue[0], + } else if (MULTI_CHOICE_DEMOGRAPHIC_FIELDS.includes(id)) { + return applyMultipleChoiceFilterValues( + filterValue as string[], data, - (a) => a?.contacts[0]?.demographics?.[id] || '' + (a) => a?.contacts[0]?.demographics[id] || '' ) - } else if (type === ServiceFieldType.Date) { - const [_from, _to] = filterValue as string[] - const from = _from ? new Date(_from) : undefined - const to = _to ? new Date(_to) : undefined - return applyDateFilter(from, to, data, (a: ServiceAnswer) => { - const field = a.fields.find((f) => f.fieldId === id) - if (field) { - const date = new Date(field.value) - date.setHours(0, 0, 0, 0) - return date - } - }) + } + + // Service filters + else if (type === ServiceFieldType.Date) { + const [from, to] = filterValue as string[] + return applyDateFilter(from, to, data, (a) => a.fields.find((f) => f.fieldId === id)?.value) } else if (type === ServiceFieldType.Number) { const [_lower, _upper] = filterValue as number[] return applyNumberFilter(_lower, _upper, data, (a: ServiceAnswer) => { @@ -99,11 +92,11 @@ function serviceFilterHelper( const NAME = 'name' const RACE = 'race' -const TAGS = 'tags' +const DATE_OF_BIRTH = 'dateOfBirth' +const CLIENT_TAGS = 'clientTags' const ADDRESS_FIELDS = ['city', 'county', 'state', 'zip', 'street', 'unit'] -const DEMOGRAPHICS_FIELDS = [ +const MULTI_CHOICE_DEMOGRAPHIC_FIELDS = [ 'gender', - 'race', 'ethnicity', 'preferredLanguage', 'preferredContactMethod', diff --git a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilters.ts b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilters.ts index 061704997..541ef2d5f 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilters.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/ServiceReport/useServiceReportFilters.ts @@ -4,20 +4,20 @@ */ import { Service } from '@cbosuite/schema/dist/client-types' -import { useEffect } from 'react' import { empty } from '~utils/noop' +import { useInitializeFilters } from '../../hooks' import { IFieldFilter } from '../../types' export function useServiceReportFilters( - service: Service, - setFieldFilters: (filters: IFieldFilter[]) => void + filters: IFieldFilter[], + setFilters: (filters: IFieldFilter[]) => void, + service: Service ) { - useEffect( - function populateFieldFilters() { - setFieldFilters(buildServiceFilters(service)) - }, - [service, setFieldFilters] - ) + function buildServiceFiltersHelper() { + return buildServiceFilters(service) + } + + useInitializeFilters(filters, setFilters, buildServiceFiltersHelper) } function buildServiceFilters(service: Service): IFieldFilter[] { @@ -46,7 +46,7 @@ function buildServiceFilters(service: Service): IFieldFilter[] { 'county', 'state', 'zip', - 'tags' + 'clientTags' ].map((filter) => ({ id: filter, name: filter, diff --git a/packages/webapp/src/components/lists/ReportList/reports/types.ts b/packages/webapp/src/components/lists/ReportList/reports/types.ts index d25f0614e..4b2e81e22 100644 --- a/packages/webapp/src/components/lists/ReportList/reports/types.ts +++ b/packages/webapp/src/components/lists/ReportList/reports/types.ts @@ -11,8 +11,8 @@ export type FilterHelper = (data: unknown[], filter: IFieldFilter, utils: any) = export interface CommonReportProps { data: unknown[] service?: Service - fieldFilters: IFieldFilter[] - setFieldFilters: (filters: IFieldFilter[]) => void + headerFilters?: IFieldFilter[] + setHeaderFilters: (filters: IFieldFilter[]) => void hiddenFields: Record filterColumns: (columnId: string, option: IDropdownOption) => void filterColumnTextValue: (key: string, value: string) => void diff --git a/packages/webapp/src/components/lists/ReportList/useFilteredData.ts b/packages/webapp/src/components/lists/ReportList/useFilteredData.ts index b8a0eb77d..5a3e05ab4 100644 --- a/packages/webapp/src/components/lists/ReportList/useFilteredData.ts +++ b/packages/webapp/src/components/lists/ReportList/useFilteredData.ts @@ -7,28 +7,26 @@ import { IDropdownOption } from '@fluentui/react' import { useCallback, useEffect, useState } from 'react' import { useRecoilState } from 'recoil' import { Namespace, useTranslation } from '~hooks/useTranslation' -import { fieldFiltersState, headerFiltersState } from '~store' -import { empty, emptyStr } from '~utils/noop' +import { headerFiltersState } from '~store' +import { emptyStr } from '~utils/noop' import { FilterHelper } from './reports/types' import { IFieldFilter } from './types' export function useFilteredData(data: unknown[], setFilteredData: (data: unknown[]) => void) { const [headerFilters, setHeaderFilters] = useRecoilState(headerFiltersState) - const [fieldFilters, setFieldFilters] = useRecoilState(fieldFiltersState) - - // const [headerFilters, setHeaderFilters] = useState(empty) - // const [fieldFilters, setFieldFilters] = useState(empty) const [filterHelper, setFilterHelper] = useState<{ helper: FilterHelper } | null>(null) - const filterUtilities = useFilterUtilities(fieldFilters, setHeaderFilters) + const filterUtilities = useFilterUtilities(headerFilters, setHeaderFilters) const [filterUtils] = useState(filterUtilities) useEffect( function filterData() { + // If filters are empty, return the original data if (headerFilters.every(isEmptyFilter)) { setFilteredData(data) - } else if (filterHelper != null) { + } else if (filterHelper?.helper) { let result = data + headerFilters .filter((f) => !isEmptyFilter(f)) .forEach((filter) => { @@ -40,19 +38,33 @@ export function useFilteredData(data: unknown[], setFilteredData: (data: unknown [headerFilters, setFilteredData, filterHelper, filterUtils, data] ) + // Clear all header filters const clearFilters = useCallback( function clearFilters() { setHeaderFilters([]) - setFieldFilters([]) }, - [setHeaderFilters, setFieldFilters] + [setHeaderFilters] + ) + + // Clear a single header filter + const clearFilter = useCallback( + function clearFilters(filterToClear: string) { + const filterToClearIdx = headerFilters.findIndex((f) => f.id === filterToClear) + if (filterToClearIdx > -1) { + const newFilters = [...headerFilters] + newFilters[filterToClearIdx] = { ...newFilters[filterToClearIdx], value: emptyStr } + setHeaderFilters(newFilters) + } + }, + [setHeaderFilters, headerFilters] ) return { clearFilters, - fieldFilters, - setFieldFilters, + clearFilter, setFilterHelper, + setHeaderFilters, + headerFilters, ...filterUtilities } } @@ -86,18 +98,24 @@ function useFilterUtilities( const filterRangedValues = useCallback( (key: string, value: string[]) => { + // Filters[key].value cannot be directly set const newFilters = [...filters] - newFilters[filters.findIndex((f) => f.id === key)].value = value - setReportHeaderFilters(newFilters) + const idx = filters.findIndex((f) => f.id === key) + if (idx > -1) { + newFilters[idx] = { ...filters[idx], value } + setReportHeaderFilters(newFilters) + } }, [filters, setReportHeaderFilters] ) + const filterColumnTextValue = useCallback( (key: string, value: string) => { filterRangedValues(key, [value]) }, [filterRangedValues] ) + const getDemographicValue = useCallback( (demographicKey: string, contact: Contact): string => { switch (contact?.demographics?.[demographicKey]) { diff --git a/packages/webapp/src/components/ui/CustomDateRangeFilter/index.tsx b/packages/webapp/src/components/ui/CustomDateRangeFilter/index.tsx index cfc012402..efa1a81bf 100644 --- a/packages/webapp/src/components/ui/CustomDateRangeFilter/index.tsx +++ b/packages/webapp/src/components/ui/CustomDateRangeFilter/index.tsx @@ -2,7 +2,7 @@ * Copyright (c) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE file in the project. */ -import { useState } from 'react' +import { useEffect, useState } from 'react' import styles from './index.module.scss' import type { StandardFC } from '~types/StandardFC' import { wrap } from '~utils/appinsights' @@ -22,6 +22,7 @@ import { useLocale } from '~hooks/useLocale' import { noop } from '~utils/noop' interface CustomDateRangeFilterProps { + defaultSelectedDates?: [string | Date, string | Date] filterLabel: string minStartDate?: Date maxEndDate?: Date @@ -76,6 +77,7 @@ const actionButtonStyles: Partial = { export const CustomDateRangeFilter: StandardFC = wrap( function CustomDateRangeFilter({ filterLabel, + defaultSelectedDates, minStartDate, maxEndDate, startDate, @@ -91,6 +93,14 @@ export const CustomDateRangeFilter: StandardFC = wra const dateLimit = minStartDate === maxEndDate ? minStartDate : undefined + useEffect(() => { + if (defaultSelectedDates && defaultSelectedDates.length === 2) { + const defaults = defaultSelectedDates.map((d) => (d ? new Date(d) : null)) + setStartDateState(defaults[0]) + setEndDateState(defaults[1]) + } + }, [defaultSelectedDates, setStartDateState, setEndDateState]) + return ( <>