From e461c46f199ccacc769a2091785e7cf901bba018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gonz=C3=A1lez?= Date: Thu, 7 Mar 2024 16:53:35 +0100 Subject: [PATCH] starts connecting filters and fetching of filters --- client/package.json | 6 +- client/src/components/ui/calendar.tsx | 57 +++ .../filters/more-filters/component.tsx | 462 ------------------ .../filters/more-filters/index.tsx | 277 ++++++++++- .../filters/more-filters/types.d.ts | 10 - .../filters/years-range/component.tsx | 99 ---- .../filters/years-range/constants.ts | 5 - .../filters/years-range/index.ts | 1 - .../filters/years-range/index.tsx | 62 +++ .../supplier-list-table/table/index.tsx | 11 +- client/src/hooks/eudr/index.ts | 93 +++- client/src/store/features/eudr/index.ts | 32 +- client/yarn.lock | 33 +- 13 files changed, 554 insertions(+), 594 deletions(-) create mode 100644 client/src/components/ui/calendar.tsx delete mode 100644 client/src/containers/analysis-eudr/filters/more-filters/component.tsx delete mode 100644 client/src/containers/analysis-eudr/filters/more-filters/types.d.ts delete mode 100644 client/src/containers/analysis-eudr/filters/years-range/component.tsx delete mode 100644 client/src/containers/analysis-eudr/filters/years-range/constants.ts delete mode 100644 client/src/containers/analysis-eudr/filters/years-range/index.ts create mode 100644 client/src/containers/analysis-eudr/filters/years-range/index.tsx diff --git a/client/package.json b/client/package.json index 370fc9ded..a15ba91ee 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "test": "start-server-and-test 'yarn build && yarn start' http://localhost:3000/auth/signin 'nyc --reporter nyc-report-lcov-absolute yarn cypress:headless'" }, "dependencies": { + "@date-fns/utc": "1.1.1", "@deck.gl/carto": "^8.9.35", "@deck.gl/core": "8.8.6", "@deck.gl/extensions": "8.8.6", @@ -39,7 +40,7 @@ "@radix-ui/react-collapsible": "1.0.3", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "2.0.2", - "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-popover": "1.0.7", "@radix-ui/react-radio-group": "1.1.3", "@radix-ui/react-select": "2.0.0", "@radix-ui/react-slot": "1.0.2", @@ -59,7 +60,7 @@ "d3-array": "3.0.2", "d3-format": "3.0.1", "d3-scale": "4.0.2", - "date-fns": "2.22.1", + "date-fns": "3.3.1", "fuse.js": "6.4.6", "jsona": "1.9.2", "lodash-es": "4.17.21", @@ -73,6 +74,7 @@ "query-string": "8.1.0", "rc-tree": "5.7.0", "react": "18.2.0", + "react-day-picker": "8.10.0", "react-dom": "18.2.0", "react-dropzone": "14.2.2", "react-hook-form": "7.43.1", diff --git a/client/src/components/ui/calendar.tsx b/client/src/components/ui/calendar.tsx new file mode 100644 index 000000000..e7fde3fdf --- /dev/null +++ b/client/src/components/ui/calendar.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { DayPicker } from 'react-day-picker'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button'; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { + return ( + , + IconRight: ({ ...props }) => , + }} + {...props} + /> + ); +} +Calendar.displayName = 'Calendar'; + +export { Calendar }; diff --git a/client/src/containers/analysis-eudr/filters/more-filters/component.tsx b/client/src/containers/analysis-eudr/filters/more-filters/component.tsx deleted file mode 100644 index 243d2b5e8..000000000 --- a/client/src/containers/analysis-eudr/filters/more-filters/component.tsx +++ /dev/null @@ -1,462 +0,0 @@ -import React, { useCallback, useState, useMemo, useEffect } from 'react'; -import { FilterIcon } from '@heroicons/react/solid'; -import { - offset, - shift, - useClick, - useDismiss, - useFloating, - useInteractions, - FloatingPortal, -} from '@floating-ui/react'; -import { Popover, Transition } from '@headlessui/react'; -import { useRouter } from 'next/router'; - -import Materials from '@/containers/analysis-visualization/analysis-filters/materials/component'; -import OriginRegions from '@/containers/analysis-visualization/analysis-filters/origin-regions/component'; -import { flattenTree, recursiveMap, recursiveSort } from 'components/tree-select/utils'; -import Select from 'components/forms/select'; -import Button from 'components/button/component'; -import TreeSelect from 'components/tree-select'; -import { useAppDispatch, useAppSelector } from 'store/hooks'; -import { analysisFilters, setFilters } from 'store/features/analysis/filters'; -import { setFilter } from 'store/features/analysis'; -import { useMaterialsTrees } from 'hooks/materials'; -import { useAdminRegionsTrees } from 'hooks/admin-regions'; -import { useSuppliersTypes } from 'hooks/suppliers'; -import { useLocationTypes } from 'hooks/location-types'; -import { useBusinessUnitsOptionsTrees } from 'hooks/business-units'; - -import type { Option } from 'components/forms/select'; -import type { LocationTypes as LocationTyping } from 'containers/interventions/enums'; -import type { TreeSelectOption } from 'components/tree-select/types'; -import type { AnalysisFiltersState } from 'store/features/analysis/filters'; - -type MoreFiltersState = { - materials: AnalysisFiltersState['materials']; - origins: AnalysisFiltersState['origins']; - t1Suppliers: AnalysisFiltersState['t1Suppliers']; - producers: AnalysisFiltersState['producers']; - locationTypes: AnalysisFiltersState['locationTypes']; - businessUnits: AnalysisFiltersState['businessUnits']; -}; - -const INITIAL_FILTERS: MoreFiltersState = { - materials: [], - origins: [], - t1Suppliers: [], - producers: [], - locationTypes: [], - businessUnits: [], -}; - -interface ApiTreeResponse { - id: string; - name: string; - children?: this[]; -} - -const DEFAULT_QUERY_OPTIONS = { - select: (data: ApiTreeResponse[]) => { - const sorted = recursiveSort(data, 'name'); - return sorted.map((item) => recursiveMap(item, ({ id, name }) => ({ label: name, value: id }))); - }, -}; - -const MoreFilters = () => { - const { query } = useRouter(); - const { scenarioId, compareScenarioId } = query; - - const dispatch = useAppDispatch(); - const { materials, origins, t1Suppliers, producers, locationTypes, businessUnits } = - useAppSelector(analysisFilters); - - const moreFilters: MoreFiltersState = useMemo( - () => ({ materials, origins, t1Suppliers, producers, locationTypes, businessUnits }), - [materials, origins, t1Suppliers, producers, locationTypes, businessUnits], - ); - - const [selectedFilters, setSelectedFilters] = useState(moreFilters); - - const materialIds = useMemo( - () => selectedFilters.materials.map(({ value }) => value), - [selectedFilters.materials], - ); - - const originIds = useMemo( - () => selectedFilters.origins.map(({ value }) => value), - [selectedFilters.origins], - ); - - const t1SupplierIds = useMemo( - () => selectedFilters.t1Suppliers.map(({ value }) => value), - [selectedFilters.t1Suppliers], - ); - - const producerIds = useMemo( - () => selectedFilters.producers.map(({ value }) => value), - [selectedFilters.producers], - ); - - const locationTypesIds = useMemo( - () => selectedFilters.locationTypes.map(({ value }) => value), - [selectedFilters.locationTypes], - ); - - const businessUnitIds = useMemo( - () => selectedFilters.businessUnits.map(({ value }) => value), - [selectedFilters.businessUnits], - ); - - const [counter, setCounter] = useState(0); - - // Only the changes are applied when the user clicks on Apply - const handleApply = useCallback(() => { - dispatch(setFilters(selectedFilters)); - }, [dispatch, selectedFilters]); - - // Restoring state from initial state only internally, - // the user have to apply the changes - const handleClearFilters = useCallback(() => { - setSelectedFilters(INITIAL_FILTERS); - }, []); - - // Updating internal state from selectors - const handleChangeFilter = useCallback( - (key: keyof MoreFiltersState, values: TreeSelectOption[] | Option) => { - setSelectedFilters((filters) => ({ ...filters, [key]: values })); - }, - [], - ); - - useEffect(() => { - setSelectedFilters(moreFilters); - }, [moreFilters]); - - const { refs, strategy, x, y, context } = useFloating({ - // open: isOpen, - // onOpenChange: handleOpen, - placement: 'bottom-start', - strategy: 'fixed', - middleware: [offset({ mainAxis: 4 }), shift({ padding: 4 })], - }); - - const { getReferenceProps, getFloatingProps } = useInteractions([ - useClick(context), - useDismiss(context), - ]); - - const scenarioIds = useMemo( - () => [scenarioId, compareScenarioId].filter((id) => id) as string[], - [scenarioId, compareScenarioId], - ); - - const { data: materialOptions, isLoading: materialOptionsIsLoading } = useMaterialsTrees( - { - depth: 1, - withSourcingLocations: true, - scenarioIds, - originIds, - t1SupplierIds, - producerIds, - locationTypes: locationTypesIds, - businessUnitIds, - }, - { - ...DEFAULT_QUERY_OPTIONS, - select: (_materials) => - recursiveSort(_materials, 'name')?.map((item) => - recursiveMap(item, ({ id, name, status }) => ({ - value: id, - label: name, - disabled: status === 'inactive', - })), - ), - }, - ); - - const { data: originOptions, isLoading: originOptionsIsLoading } = useAdminRegionsTrees( - { - withSourcingLocations: true, - materialIds, - t1SupplierIds, - producerIds, - locationTypes: locationTypesIds, - scenarioIds, - businessUnitIds, - }, - DEFAULT_QUERY_OPTIONS, - ); - - const { data: t1SupplierOptions, isLoading: t1SupplierOptionsIsLoading } = useSuppliersTypes( - { - type: 't1supplier', - producerIds, - materialIds, - originIds, - locationTypes: locationTypesIds, - scenarioIds, - businessUnitIds, - }, - DEFAULT_QUERY_OPTIONS, - ); - - const { data: producerOptions, isLoading: producerOptionsIsLoading } = useSuppliersTypes( - { - type: 'producer', - t1SupplierIds, - materialIds, - originIds, - locationTypes: locationTypesIds, - scenarioIds, - businessUnitIds, - }, - DEFAULT_QUERY_OPTIONS, - ); - - const { data: locationTypeOptions, isLoading: locationTypeOptionsIsLoading } = useLocationTypes( - { - materialIds, - originIds, - t1SupplierIds, - producerIds, - scenarioIds, - businessUnitIds, - }, - { - onSuccess: (_locationTypeOptions) => { - // * every time new location types are fetched, we need to validate if the previous location types selected are still - // * available in the new options. Otherwise, we will remove them from the current selection. - setSelectedFilters((filters) => ({ - ...filters, - locationTypes: _locationTypeOptions.filter(({ value }) => - locationTypesIds.includes(value), - ), - })); - }, - }, - ); - - const { data: businessUnitsOptions, isLoading: businessUnitsOptionsIsLoading } = - useBusinessUnitsOptionsTrees({ - depth: 1, - withSourcingLocations: true, - materialIds, - originIds, - t1SupplierIds, - producerIds, - locationTypes: locationTypesIds, - scenarioIds, - }); - - const reviewFilterContent = useCallback( - ( - name: keyof MoreFiltersState, - currentValues: TreeSelectOption[], - allOptions: TreeSelectOption[], - ) => { - const allNodes = allOptions.flatMap((opt) => flattenTree(opt)); - const allKeys = allNodes.map(({ value }) => value); - const currentNodes = currentValues.flatMap(flattenTree); - const validOptions = currentNodes.filter(({ value }) => allKeys.includes(value)); - - if (validOptions.length !== allKeys.length) { - dispatch(setFilter({ id: name, value: validOptions })); - } - }, - [dispatch], - ); - - // Check current values are valid if the scenario changes - const handleScenarioChange = useCallback(() => { - reviewFilterContent('materials', materials, materialOptions); - reviewFilterContent('locationTypes', locationTypes, locationTypes); - reviewFilterContent('origins', origins, origins); - reviewFilterContent('t1Suppliers', t1Suppliers, t1SupplierOptions); - reviewFilterContent('producers', producers, producerOptions); - reviewFilterContent('businessUnits', businessUnits, businessUnitsOptions); - }, [ - businessUnits, - businessUnitsOptions, - locationTypes, - materialOptions, - materials, - origins, - producerOptions, - producers, - reviewFilterContent, - t1SupplierOptions, - t1Suppliers, - ]); - - useEffect(() => { - const counters = Object.values(moreFilters).map((value) => value.length); - const total = counters.reduce((a, b) => a + b); - setCounter(total); - }, [moreFilters]); - - useEffect(() => { - handleScenarioChange(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scenarioId]); - - return ( - - {({ open, close }) => ( - <> - - - - - -
-
Filter by
- -
-
-
-
Material
- handleChangeFilter('materials', values)} - id="materials-filter" - /> -
-
-
Business units
- handleChangeFilter('businessUnits', values)} - id="business-units-filter" - /> -
-
-
Origins
- handleChangeFilter('origins', values)} - id="origins-filter" - /> -
-
-
T1 Suppliers
- handleChangeFilter('t1Suppliers', values)} - id="t1-suppliers-filter" - /> -
-
-
Producers
- handleChangeFilter('producers', values)} - id="producers-filter" - /> -
-
-
Location type
- - id="location-type-filter" - multiple - loading={locationTypeOptionsIsLoading} - options={locationTypeOptions} - placeholder="Location types" - onChange={(values) => handleChangeFilter('locationTypes', values)} - value={selectedFilters.locationTypes} - /> -
-
- -
- - -
-
-
-
- - )} -
- ); -}; - -export default MoreFilters; diff --git a/client/src/containers/analysis-eudr/filters/more-filters/index.tsx b/client/src/containers/analysis-eudr/filters/more-filters/index.tsx index b404d7fd4..135628f13 100644 --- a/client/src/containers/analysis-eudr/filters/more-filters/index.tsx +++ b/client/src/containers/analysis-eudr/filters/more-filters/index.tsx @@ -1 +1,276 @@ -export { default } from './component'; +import React, { useCallback, useState, useEffect, useMemo } from 'react'; +import { FilterIcon } from '@heroicons/react/solid'; +import { + offset, + shift, + useClick, + useDismiss, + useFloating, + useInteractions, + FloatingPortal, +} from '@floating-ui/react'; +import { Popover, Transition } from '@headlessui/react'; + +import Materials from '@/containers/analysis-visualization/analysis-filters/materials/component'; +import OriginRegions from '@/containers/analysis-visualization/analysis-filters/origin-regions/component'; +import { recursiveMap, recursiveSort } from 'components/tree-select/utils'; +import Button from 'components/button/component'; +import TreeSelect from 'components/tree-select'; +import { useAppDispatch, useAppSelector } from 'store/hooks'; +import { eudr, setFilters } from 'store/features/eudr'; +import { + useEUDRMaterialsTree, + useEUDRAdminRegionsTree, + useEUDRSuppliers, + useEUDRPlotsTree, +} from 'hooks/eudr'; + +import type { EUDRState } from 'store/features/eudr'; +import type { Option } from 'components/forms/select'; +import type { TreeSelectOption } from 'components/tree-select/types'; + +type MoreFiltersState = Omit; + +const INITIAL_FILTERS: MoreFiltersState = { + materials: [], + origins: [], + suppliers: [], + plots: [], +}; + +interface ApiTreeResponse { + id: string; + name: string; + children?: this[]; +} + +const DEFAULT_QUERY_OPTIONS = { + select: (data: ApiTreeResponse[]) => { + const sorted = recursiveSort(data, 'name'); + return sorted.map((item) => recursiveMap(item, ({ id, name }) => ({ label: name, value: id }))); + }, +}; + +const MoreFilters = () => { + const dispatch = useAppDispatch(); + const { + filters: { materials, origins, suppliers, plots }, + } = useAppSelector(eudr); + + const filters = useMemo( + () => ({ + materials, + origins, + suppliers, + plots, + }), + [materials, origins, suppliers, plots], + ); + + const [selectedFilters, setSelectedFilters] = useState(filters); + const [counter, setCounter] = useState(0); + + // Only the changes are applied when the user clicks on Apply + const handleApply = useCallback(() => { + dispatch(setFilters(selectedFilters)); + }, [dispatch, selectedFilters]); + + // Restoring state from initial state only internally, + // the user have to apply the changes + const handleClearFilters = useCallback(() => { + setSelectedFilters(INITIAL_FILTERS); + }, []); + + // Updating internal state from selectors + const handleChangeFilter = useCallback( + (key: keyof MoreFiltersState, values: TreeSelectOption[] | Option) => { + setSelectedFilters((filters) => ({ ...filters, [key]: values })); + }, + [], + ); + + const { refs, strategy, x, y, context } = useFloating({ + placement: 'bottom-start', + strategy: 'fixed', + middleware: [offset({ mainAxis: 4 }), shift({ padding: 4 })], + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + useClick(context), + useDismiss(context), + ]); + + const { data: materialOptions, isLoading: materialOptionsIsLoading } = useEUDRMaterialsTree( + undefined, + { + ...DEFAULT_QUERY_OPTIONS, + select: (_materials) => { + return recursiveSort(_materials, 'name')?.map((item) => + recursiveMap(item, ({ id, name, status }) => ({ + value: id, + label: name, + disabled: status === 'inactive', + })), + ); + }, + initialData: [], + }, + ); + + const { data: originOptions, isLoading: originOptionsIsLoading } = useEUDRAdminRegionsTree( + undefined, + DEFAULT_QUERY_OPTIONS, + ); + + const { data: supplierOptions, isLoading: supplierOptionsIsLoading } = useEUDRSuppliers( + undefined, + { + ...DEFAULT_QUERY_OPTIONS, + initialData: [], + }, + ); + + const { data: plotOptions, isLoading: plotOptionsIsLoading } = useEUDRPlotsTree(undefined, { + ...DEFAULT_QUERY_OPTIONS, + initialData: [], + }); + + useEffect(() => { + const counters = Object.values(filters).map((value) => value.length); + const total = counters.reduce((a, b) => a + b); + setCounter(total); + }, [filters]); + + return ( + + {({ open, close }) => ( + <> + + + + + +
+
Filter by
+ +
+
+
+
Material
+ handleChangeFilter('materials', values)} + id="materials-filter" + /> +
+
+
Origins
+ handleChangeFilter('origins', values)} + id="origins-filter" + /> +
+
+
Suppliers
+ handleChangeFilter('suppliers', values)} + id="suppliers-filter" + /> +
+
+
Plots
+ handleChangeFilter('plots', values)} + id="plots-filter" + /> +
+
+
+ + +
+
+
+
+ + )} +
+ ); +}; + +export default MoreFilters; diff --git a/client/src/containers/analysis-eudr/filters/more-filters/types.d.ts b/client/src/containers/analysis-eudr/filters/more-filters/types.d.ts deleted file mode 100644 index 540cd7215..000000000 --- a/client/src/containers/analysis-eudr/filters/more-filters/types.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface BaseTreeSearchParams { - depth?: number; - materialIds?: string[]; - businessUnitIds?: string[]; - originIds?: string[]; - scenarioId?: string; - scenarioIds?: string[]; - producerIds?: string[]; - t1SupplierIds?: string[]; -} diff --git a/client/src/containers/analysis-eudr/filters/years-range/component.tsx b/client/src/containers/analysis-eudr/filters/years-range/component.tsx deleted file mode 100644 index 281998178..000000000 --- a/client/src/containers/analysis-eudr/filters/years-range/component.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { isFinite, toNumber, range } from 'lodash-es'; -import toast from 'react-hot-toast'; - -import { DEFAULT_END_YEAR_GAP, MAX_END_YEAR_RANGE } from './constants'; - -import { useAppDispatch, useAppSelector } from 'store/hooks'; -import { analysisUI } from 'store/features/analysis/ui'; -import { analysisFilters, setFilters } from 'store/features/analysis/filters'; -import { useYears } from 'hooks/years'; -import YearsRangeFilter, { useYearsRange } from 'containers/filters/years-range'; - -import type { YearsRangeParams } from 'containers/filters/years-range'; - -const YearsRange: React.FC = () => { - const dispatch = useAppDispatch(); - - const [years, setYears] = useState([]); - const { visualizationMode } = useAppSelector(analysisUI); - const filters = useAppSelector(analysisFilters); - const { layer, materials, indicator } = filters; - - const materialIds = useMemo(() => materials.map((mat) => mat.value), [materials]); - - const { data, isLoading } = useYears(layer, materialIds, indicator?.value, { - enabled: !!(layer === 'impact' && indicator?.value) || true, - }); - - const { startYear, endYear, yearsGap, setYearsRange } = useYearsRange({ - years, - yearsGap: 1, - // Map mode only makes use of the endYear and will display the Select, - // not the YearsRangeFilter. - validateRange: visualizationMode !== 'map', - ...filters, - }); - - const lastYearWithData = useMemo(() => data[data.length - 1], [data]); - const defaultLastYear = useMemo( - () => lastYearWithData + DEFAULT_END_YEAR_GAP, - [lastYearWithData], - ); - - useEffect(() => { - setYears(range(data[0], defaultLastYear + 1)); - }, [data, defaultLastYear]); - - useEffect(() => { - dispatch(setFilters({ startYear, endYear })); - }, [startYear, endYear, dispatch]); - - const handleOnEndYearSearch: (searchedYear: string) => void = (searchedYear) => { - const year = toNumber(searchedYear); - - if (!isFinite(year) || year <= data[0]) { - return; - } - if (year > MAX_END_YEAR_RANGE + defaultLastYear) { - toast.error(`Max year limit is ${MAX_END_YEAR_RANGE + defaultLastYear}`); - return; - } - - if (year === lastYearWithData) { - setYears(range(data[0], defaultLastYear + 1)); - } else if (!years.includes(year)) { - setYears(range(data[0], year + 1)); - } - }; - - const handleYearChange = ({ startYear, endYear }: YearsRangeParams) => { - const lastYear = years[years.length - 1]; - // Reduce the years range in case the current selected end year is smaller than the previous and the previous range was larger than the default - if (endYear < lastYear) { - if (endYear > defaultLastYear) { - setYears(range(years[0], toNumber(endYear) + 1)); - } else { - setYears(range(years[0], defaultLastYear + 1)); - } - } - if (endYear) setYearsRange({ startYear, endYear }); - }; - - return ( - - ); -}; - -export default YearsRange; diff --git a/client/src/containers/analysis-eudr/filters/years-range/constants.ts b/client/src/containers/analysis-eudr/filters/years-range/constants.ts deleted file mode 100644 index a97451003..000000000 --- a/client/src/containers/analysis-eudr/filters/years-range/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** Arbitrary value to define the end year list range */ -export const DEFAULT_END_YEAR_GAP = 5; - -/** Arbitrary value to define the max range of end year options to avoid performance issues */ -export const MAX_END_YEAR_RANGE = 1000; diff --git a/client/src/containers/analysis-eudr/filters/years-range/index.ts b/client/src/containers/analysis-eudr/filters/years-range/index.ts deleted file mode 100644 index b404d7fd4..000000000 --- a/client/src/containers/analysis-eudr/filters/years-range/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './component'; diff --git a/client/src/containers/analysis-eudr/filters/years-range/index.tsx b/client/src/containers/analysis-eudr/filters/years-range/index.tsx new file mode 100644 index 000000000..f2ebc53bf --- /dev/null +++ b/client/src/containers/analysis-eudr/filters/years-range/index.tsx @@ -0,0 +1,62 @@ +import React, { useCallback } from 'react'; +import { UTCDate } from '@date-fns/utc'; +import { format } from 'date-fns'; +import { ChevronDown } from 'lucide-react'; + +import { useAppDispatch, useAppSelector } from 'store/hooks'; +import { eudr, setFilters } from 'store/features/eudr'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; + +import type { DateRange } from 'react-day-picker'; + +// ! the date range is hardcoded for now +export const DATES_RANGE = [new UTCDate('2020-12-31'), new UTCDate()]; + +const dateFormatter = (date: Date) => format(date, 'dd/MM/yyyy'); + +const DatesRange = (): JSX.Element => { + const dispatch = useAppDispatch(); + const { + filters: { dates }, + } = useAppSelector(eudr); + + const handleDatesChange = useCallback( + (dates: DateRange) => { + if (dates) { + dispatch(setFilters({ dates })); + } + }, + [dispatch], + ); + + return ( + + + + + + + + + ); +}; + +export default DatesRange; diff --git a/client/src/containers/analysis-eudr/supplier-list-table/table/index.tsx b/client/src/containers/analysis-eudr/supplier-list-table/table/index.tsx index 55793500f..575060a0a 100644 --- a/client/src/containers/analysis-eudr/supplier-list-table/table/index.tsx +++ b/client/src/containers/analysis-eudr/supplier-list-table/table/index.tsx @@ -22,7 +22,7 @@ import { TableBody, TableCell, } from '@/components/ui/table'; -import { useEUDRSuppliers } from '@/hooks/eudr'; +// import { useEUDRSuppliers } from '@/hooks/eudr'; import type { // ColumnFiltersState, @@ -54,10 +54,11 @@ const SuppliersListTable = (): JSX.Element => { // const [columnFilters, setColumnFilters] = useState([]); const [sorting, setSorting] = useState([]); - const { data } = useEUDRSuppliers(undefined, { - enabled: false, - placeholderData: MOCK_DATA, - }); + const data = MOCK_DATA; + // const { data } = useEUDRSuppliers(undefined, { + // enabled: false, + // placeholderData: MOCK_DATA, + // }); const table = useReactTable({ data: data, diff --git a/client/src/hooks/eudr/index.ts b/client/src/hooks/eudr/index.ts index c39176d69..aa86ec2ae 100644 --- a/client/src/hooks/eudr/index.ts +++ b/client/src/hooks/eudr/index.ts @@ -2,7 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { apiService } from 'services/api'; -import type { Supplier } from '@/containers/analysis-eudr/supplier-list-table/table'; +import type { AdminRegionsTreesParams } from '@/hooks/admin-regions'; +import type { MaterialTreeItem, OriginRegion, Supplier } from '@/types'; import type { UseQueryOptions } from '@tanstack/react-query'; export const useEUDRSuppliers = ( @@ -49,3 +50,93 @@ export const usePlotGeometries = ( }, ); }; + +export const useEUDRMaterialsTree = ( + params?: { producersIds: string[]; originsId: string[]; materialsId: string[] }, + options: UseQueryOptions = {}, +) => { + return useQuery( + ['eudr-materials', params], + () => + apiService + .request<{ data: MaterialTreeItem[] }>({ + method: 'GET', + url: '/eudr/materials', + params, + }) + .then(({ data: responseData }) => responseData.data), + { + ...options, + }, + ); +}; + +export const useEUDRAdminRegionsTree = ( + params: AdminRegionsTreesParams, + options: UseQueryOptions = {}, +) => { + const query = useQuery( + ['eudr-admin-regions', params], + () => + apiService + .request<{ data: OriginRegion[] }>({ + method: 'GET', + url: '/eudr/admin-regions', + params, + }) + .then(({ data: responseData }) => responseData.data), + { + ...options, + }, + ); + + return query; +}; + +export const useEUDRPlotsTree = ( + params: AdminRegionsTreesParams, + options: UseQueryOptions = {}, +) => { + const query = useQuery( + ['eudr-geo-regions', params], + () => + apiService + .request<{ data: OriginRegion[] }>({ + method: 'GET', + url: '/eudr/geo-regions', + params, + }) + .then(({ data: responseData }) => responseData.data), + { + ...options, + }, + ); + + return query; +}; + +interface Alert { + alertDate: { + value: string; + }; +} + +export const useEUDRAlertDates = ( + params?: { producersIds: string[]; originsId: string[]; materialsId: string[] }, + options: UseQueryOptions = {}, +) => { + return useQuery( + ['eudr-dates', params], + () => + apiService + .request({ + method: 'GET', + url: '/eudr/dates', + params, + }) + .then(({ data: responseData }) => responseData.data), + { + ...options, + }, + ); +}; diff --git a/client/src/store/features/eudr/index.ts b/client/src/store/features/eudr/index.ts index 5b0a934d0..d90397b8e 100644 --- a/client/src/store/features/eudr/index.ts +++ b/client/src/store/features/eudr/index.ts @@ -1,15 +1,38 @@ import { createSlice } from '@reduxjs/toolkit'; +import { DATES_RANGE } from 'containers/analysis-eudr/filters/years-range'; + +import type { Option } from '@/components/forms/select'; import type { VIEW_BY_OPTIONS } from 'containers/analysis-eudr/suppliers-stacked-bar'; import type { PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from 'store'; export type EUDRState = { viewBy: (typeof VIEW_BY_OPTIONS)[number]['value']; + filters: { + materials: Option[]; + origins: Option[]; + plots: Option[]; + suppliers: Option[]; + dates: { + from: Date; + to: Date; + }; + }; }; export const initialState: EUDRState = { viewBy: 'commodities', + filters: { + materials: [], + origins: [], + plots: [], + suppliers: [], + dates: { + from: DATES_RANGE[0], + to: DATES_RANGE[1], + }, + }, }; export const EUDRSlice = createSlice({ @@ -20,10 +43,17 @@ export const EUDRSlice = createSlice({ ...state, viewBy: action.payload, }), + setFilters: (state, action: PayloadAction>) => ({ + ...state, + filters: { + ...state.filters, + ...action.payload, + }, + }), }, }); -export const { setViewBy } = EUDRSlice.actions; +export const { setViewBy, setFilters } = EUDRSlice.actions; export const eudr = (state: RootState) => state['eudr']; diff --git a/client/yarn.lock b/client/yarn.lock index 98c5d3880..f24bb8aa1 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -326,6 +326,13 @@ __metadata: languageName: node linkType: hard +"@date-fns/utc@npm:1.1.1": + version: 1.1.1 + resolution: "@date-fns/utc@npm:1.1.1" + checksum: 8369b27dce2a248d102f4a4756a54263b544a448da6ae621de7951c05e6e66495e38d2bfade103dbb38210cdb1a42faa5741f498404eb965eb49373e78c75d43 + languageName: node + linkType: hard + "@deck.gl/carto@npm:^8.9.35": version: 8.9.35 resolution: "@deck.gl/carto@npm:8.9.35" @@ -2086,7 +2093,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-popover@npm:^1.0.7": +"@radix-ui/react-popover@npm:1.0.7": version: 1.0.7 resolution: "@radix-ui/react-popover@npm:1.0.7" dependencies: @@ -4745,10 +4752,10 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:2.22.1": - version: 2.22.1 - resolution: "date-fns@npm:2.22.1" - checksum: 7ff97cd605af50c02f341687c2cafd218839a1aace67965374989855a13f76dc4fe52e0e38c343c1ad1f8399787cb6839a0b14a669c44b30550c287300b1bb50 +"date-fns@npm:3.3.1": + version: 3.3.1 + resolution: "date-fns@npm:3.3.1" + checksum: 6245e93a47de28ac96dffd4d62877f86e6b64854860ae1e00a4f83174d80bc8e59bd1259cf265223fb2ddce5c8e586dc9cc210f0d052faba2f7660e265877283 languageName: node linkType: hard @@ -7778,6 +7785,7 @@ __metadata: version: 0.0.0-use.local resolution: "landgriffon-client@workspace:." dependencies: + "@date-fns/utc": 1.1.1 "@deck.gl/carto": ^8.9.35 "@deck.gl/core": 8.8.6 "@deck.gl/extensions": 8.8.6 @@ -7802,7 +7810,7 @@ __metadata: "@radix-ui/react-collapsible": 1.0.3 "@radix-ui/react-dropdown-menu": ^2.0.6 "@radix-ui/react-label": 2.0.2 - "@radix-ui/react-popover": ^1.0.7 + "@radix-ui/react-popover": 1.0.7 "@radix-ui/react-radio-group": 1.1.3 "@radix-ui/react-select": 2.0.0 "@radix-ui/react-slot": 1.0.2 @@ -7833,7 +7841,7 @@ __metadata: d3-array: 3.0.2 d3-format: 3.0.1 d3-scale: 4.0.2 - date-fns: 2.22.1 + date-fns: 3.3.1 eslint: 8.23.1 eslint-config-next: 13.5.5 eslint-config-prettier: ^9.1.0 @@ -7858,6 +7866,7 @@ __metadata: query-string: 8.1.0 rc-tree: 5.7.0 react: 18.2.0 + react-day-picker: 8.10.0 react-dom: 18.2.0 react-dropzone: 14.2.2 react-hook-form: 7.43.1 @@ -9762,6 +9771,16 @@ __metadata: languageName: node linkType: hard +"react-day-picker@npm:8.10.0": + version: 8.10.0 + resolution: "react-day-picker@npm:8.10.0" + peerDependencies: + date-fns: ^2.28.0 || ^3.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: a265e8c2f3f0e92e5a23e2edeca40fe67c67c00bae64aa9e1a99c6fe7f58b2f697b538937822b8f68d4b890d7903a06f074f1365deb465a0aeef1a14c701cc65 + languageName: node + linkType: hard + "react-dom@npm:18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0"