From 8fd881b00282bbd0dd123b37be75e78516ebd79f Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Mon, 15 Apr 2024 14:23:07 +0545 Subject: [PATCH 1/4] Add global filter --- src/hooks/useInputState.ts | 43 +++++++++ src/views/Home/Filters/i18n.json | 9 ++ src/views/Home/Filters/index.tsx | 109 +++++++++++++++++++++++ src/views/Home/Filters/styles.module.css | 5 ++ src/views/Home/index.tsx | 81 ++++++++++++++++- 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useInputState.ts create mode 100644 src/views/Home/Filters/i18n.json create mode 100644 src/views/Home/Filters/index.tsx create mode 100644 src/views/Home/Filters/styles.module.css diff --git a/src/hooks/useInputState.ts b/src/hooks/useInputState.ts new file mode 100644 index 00000000..f731be64 --- /dev/null +++ b/src/hooks/useInputState.ts @@ -0,0 +1,43 @@ +import React, { + useEffect, + useRef, +} from 'react'; + +type ValueOrSetterFn = T | ((value: T) => T); +function isSetterFn(value: ValueOrSetterFn): value is ((value: T) => T) { + return typeof value === 'function'; +} + +function useInputState( + initialValue: T, + sideEffect?: (newValue: T, oldValue: T) => T, +) { + const [value, setValue] = React.useState(initialValue); + const sideEffectRef = useRef(sideEffect); + + useEffect( + () => { + sideEffectRef.current = sideEffect; + }, + [sideEffect], + ); + + type SetValue = React.Dispatch>; + const setValueSafe: SetValue = React.useCallback((newValueOrSetter) => { + setValue((oldValue) => { + const newValue = isSetterFn(newValueOrSetter) + ? newValueOrSetter(oldValue) + : newValueOrSetter; + + if (sideEffectRef.current) { + return sideEffectRef.current(newValue, oldValue); + } + + return newValue; + }); + }, []); + + return [value, setValueSafe] as const; +} + +export default useInputState; diff --git a/src/views/Home/Filters/i18n.json b/src/views/Home/Filters/i18n.json new file mode 100644 index 00000000..f9dadf6b --- /dev/null +++ b/src/views/Home/Filters/i18n.json @@ -0,0 +1,9 @@ +{ + "namespace": "alertFilters", + "strings": { + "alertCountries": "All countries", + "alertUrgency": "Select Urgency type", + "alertSeverity": "Select Severity type", + "alertCertainty": "Select Certainty type" + } +} diff --git a/src/views/Home/Filters/index.tsx b/src/views/Home/Filters/index.tsx new file mode 100644 index 00000000..ab40a5d4 --- /dev/null +++ b/src/views/Home/Filters/index.tsx @@ -0,0 +1,109 @@ +import { useCallback } from 'react'; +import { MultiSelectInput } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; +import { stringNameSelector } from '@ifrc-go/ui/utils'; + +import { + AlertEnumsQuery, + CountryListQuery, +} from '#generated/types/graphql'; + +import { EntriesAsList } from '../../../types'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +type CountryType = NonNullable['allCountries']>[number]; + +interface AlertFilters { + key: string; + label: string; +} + +const countryKeySelector = (country: CountryType) => country.id; + +const keySelector = (alert: AlertFilters) => alert.key; +const labelSelector = (alert: AlertFilters) => alert.label; + +export interface FilterValue { + countries: string[]; + urgencyList: string[]; + severityList: string[]; + certaintyList: string[]; +} + +interface Props { + value: FilterValue; + onChange: React.Dispatch>; + countries?: NonNullable; + urgencyList?: NonNullable; + severityList?: NonNullable; + certaintyList?: NonNullable; +} + +function Filters(props: Props) { + const { + value, + onChange, + countries, + urgencyList, + severityList, + certaintyList, + } = props; + + const strings = useTranslation(i18n); + const handleChange = useCallback( + (...args: EntriesAsList) => { + const [val, key] = args; + onChange((prevValue): FilterValue => ({ + ...prevValue, + [key]: val, + })); + }, + [onChange], + ); + + return ( +
+ + + + +
+ ); +} + +export default Filters; diff --git a/src/views/Home/Filters/styles.module.css b/src/views/Home/Filters/styles.module.css new file mode 100644 index 00000000..07a3bfcd --- /dev/null +++ b/src/views/Home/Filters/styles.module.css @@ -0,0 +1,5 @@ +.filters { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); + grid-gap: var(--go-ui-spacing-md); +} diff --git a/src/views/Home/index.tsx b/src/views/Home/index.tsx index 0885e278..5de53319 100644 --- a/src/views/Home/index.tsx +++ b/src/views/Home/index.tsx @@ -1,4 +1,11 @@ -import { useState } from 'react'; +import { + useMemo, + useState, +} from 'react'; +import { + gql, + useQuery, +} from '@apollo/client'; import { Tab, TabList, @@ -8,18 +15,82 @@ import { import { useTranslation } from '@ifrc-go/ui/hooks'; import Page from '#components/Page'; +import { + AlertEnumsQuery, + AlertEnumsQueryVariables, + CountryListQuery, + CountryListQueryVariables, +} from '#generated/types/graphql'; +import useInputState from '#hooks/useInputState'; import AlertsTable from './AlertsTable'; import AlertsView from './AlertsView'; +import Filters, { FilterValue } from './Filters'; import i18n from './i18n.json'; import styles from './styles.module.css'; +const ALERT_ENUMS = gql` +query AlertEnums { + enums { + AlertInfoCertainty { + key + label + } + AlertInfoUrgency { + label + key + } + AlertInfoSeverity { + key + label + } + } +}`; + +// NOTE: alertFilters is related with filteredAlertCount +const COUNTRIES_LIST = gql` +query CountryList { + public { + allCountries(alertFilters: {}) { + name + id + iso3 + filteredAlertCount + } + } +} +`; + +const defaultFilterValue: FilterValue = { + countries: [], + urgencyList: [], + severityList: [], + certaintyList: [], +}; + export type TabKeys = 'map' | 'table'; // eslint-disable-next-line import/prefer-default-export export function Component() { const strings = useTranslation(i18n); const [activeTab, setActiveTab] = useState('map'); + const [filters, setFilters] = useInputState(defaultFilterValue); + + const { + data: alertEnumsResponse, + } = useQuery( + ALERT_ENUMS, + ); + + const { + data: countryResponse, + } = useQuery( + COUNTRIES_LIST, + ); + + const countriesWithAlert = useMemo(() => countryResponse?.public.allCountries.filter( + (country) => (country?.filteredAlertCount ?? 0) > 0, + ), [countryResponse?.public.allCountries]); return ( )} > + From c02f659fb10dab3903d7cf1b02be65daf5a04fef Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Thu, 18 Apr 2024 16:48:33 +0545 Subject: [PATCH 2/4] Add region filter --- src/views/Home/Filters/i18n.json | 3 ++- src/views/Home/Filters/index.tsx | 36 +++++++++++++++++++++++++------- src/views/Home/index.tsx | 23 ++++++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/views/Home/Filters/i18n.json b/src/views/Home/Filters/i18n.json index f9dadf6b..2e5cdff8 100644 --- a/src/views/Home/Filters/i18n.json +++ b/src/views/Home/Filters/i18n.json @@ -1,7 +1,8 @@ { "namespace": "alertFilters", "strings": { - "alertCountries": "All countries", + "alertCountries": "All Countries", + "alertAdmin1": "All Admin1", "alertUrgency": "Select Urgency type", "alertSeverity": "Select Severity type", "alertCertainty": "Select Certainty type" diff --git a/src/views/Home/Filters/index.tsx b/src/views/Home/Filters/index.tsx index ab40a5d4..4440a41a 100644 --- a/src/views/Home/Filters/index.tsx +++ b/src/views/Home/Filters/index.tsx @@ -1,9 +1,13 @@ import { useCallback } from 'react'; -import { MultiSelectInput } from '@ifrc-go/ui'; +import { + MultiSelectInput, + SelectInput, +} from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; import { stringNameSelector } from '@ifrc-go/ui/utils'; import { + AdminListQuery, AlertEnumsQuery, CountryListQuery, } from '#generated/types/graphql'; @@ -13,20 +17,25 @@ import { EntriesAsList } from '../../../types'; import i18n from './i18n.json'; import styles from './styles.module.css'; -type CountryType = NonNullable['allCountries']>[number]; +type CountryOption = NonNullable['allCountries']>[number]; + +type AdminOption = NonNullable['admin1s']>['items']>[number]; interface AlertFilters { key: string; label: string; } -const countryKeySelector = (country: CountryType) => country.id; +const countryKeySelector = (country: CountryOption) => country.id; + +const adminKeySelector =(admin: AdminOption) => admin.id; const keySelector = (alert: AlertFilters) => alert.key; const labelSelector = (alert: AlertFilters) => alert.label; export interface FilterValue { - countries: string[]; + countries: string | undefined; + admin1: string | undefined; urgencyList: string[]; severityList: string[]; certaintyList: string[]; @@ -35,6 +44,8 @@ export interface FilterValue { interface Props { value: FilterValue; onChange: React.Dispatch>; + onCountryChange: (...args: EntriesAsList) => void; + admin1?: NonNullable['admin1s']>['items']>; countries?: NonNullable; urgencyList?: NonNullable; severityList?: NonNullable; @@ -46,12 +57,15 @@ function Filters(props: Props) { value, onChange, countries, + admin1, urgencyList, severityList, certaintyList, + onCountryChange, } = props; const strings = useTranslation(i18n); + const handleChange = useCallback( (...args: EntriesAsList) => { const [val, key] = args; @@ -65,15 +79,23 @@ function Filters(props: Props) { return (
- + ( + ADMIN_LIST, + ); + const countriesWithAlert = useMemo(() => countryResponse?.public.allCountries.filter( (country) => (country?.filteredAlertCount ?? 0) > 0, ), [countryResponse?.public.allCountries]); @@ -118,8 +139,10 @@ export function Component() { > Date: Mon, 22 Apr 2024 11:29:05 +0545 Subject: [PATCH 3/4] Add admin1 list --- src/views/Home/Filters/index.tsx | 71 ++++++++++++++++++++++---------- src/views/Home/index.tsx | 19 +++++---- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/views/Home/Filters/index.tsx b/src/views/Home/Filters/index.tsx index 4440a41a..8cc83996 100644 --- a/src/views/Home/Filters/index.tsx +++ b/src/views/Home/Filters/index.tsx @@ -1,10 +1,14 @@ -import { useCallback } from 'react'; +import { + useCallback, + useMemo, +} from 'react'; import { MultiSelectInput, SelectInput, } from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; import { stringNameSelector } from '@ifrc-go/ui/utils'; +import { isNotDefined } from '@togglecorp/fujs'; import { AdminListQuery, @@ -18,24 +22,30 @@ import i18n from './i18n.json'; import styles from './styles.module.css'; type CountryOption = NonNullable['allCountries']>[number]; - type AdminOption = NonNullable['admin1s']>['items']>[number]; +type Admin1 = NonNullable['admin1s']>['items']>; +type Countries = NonNullable; +type Urgency = NonNullable; +type Severity = NonNullable; +type Certainty = NonNullable; + interface AlertFilters { key: string; label: string; } const countryKeySelector = (country: CountryOption) => country.id; +const countryLabelSelector = (country: CountryOption) => country.name; -const adminKeySelector =(admin: AdminOption) => admin.id; +const adminKeySelector = (admin1: AdminOption) => admin1.id; const keySelector = (alert: AlertFilters) => alert.key; const labelSelector = (alert: AlertFilters) => alert.label; export interface FilterValue { - countries: string | undefined; - admin1: string | undefined; + countryList: string | undefined; + admin1List: string | undefined; urgencyList: string[]; severityList: string[]; certaintyList: string[]; @@ -44,24 +54,22 @@ export interface FilterValue { interface Props { value: FilterValue; onChange: React.Dispatch>; - onCountryChange: (...args: EntriesAsList) => void; - admin1?: NonNullable['admin1s']>['items']>; - countries?: NonNullable; - urgencyList?: NonNullable; - severityList?: NonNullable; - certaintyList?: NonNullable; + admin1List?: Admin1; + countryList?: Countries; + urgencyList?: Urgency; + severityList?: Severity; + certaintyList?: Certainty; } function Filters(props: Props) { const { value, onChange, - countries, - admin1, + countryList, + admin1List, urgencyList, severityList, certaintyList, - onCountryChange, } = props; const strings = useTranslation(i18n); @@ -77,25 +85,44 @@ function Filters(props: Props) { [onChange], ); + const filteredAdmin1 = useMemo(() => { + if (isNotDefined(value.countryList) || isNotDefined(admin1List)) return admin1List; + + const selectedCountry = countryList?.find( + (country: CountryOption) => country.id === value.certaintyList, + ); + + if (isNotDefined(selectedCountry)) return admin1List; + + return admin1List?.filter( + (admin: AdminOption) => admin.countryId === selectedCountry.id, + ); + }, [ + value.countryList, + admin1List, + countryList, + value.certaintyList, + ]); + return (
['allCountries']>[number]; + export type TabKeys = 'map' | 'table'; + // eslint-disable-next-line import/prefer-default-export export function Component() { const strings = useTranslation(i18n); @@ -110,7 +114,7 @@ export function Component() { ); const countriesWithAlert = useMemo(() => countryResponse?.public.allCountries.filter( - (country) => (country?.filteredAlertCount ?? 0) > 0, + (country: CountryOption) => (country?.filteredAlertCount ?? 0) > 0, ), [countryResponse?.public.allCountries]); return ( @@ -138,11 +142,10 @@ export function Component() { )} > Date: Mon, 22 Apr 2024 17:53:37 +0545 Subject: [PATCH 4/4] Improve filters --- backend | 2 +- src/hooks/useInputState.ts | 43 ----- .../Home/{AlertsView => }/AlertContext.tsx | 29 +++ src/views/Home/AlertsTable/index.tsx | 29 ++- src/views/Home/AlertsTable/styles.module.css | 4 +- .../AlertsAside/Admin1Alerts/index.tsx | 2 +- .../AlertsAside/CountryAdmin1List/index.tsx | 16 +- .../AlertsAside/CountryAlerts/index.tsx | 2 +- .../AlertsAside/CountryDetail/index.tsx | 2 +- .../Home/AlertsView/AlertsAside/index.tsx | 2 +- src/views/Home/AlertsView/AlertsMap/index.tsx | 2 +- .../AlertsView/AlertsMap/styles.module.css | 2 +- src/views/Home/AlertsView/index.tsx | 91 ++------- src/views/Home/AlertsView/styles.module.css | 2 +- src/views/Home/Filters/index.tsx | 152 +++++++-------- src/views/Home/index.tsx | 175 +++++++++++++----- src/views/Home/useAlertFilters.ts | 36 ++++ 17 files changed, 320 insertions(+), 271 deletions(-) delete mode 100644 src/hooks/useInputState.ts rename src/views/Home/{AlertsView => }/AlertContext.tsx (65%) create mode 100644 src/views/Home/useAlertFilters.ts diff --git a/backend b/backend index df2de8d8..4e7dc6d4 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit df2de8d85510a9364c405bc17b1b35fb0946b26d +Subproject commit 4e7dc6d410948c9a49e2196796b55b8d111b5279 diff --git a/src/hooks/useInputState.ts b/src/hooks/useInputState.ts deleted file mode 100644 index f731be64..00000000 --- a/src/hooks/useInputState.ts +++ /dev/null @@ -1,43 +0,0 @@ -import React, { - useEffect, - useRef, -} from 'react'; - -type ValueOrSetterFn = T | ((value: T) => T); -function isSetterFn(value: ValueOrSetterFn): value is ((value: T) => T) { - return typeof value === 'function'; -} - -function useInputState( - initialValue: T, - sideEffect?: (newValue: T, oldValue: T) => T, -) { - const [value, setValue] = React.useState(initialValue); - const sideEffectRef = useRef(sideEffect); - - useEffect( - () => { - sideEffectRef.current = sideEffect; - }, - [sideEffect], - ); - - type SetValue = React.Dispatch>; - const setValueSafe: SetValue = React.useCallback((newValueOrSetter) => { - setValue((oldValue) => { - const newValue = isSetterFn(newValueOrSetter) - ? newValueOrSetter(oldValue) - : newValueOrSetter; - - if (sideEffectRef.current) { - return sideEffectRef.current(newValue, oldValue); - } - - return newValue; - }); - }, []); - - return [value, setValueSafe] as const; -} - -export default useInputState; diff --git a/src/views/Home/AlertsView/AlertContext.tsx b/src/views/Home/AlertContext.tsx similarity index 65% rename from src/views/Home/AlertsView/AlertContext.tsx rename to src/views/Home/AlertContext.tsx index e2ac6912..ae0edbfe 100644 --- a/src/views/Home/AlertsView/AlertContext.tsx +++ b/src/views/Home/AlertContext.tsx @@ -1,5 +1,11 @@ import { createContext } from 'react'; +import { + AlertInfoCertaintyEnum, + AlertInfoSeverityEnum, + AlertInfoUrgencyEnum, +} from '#generated/types/graphql'; + type Id = string; // type SetStateFn = React.Dispatch>; type SetStateFn = (newValue: T | undefined) => void; @@ -24,6 +30,14 @@ export interface AlertContextProps { setActiveAlertId: SetStateFn; setActiveCountryName: SetStateFn; + + selectedUrgencyTypes: AlertInfoUrgencyEnum[] | undefined; + selectedSeverityTypes: AlertInfoSeverityEnum[] | undefined; + selectedCertaintyTypes: AlertInfoCertaintyEnum[] | undefined; + + setSelectedUrgencyTypes: SetStateFn; + setSelectedSeverityTypes: SetStateFn; + setSelectedCertaintyTypes: SetStateFn; } const AlertContext = createContext({ @@ -34,6 +48,9 @@ const AlertContext = createContext({ activeAdmin1Id: undefined, activeGoAdmin1Id: undefined, activeAlertId: undefined, + selectedUrgencyTypes: undefined, + selectedSeverityTypes: undefined, + selectedCertaintyTypes: undefined, setBbox: () => { // eslint-disable-next-line no-console console.warn('AlertContext::setBbox called without provider'); @@ -62,6 +79,18 @@ const AlertContext = createContext({ // eslint-disable-next-line no-console console.warn('AlertContext::setActiveCountryName called without provider'); }, + setSelectedUrgencyTypes: () => { + // eslint-disable-next-line no-console + console.warn('AlertContext::setSelectedUrgencyTypes called without provider'); + }, + setSelectedSeverityTypes: () => { + // eslint-disable-next-line no-console + console.warn('AlertContext::setSelectedSeverityTypes called without provider'); + }, + setSelectedCertaintyTypes: () => { + // eslint-disable-next-line no-console + console.warn('AlertContext::setSelectedCertaintyTypes called without provider'); + }, }); export default AlertContext; diff --git a/src/views/Home/AlertsTable/index.tsx b/src/views/Home/AlertsTable/index.tsx index b4e07cfe..cb02be9d 100644 --- a/src/views/Home/AlertsTable/index.tsx +++ b/src/views/Home/AlertsTable/index.tsx @@ -1,6 +1,7 @@ import { ComponentType, HTMLProps, + useContext, useMemo, } from 'react'; import { @@ -19,22 +20,30 @@ import { createListDisplayColumn, createStringColumn, } from '@ifrc-go/ui/utils'; -import { isNotDefined } from '@togglecorp/fujs'; +import { + isDefined, + isNotDefined, +} from '@togglecorp/fujs'; import { + AlertFilter, AlertInformationsQuery, AlertInformationsQueryVariables, + OffsetPaginationInput, } from '#generated/types/graphql'; import useFilterState from '#hooks/useFilterState'; import { createLinkColumn } from '#utils/domain/tableHelpers'; +import AlertContext from '../AlertContext'; +import useAlertFilters from '../useAlertFilters'; + import i18n from './i18n.json'; import styles from './styles.module.css'; const ALERT_INFORMATIONS = gql` - query AlertInformations($pagination: OffsetPaginationInput) { + query AlertInformations($pagination: OffsetPaginationInput, $filters: AlertFilter) { public { - alerts(pagination: $pagination) { + alerts(pagination: $pagination, filters: $filters) { limit offset count @@ -74,6 +83,8 @@ const PAGE_SIZE = 20; function AlertsTable() { const strings = useTranslation(i18n); + const alertFilters = useAlertFilters(); + const { activeCountryId, activeAdmin1Id } = useContext(AlertContext); const { sortState, @@ -89,14 +100,24 @@ function AlertsTable() { filter: {}, }); - const variables = useMemo(() => ({ + const variables = useMemo<{ filters: AlertFilter, pagination: OffsetPaginationInput }>(() => ({ pagination: { offset: page, limit, }, + filters: { + ...alertFilters, + country: isDefined(activeCountryId) + ? { pk: activeCountryId } + : undefined, + admin1: activeAdmin1Id, + }, }), [ page, limit, + alertFilters, + activeCountryId, + activeAdmin1Id, ]); const { diff --git a/src/views/Home/AlertsTable/styles.module.css b/src/views/Home/AlertsTable/styles.module.css index 8220554a..a10f696c 100644 --- a/src/views/Home/AlertsTable/styles.module.css +++ b/src/views/Home/AlertsTable/styles.module.css @@ -11,12 +11,12 @@ .region { width: 0%; - min-width: 5rem; + min-width: 7rem; } .country { width: 0%; - min-width: 6rem; + min-width: 8rem; } .admins { diff --git a/src/views/Home/AlertsView/AlertsAside/Admin1Alerts/index.tsx b/src/views/Home/AlertsView/AlertsAside/Admin1Alerts/index.tsx index a4cdb259..8fd6bbdf 100644 --- a/src/views/Home/AlertsView/AlertsAside/Admin1Alerts/index.tsx +++ b/src/views/Home/AlertsView/AlertsAside/Admin1Alerts/index.tsx @@ -26,7 +26,7 @@ import { } from '#generated/types/graphql'; import { stringIdSelector } from '#utils/selectors'; -import AlertContext from '../../AlertContext'; +import AlertContext from '../../../AlertContext'; import AlertListItem from '../AlertListItem'; const ADMIN1_DETAIL = gql` diff --git a/src/views/Home/AlertsView/AlertsAside/CountryAdmin1List/index.tsx b/src/views/Home/AlertsView/AlertsAside/CountryAdmin1List/index.tsx index 2a7108ae..d32cf8d3 100644 --- a/src/views/Home/AlertsView/AlertsAside/CountryAdmin1List/index.tsx +++ b/src/views/Home/AlertsView/AlertsAside/CountryAdmin1List/index.tsx @@ -18,22 +18,22 @@ import { CountryAdmin1QueryVariables, } from '#generated/types/graphql'; import { stringIdSelector } from '#utils/selectors'; +import useAlertFilters from '#views/Home/useAlertFilters'; -import AlertContext from '../../AlertContext'; +import AlertContext from '../../../AlertContext'; import Admin1ListItem from '../Admin1ListItem'; type CountryAdmin1 = NonNullable['country']>['admin1s'][number]; const COUNTRY_ADMIN1 = gql` -query CountryAdmin1($countryId: ID!) { +query CountryAdmin1($countryId: ID!, $alertFilters: AlertFilter) { public { - id country(pk: $countryId) { id name alertCount ifrcGoId - admin1s(alertFilters: {}) { + admin1s(alertFilters: $alertFilters) { id name ifrcGoId @@ -51,10 +51,14 @@ interface Props { function CountryAdmin1List(props: Props) { const { countryId } = props; const { setActiveAdmin1Id } = useContext(AlertContext); + const alertFilters = useAlertFilters(); const variables = useMemo( - () => ({ countryId }), - [countryId], + () => ({ + countryId, + alertFilters, + }), + [countryId, alertFilters], ); const { diff --git a/src/views/Home/AlertsView/AlertsAside/CountryAlerts/index.tsx b/src/views/Home/AlertsView/AlertsAside/CountryAlerts/index.tsx index 3453a817..6b42410d 100644 --- a/src/views/Home/AlertsView/AlertsAside/CountryAlerts/index.tsx +++ b/src/views/Home/AlertsView/AlertsAside/CountryAlerts/index.tsx @@ -24,7 +24,7 @@ import { } from '#generated/types/graphql'; import { stringIdSelector } from '#utils/selectors'; -import AlertContext from '../../AlertContext'; +import AlertContext from '../../../AlertContext'; import AlertListItem from '../AlertListItem'; const COUNTRY_ALERTS = gql` diff --git a/src/views/Home/AlertsView/AlertsAside/CountryDetail/index.tsx b/src/views/Home/AlertsView/AlertsAside/CountryDetail/index.tsx index 4df782d6..6593198d 100644 --- a/src/views/Home/AlertsView/AlertsAside/CountryDetail/index.tsx +++ b/src/views/Home/AlertsView/AlertsAside/CountryDetail/index.tsx @@ -25,7 +25,7 @@ import { CountryDetailQueryVariables, } from '#generated/types/graphql'; -import AlertContext from '../../AlertContext'; +import AlertContext from '../../../AlertContext'; import Admin1Alerts from '../Admin1Alerts'; import AlertDetail from '../AlertDetail'; import CountryAdmin1List from '../CountryAdmin1List'; diff --git a/src/views/Home/AlertsView/AlertsAside/index.tsx b/src/views/Home/AlertsView/AlertsAside/index.tsx index a4d60f10..35790a0e 100644 --- a/src/views/Home/AlertsView/AlertsAside/index.tsx +++ b/src/views/Home/AlertsView/AlertsAside/index.tsx @@ -18,7 +18,7 @@ import { import { CountryListQuery } from '#generated/types/graphql'; import { stringIdSelector } from '#utils/selectors'; -import AlertContext from '../AlertContext'; +import AlertContext from '../../AlertContext'; import CountryDetail from './CountryDetail'; import CountryListItem from './CountryListItem'; diff --git a/src/views/Home/AlertsView/AlertsMap/index.tsx b/src/views/Home/AlertsView/AlertsMap/index.tsx index f8df1589..e87ccdfa 100644 --- a/src/views/Home/AlertsView/AlertsMap/index.tsx +++ b/src/views/Home/AlertsView/AlertsMap/index.tsx @@ -26,7 +26,7 @@ import { COLOR_PRIMARY_RED, } from '#utils/constants'; -import AlertContext from '../AlertContext'; +import AlertContext from '../../AlertContext'; import styles from './styles.module.css'; diff --git a/src/views/Home/AlertsView/AlertsMap/styles.module.css b/src/views/Home/AlertsView/AlertsMap/styles.module.css index 7124a2c4..d768c966 100644 --- a/src/views/Home/AlertsView/AlertsMap/styles.module.css +++ b/src/views/Home/AlertsView/AlertsMap/styles.module.css @@ -3,7 +3,7 @@ .map-container { flex-grow: 1; - height: 40rem; + height: 45rem; } } diff --git a/src/views/Home/AlertsView/index.tsx b/src/views/Home/AlertsView/index.tsx index a0d12ad9..8d5e8dc9 100644 --- a/src/views/Home/AlertsView/index.tsx +++ b/src/views/Home/AlertsView/index.tsx @@ -1,8 +1,4 @@ -import { - useCallback, - useMemo, - useState, -} from 'react'; +import { useMemo } from 'react'; import { Link } from 'react-router-dom'; import { gql, @@ -13,7 +9,6 @@ import { useTranslation } from '@ifrc-go/ui/hooks'; import { _cs, isDefined, - isNotDefined, } from '@togglecorp/fujs'; import { @@ -21,7 +16,7 @@ import { CountryListQueryVariables, } from '#generated/types/graphql'; -import AlertContext, { AlertContextProps } from './AlertContext'; +import useAlertFilters from '../useAlertFilters'; import AlertsAside from './AlertsAside'; import AlertsMap from './AlertsMap'; @@ -29,10 +24,10 @@ import i18n from './i18n.json'; import styles from './styles.module.css'; // NOTE: alertFilters is related with filteredAlertCount -const COUNTRIES_LIST = gql` -query CountryList { +const COUNTRY_LIST = gql` +query CountryList($alertFilters: AlertFilter) { public { - allCountries(alertFilters: {}) { + allCountries(alertFilters: $alertFilters) { name id iso3 @@ -58,72 +53,21 @@ function AlertsView(props: Props) { const { className } = props; const strings = useTranslation(i18n); - - const [activeCountryId, setActiveCountryId] = useState(undefined); - const [activeGoCountryId, setActiveGoCountryId] = useState(undefined); - const [activeAlertId, setActiveAlertId] = useState(undefined); - const [activeAdmin1Id, setActiveAdmin1Id] = useState(undefined); - const [activeGoAdmin1Id, setActiveGoAdmin1Id] = useState(undefined); + const alertFilters = useAlertFilters(); const { data: countryListResponse, loading: countryListLoading, error: countryListError, } = useQuery( - COUNTRIES_LIST, + COUNTRY_LIST, + { variables: { alertFilters } }, ); const countriesWithAlert = useMemo(() => countryListResponse?.public.allCountries.filter( (country) => (country?.filteredAlertCount ?? 0) > 0, ), [countryListResponse?.public.allCountries]); - const [bbox, setBbox] = useState(); - const [activeCountryName, setActiveCountryName] = useState(); - - const setActiveCountryIdSafe = useCallback( - (countryId: string | undefined) => { - setActiveCountryId(countryId); - setActiveCountryName(undefined); - setActiveAlertId(undefined); - setActiveAdmin1Id(undefined); - setActiveGoAdmin1Id(undefined); - setActiveGoCountryId(undefined); - if (isNotDefined(countryId)) { - setBbox(undefined); - } - }, - [], - ); - - const alertContextValue = useMemo( - () => ({ - bbox, - setBbox, - activeAlertId, - activeCountryId, - activeCountryName, - activeAdmin1Id, - activeGoAdmin1Id, - activeGoCountryId, - setActiveAlertId, - setActiveGoCountryId, - setActiveGoAdmin1Id, - setActiveCountryId: setActiveCountryIdSafe, - setActiveAdmin1Id, - setActiveCountryName, - }), - [ - bbox, - activeCountryName, - activeAlertId, - activeGoCountryId, - activeGoAdmin1Id, - activeAdmin1Id, - activeCountryId, - setActiveCountryIdSafe, - ], - ); - return ( )} + overlayPending pending={countryListLoading} errored={isDefined(countryListError)} errorMessage={countryListError?.message} contentViewType="grid" numPreferredGridContentColumns={3} > - - - - + + ); } diff --git a/src/views/Home/AlertsView/styles.module.css b/src/views/Home/AlertsView/styles.module.css index e291b1cd..58a492c4 100644 --- a/src/views/Home/AlertsView/styles.module.css +++ b/src/views/Home/AlertsView/styles.module.css @@ -8,7 +8,7 @@ } .alerts-aside { - height: 40rem; + height: 45rem; overflow: auto; } diff --git a/src/views/Home/Filters/index.tsx b/src/views/Home/Filters/index.tsx index 8cc83996..f6f8219c 100644 --- a/src/views/Home/Filters/index.tsx +++ b/src/views/Home/Filters/index.tsx @@ -1,5 +1,5 @@ import { - useCallback, + useContext, useMemo, } from 'react'; import { @@ -11,60 +11,47 @@ import { stringNameSelector } from '@ifrc-go/ui/utils'; import { isNotDefined } from '@togglecorp/fujs'; import { - AdminListQuery, AlertEnumsQuery, CountryListQuery, + FilteredAdminListQuery, } from '#generated/types/graphql'; +import { stringIdSelector } from '#utils/selectors'; -import { EntriesAsList } from '../../../types'; +import AlertContext from '../AlertContext'; import i18n from './i18n.json'; import styles from './styles.module.css'; -type CountryOption = NonNullable['allCountries']>[number]; -type AdminOption = NonNullable['admin1s']>['items']>[number]; +type AdminOption = NonNullable['admin1s']>['items']>[number]; -type Admin1 = NonNullable['admin1s']>['items']>; +type Admin1 = NonNullable['admin1s']>['items']>; type Countries = NonNullable; -type Urgency = NonNullable; -type Severity = NonNullable; -type Certainty = NonNullable; +type Urgency = NonNullable[number]; +type Severity = NonNullable[number]; +type Certainty = NonNullable[number]; interface AlertFilters { key: string; label: string; } -const countryKeySelector = (country: CountryOption) => country.id; -const countryLabelSelector = (country: CountryOption) => country.name; - const adminKeySelector = (admin1: AdminOption) => admin1.id; +const urgencyKeySelector = (urgency: Urgency) => urgency.key; +const severityKeySelector = (severity: Severity) => severity.key; +const certaintyKeySelector = (certainty: Certainty) => certainty.key; -const keySelector = (alert: AlertFilters) => alert.key; const labelSelector = (alert: AlertFilters) => alert.label; -export interface FilterValue { - countryList: string | undefined; - admin1List: string | undefined; - urgencyList: string[]; - severityList: string[]; - certaintyList: string[]; -} - interface Props { - value: FilterValue; - onChange: React.Dispatch>; admin1List?: Admin1; countryList?: Countries; - urgencyList?: Urgency; - severityList?: Severity; - certaintyList?: Certainty; + urgencyList?: Urgency[]; + severityList?: Severity[]; + certaintyList?: Certainty[]; } function Filters(props: Props) { const { - value, - onChange, countryList, admin1List, urgencyList, @@ -72,84 +59,83 @@ function Filters(props: Props) { certaintyList, } = props; + const { + activeCountryId, + activeAdmin1Id, + selectedSeverityTypes, + selectedUrgencyTypes, + selectedCertaintyTypes, + setActiveCountryId, + setActiveAdmin1Id, + setSelectedSeverityTypes, + setSelectedUrgencyTypes, + setSelectedCertaintyTypes, + } = useContext(AlertContext); + const strings = useTranslation(i18n); - const handleChange = useCallback( - (...args: EntriesAsList) => { - const [val, key] = args; - onChange((prevValue): FilterValue => ({ - ...prevValue, - [key]: val, - })); - }, - [onChange], + // TODO: this should be done in server + const admin1ListForSelectedCountry = useMemo( + () => ( + admin1List?.filter( + ({ countryId }) => countryId === activeCountryId, + ) + ), + [activeCountryId, admin1List], ); - const filteredAdmin1 = useMemo(() => { - if (isNotDefined(value.countryList) || isNotDefined(admin1List)) return admin1List; - - const selectedCountry = countryList?.find( - (country: CountryOption) => country.id === value.certaintyList, - ); - - if (isNotDefined(selectedCountry)) return admin1List; - - return admin1List?.filter( - (admin: AdminOption) => admin.countryId === selectedCountry.id, - ); - }, [ - value.countryList, - admin1List, - countryList, - value.certaintyList, - ]); - return (
- - + +
); diff --git a/src/views/Home/index.tsx b/src/views/Home/index.tsx index e180e9ec..a1794596 100644 --- a/src/views/Home/index.tsx +++ b/src/views/Home/index.tsx @@ -1,4 +1,5 @@ import { + useCallback, useMemo, useState, } from 'react'; @@ -13,20 +14,25 @@ import { Tabs, } from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; +import { isNotDefined } from '@togglecorp/fujs'; import Page from '#components/Page'; -import useInputState from '#hooks/useInputState'; import { - AdminListQuery, AlertEnumsQuery, AlertEnumsQueryVariables, + AlertInfoCertaintyEnum, + AlertInfoSeverityEnum, + AlertInfoUrgencyEnum, CountryListQuery, CountryListQueryVariables, + FilteredAdminListQuery, + FilteredAdminListQueryVariables, } from '#generated/types/graphql'; +import AlertContext, { AlertContextProps } from './AlertContext'; import AlertsTable from './AlertsTable'; import AlertsView from './AlertsView'; -import Filters, { FilterValue } from './Filters'; +import Filters from './Filters'; import i18n from './i18n.json'; import styles from './styles.module.css'; @@ -51,20 +57,22 @@ query AlertEnums { // NOTE: alertFilters is related with filteredAlertCount const COUNTRIES_LIST = gql` -query CountryList { +query CountryList($alertFilters: AlertFilter) { public { - allCountries(alertFilters: {}) { + allCountries(alertFilters: $alertFilters) { name id iso3 filteredAlertCount + ifrcGoId } } } `; +// TODO: filter this by selected country const ADMIN_LIST = gql` -query AdminList { +query FilteredAdminList { public { admin1s(filters: {}) { items { @@ -77,14 +85,6 @@ query AdminList { } `; -const defaultFilterValue: FilterValue = { - countryList: undefined, - admin1List: undefined, - urgencyList: [], - severityList: [], - certaintyList: [], -}; - type CountryOption = NonNullable['allCountries']>[number]; export type TabKeys = 'map' | 'table'; @@ -93,7 +93,26 @@ export type TabKeys = 'map' | 'table'; export function Component() { const strings = useTranslation(i18n); const [activeTab, setActiveTab] = useState('map'); - const [filters, setFilters] = useInputState(defaultFilterValue); + + const [activeCountryId, setActiveCountryId] = useState(undefined); + const [activeGoCountryId, setActiveGoCountryId] = useState(undefined); + const [activeAlertId, setActiveAlertId] = useState(undefined); + const [activeAdmin1Id, setActiveAdmin1Id] = useState(undefined); + const [activeGoAdmin1Id, setActiveGoAdmin1Id] = useState(undefined); + const [bbox, setBbox] = useState(); + const [activeCountryName, setActiveCountryName] = useState(); + const [ + selectedUrgencyTypes, + setSelectedUrgencyTypes, + ] = useState(); + const [ + selectedSeverityTypes, + setSelectedSeverityTypes, + ] = useState(); + const [ + selectedCertaintyTypes, + setSelectedCertaintyTypes, + ] = useState(); const { data: alertEnumsResponse, @@ -105,17 +124,71 @@ export function Component() { data: countryResponse, } = useQuery( COUNTRIES_LIST, + { variables: { alertFilters: {} } }, ); const { data: adminResponse, - } = useQuery( + } = useQuery( ADMIN_LIST, ); const countriesWithAlert = useMemo(() => countryResponse?.public.allCountries.filter( (country: CountryOption) => (country?.filteredAlertCount ?? 0) > 0, - ), [countryResponse?.public.allCountries]); + ), [countryResponse]); + + const setActiveCountryIdSafe = useCallback( + (countryId: string | undefined) => { + setActiveCountryId(countryId); + setActiveCountryName(undefined); + setActiveAlertId(undefined); + setActiveAdmin1Id(undefined); + setActiveGoAdmin1Id(undefined); + setActiveGoCountryId(undefined); + if (isNotDefined(countryId)) { + setBbox(undefined); + } + }, + [], + ); + + const alertContextValue = useMemo( + () => ({ + bbox, + setBbox, + activeAlertId, + activeCountryId, + activeCountryName, + activeAdmin1Id, + activeGoAdmin1Id, + activeGoCountryId, + selectedUrgencyTypes, + selectedSeverityTypes, + selectedCertaintyTypes, + setActiveAlertId, + setActiveGoCountryId, + setActiveGoAdmin1Id, + setActiveCountryId: setActiveCountryIdSafe, + setActiveAdmin1Id, + setActiveCountryName, + setSelectedCertaintyTypes, + setSelectedUrgencyTypes, + setSelectedSeverityTypes, + }), + [ + bbox, + activeCountryName, + activeAlertId, + activeGoCountryId, + activeGoAdmin1Id, + activeAdmin1Id, + activeCountryId, + selectedCertaintyTypes, + selectedUrgencyTypes, + selectedSeverityTypes, + setActiveCountryIdSafe, + ], + ); return ( - - - { strings.mapTabTitle } - - - { strings.tableTabTitle } - - - )} - > - - - - - - - - + + + + {strings.mapTabTitle} + + + {strings.tableTabTitle} + + + )} + > + + + + + + + + + ); } diff --git a/src/views/Home/useAlertFilters.ts b/src/views/Home/useAlertFilters.ts new file mode 100644 index 00000000..5e7119c5 --- /dev/null +++ b/src/views/Home/useAlertFilters.ts @@ -0,0 +1,36 @@ +import { + useContext, + useMemo, +} from 'react'; +import { isDefined } from '@togglecorp/fujs'; + +import { AlertFilter } from '#generated/types/graphql'; + +import AlertContext from './AlertContext'; + +function useAlertFilters() { + const { + selectedCertaintyTypes, + selectedSeverityTypes, + selectedUrgencyTypes, + } = useContext(AlertContext); + + const alertFilters = useMemo( + () => ({ + certainty: isDefined(selectedCertaintyTypes) && selectedCertaintyTypes.length > 0 + ? selectedCertaintyTypes + : undefined, + severity: isDefined(selectedSeverityTypes) && selectedSeverityTypes.length > 0 + ? selectedSeverityTypes + : undefined, + urgency: isDefined(selectedUrgencyTypes) && selectedUrgencyTypes.length > 0 + ? selectedUrgencyTypes + : undefined, + }), + [selectedUrgencyTypes, selectedSeverityTypes, selectedCertaintyTypes], + ); + + return alertFilters; +} + +export default useAlertFilters;