From 6a035b158ff0dd40e4f71513accc120eddb8e0bb Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Mon, 3 Jun 2024 10:04:34 +0200 Subject: [PATCH] feat: Ability to update temporal shared filter --- app/charts/shared/chart-data-filters.tsx | 8 +- .../components/field-date-picker.tsx | 2 +- .../components/layout-configurator.tsx | 263 +++++++++++++++--- .../configurator-state/actions.tsx | 4 + .../configurator-state/reducer.tsx | 14 + 5 files changed, 245 insertions(+), 46 deletions(-) diff --git a/app/charts/shared/chart-data-filters.tsx b/app/charts/shared/chart-data-filters.tsx index d4b0a1a0f3..90ad483aa9 100644 --- a/app/charts/shared/chart-data-filters.tsx +++ b/app/charts/shared/chart-data-filters.tsx @@ -341,7 +341,7 @@ const DataFilter = (props: DataFilterProps) => { ); }; -type DataFilterGenericDimensionProps = { +export type DataFilterGenericDimensionProps = { dimension: Dimension; value: string; onChange: (e: SelectChangeEvent) => void; @@ -349,7 +349,9 @@ type DataFilterGenericDimensionProps = { disabled: boolean; }; -const DataFilterGenericDimension = (props: DataFilterGenericDimensionProps) => { +export const DataFilterGenericDimension = ( + props: DataFilterGenericDimensionProps +) => { const { dimension, value, onChange, options: propOptions, disabled } = props; const { label, isKeyDimension } = dimension; const noneLabel = t({ @@ -466,7 +468,7 @@ const DataFilterTemporalDimension = ({ ) => void; disabled: boolean; }) => { - const { label, values, timeUnit, timeFormat } = dimension; + const { label, timeUnit, timeFormat } = dimension; const formatLocale = useTimeFormatLocale(); const formatDate = formatLocale.format(timeFormat); const parseDate = formatLocale.parse(timeFormat); diff --git a/app/configurator/components/field-date-picker.tsx b/app/configurator/components/field-date-picker.tsx index 40a1114a4c..b75877d0fd 100644 --- a/app/configurator/components/field-date-picker.tsx +++ b/app/configurator/components/field-date-picker.tsx @@ -8,7 +8,7 @@ import { Label } from "@/components/form"; import { TimeUnit } from "@/graphql/resolver-types"; import { Icon } from "@/icons"; -type DatePickerFieldProps = { +export type DatePickerFieldProps = { name: string; label?: React.ReactNode; value: Date; diff --git a/app/configurator/components/layout-configurator.tsx b/app/configurator/components/layout-configurator.tsx index 9d1a6111e5..d29fcb19be 100644 --- a/app/configurator/components/layout-configurator.tsx +++ b/app/configurator/components/layout-configurator.tsx @@ -1,4 +1,4 @@ -import { Trans } from "@lingui/macro"; +import { Trans, t } from "@lingui/macro"; import { Box, FormControlLabel, @@ -14,6 +14,8 @@ import omit from "lodash/omit"; import uniqBy from "lodash/uniqBy"; import { useMemo } from "react"; +import { DataFilterGenericDimensionProps } from "@/charts/shared/chart-data-filters"; +import { Select } from "@/components/form"; import { generateLayout } from "@/components/react-grid"; import { ChartConfig, LayoutDashboard } from "@/config-types"; import { LayoutAnnotator } from "@/configurator/components/annotators"; @@ -22,17 +24,32 @@ import { ControlSectionContent, SubsectionTitle, } from "@/configurator/components/chart-controls/section"; +import { + DatePickerField, + DatePickerFieldProps, + canRenderDatePickerField, +} from "@/configurator/components/field-date-picker"; import { IconButton } from "@/configurator/components/icon-button"; -import { timeUnitToFormatter } from "@/configurator/components/ui-helpers"; +import { + extractDataPickerOptionsFromDimension, + timeUnitToFormatter, +} from "@/configurator/components/ui-helpers"; import { isLayouting, useConfiguratorState, } from "@/configurator/configurator-state"; -import { isTemporalDimension } from "@/domain/data"; +import { + Dimension, + TemporalDimension, + isTemporalDimension, +} from "@/domain/data"; import { useTimeFormatLocale, useTimeFormatUnit } from "@/formatters"; import { useDataCubesComponentsQuery } from "@/graphql/hooks"; import { useLocale } from "@/src"; -import { useDashboardInteractiveFilters } from "@/stores/interactive-filters"; +import { + SharedFilter, + useDashboardInteractiveFilters, +} from "@/stores/interactive-filters"; import { getTimeFilterOptions } from "@/utils/time-filter-options"; export const LayoutConfigurator = () => { @@ -187,49 +204,50 @@ const LayoutSharedFiltersConfigurator = () => { {potentialSharedFilters.map((filter) => { const dimension = dimensionsByIri[filter.componentIri]; const sharedFilter = sharedFiltersByIri[filter.componentIri]; - console.log( - dimension, - sharedFilter, - filter.componentIri, - sharedFiltersByIri - ); + if (!dimension || !isTemporalDimension(dimension)) { return null; } return ( - - - {dimension.label || filter.componentIri} - - - - Shared - - - } - control={ - - } + <> + + + {dimension.label || filter.componentIri} + + + + Shared + + + } + control={ + + } + /> + + - + ); })} @@ -240,6 +258,167 @@ const LayoutSharedFiltersConfigurator = () => { } }; +const SharedFilterOptions = ({ + sharedFilter, + dimension, +}: { + sharedFilter: SharedFilter; + dimension: Dimension; +}) => { + if (!sharedFilter) { + return null; + } + + if (sharedFilter.type !== "timeRange") { + return null; + } + if ( + !isTemporalDimension(dimension) || + !canRenderDatePickerField(dimension.timeUnit) + ) { + return null; + } + + return ( + + ); +}; + +const SharedFilterOptionsTimeRange = ({ + sharedFilter, + dimension, +}: { + sharedFilter: SharedFilter; + dimension: TemporalDimension; +}) => { + const { timeUnit, timeFormat } = dimension; + const formatLocale = useTimeFormatLocale(); + const formatDate = formatLocale.format(timeFormat); + const parseDate = formatLocale.parse(timeFormat); + const [, dispatch] = useConfiguratorState(); + + const { minDate, maxDate, optionValues, options } = useMemo(() => { + return extractDataPickerOptionsFromDimension({ + dimension, + parseDate, + }); + }, [dimension, parseDate]); + + const handleChangeFromDate: DatePickerFieldProps["onChange"] = (ev) => + dispatch({ + type: "DASHBOARD_FILTER_UPDATE", + value: { + ...sharedFilter, + presets: { + ...sharedFilter.presets, + from: formatDate(new Date(ev.target.value)), + }, + }, + }); + + const handleChangeFromGeneric: DataFilterGenericDimensionProps["onChange"] = ( + ev + ) => + dispatch({ + type: "DASHBOARD_FILTER_UPDATE", + value: { + ...sharedFilter, + presets: { + ...sharedFilter.presets, + from: ev.target.value as string, + }, + }, + }); + + const handleChangeToDate: DatePickerFieldProps["onChange"] = (ev) => + dispatch({ + type: "DASHBOARD_FILTER_UPDATE", + value: { + ...sharedFilter, + presets: { + ...sharedFilter.presets, + to: formatDate(new Date(ev.target.value)), + }, + }, + }); + + const handleChangeToGeneric: DataFilterGenericDimensionProps["onChange"] = ( + ev + ) => + dispatch({ + type: "DASHBOARD_FILTER_UPDATE", + value: { + ...sharedFilter, + presets: { + ...sharedFilter.presets, + to: ev.target.value as string, + }, + }, + }); + + return ( + + + - + + } + > + {canRenderDatePickerField(timeUnit) ? ( + !optionValues.includes(formatDate(d))} + timeUnit={timeUnit} + dateFormat={formatDate} + minDate={minDate} + maxDate={maxDate} + disabled={false} + label={t({ id: "controls.filters.select.from", message: "From" })} + /> + ) : ( + + )} + + + ); +}; + type LayoutButtonProps = { type: LayoutDashboard["layout"]; layout: LayoutDashboard; diff --git a/app/configurator/configurator-state/actions.tsx b/app/configurator/configurator-state/actions.tsx index 5a736191bb..9aa4feaaaf 100644 --- a/app/configurator/configurator-state/actions.tsx +++ b/app/configurator/configurator-state/actions.tsx @@ -276,4 +276,8 @@ export type ConfiguratorStateAction = | { type: "DASHBOARD_FILTER_REMOVE"; value: DashboardFiltersConfig["filters"][number]["componentIri"]; + } + | { + type: "DASHBOARD_FILTER_UPDATE"; + value: DashboardFiltersConfig["filters"][number]; }; diff --git a/app/configurator/configurator-state/reducer.tsx b/app/configurator/configurator-state/reducer.tsx index a3962e51a1..c2c6b64a41 100644 --- a/app/configurator/configurator-state/reducer.tsx +++ b/app/configurator/configurator-state/reducer.tsx @@ -1064,6 +1064,20 @@ export const reducer_: Reducer = ( return draft; + case "DASHBOARD_FILTER_UPDATE": + if (isLayouting(draft)) { + const idx = draft.dashboardFilters?.filters.findIndex( + (f) => f.componentIri === action.value.componentIri + ); + + if (idx !== undefined && idx > -1) { + const newFilters = [...(draft.dashboardFilters?.filters ?? [])]; + newFilters.splice(idx, 1, action.value); + setWith(draft, "dashboardFilters.filters", newFilters, Object); + } + } + return draft; + case "DASHBOARD_FILTER_REMOVE": if (isLayouting(draft)) { const newFilters = draft.dashboardFilters?.filters.filter(