Skip to content

Commit e16a507

Browse files
Merge pull request #56 from Vizzuality/develop
Update staging
2 parents b72d0e0 + 918af38 commit e16a507

File tree

17 files changed

+368
-48
lines changed

17 files changed

+368
-48
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
"use client";
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from "@/components/ui/select";
9+
import { useSyncLayers, useSyncLayersSettings } from "@/store/map";
10+
import { DefaultLayerComponent } from "@/types/generated/strapi.schemas";
11+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
12+
import { CalendarDaysIcon } from "lucide-react";
13+
import { useTranslations } from "next-intl";
14+
import { useEffect, useMemo, useState } from "react";
15+
16+
type TemporalDatasetProps = {
17+
layers: DefaultLayerComponent[];
18+
};
19+
20+
type TemporalDatasetItemProps = {
21+
layer: DefaultLayerComponent;
22+
};
23+
24+
const isCorrectTimeSelect = (timeSelect: unknown): timeSelect is [number, number] => {
25+
return (
26+
Array.isArray(timeSelect) &&
27+
timeSelect.length === 2 &&
28+
timeSelect.every((t) => typeof t === "number")
29+
);
30+
};
31+
32+
type TemporalDatasetItemSelectProps = {
33+
value?: number | undefined;
34+
disabled?: boolean;
35+
onChange?: (value: string) => void;
36+
defaultValue?: number;
37+
options: { value: number; disabled: boolean }[] | undefined;
38+
};
39+
const TemporalDatasetItemSelect = ({
40+
value,
41+
disabled,
42+
defaultValue,
43+
onChange,
44+
options,
45+
}: TemporalDatasetItemSelectProps) => {
46+
return (
47+
<Select
48+
disabled={disabled}
49+
defaultValue={`${defaultValue}`}
50+
value={`${value}`}
51+
onValueChange={onChange}
52+
>
53+
<SelectTrigger>
54+
<div className="flex gap-2">
55+
<CalendarDaysIcon className="h-5 w-5 text-foreground" />
56+
<SelectValue aria-label={`${value}`}>{value || defaultValue}</SelectValue>
57+
</div>
58+
</SelectTrigger>
59+
<SelectContent>
60+
{options?.map((option) => (
61+
<SelectItem disabled={option.disabled} key={option.value} value={`${option.value}`}>
62+
{option.value}
63+
</SelectItem>
64+
))}
65+
</SelectContent>
66+
</Select>
67+
);
68+
};
69+
70+
const _getOptions = (params_config: unknown, start?: number, end?: number) => {
71+
const timeSelect = (params_config as Record<string, unknown>[])?.find(
72+
(p) => p.key === "time-select",
73+
)?.default;
74+
75+
return isCorrectTimeSelect(timeSelect)
76+
? Array.from({ length: timeSelect[1] - timeSelect[0] + 1 }, (_, i) => {
77+
const value = timeSelect[0] + i;
78+
return {
79+
value,
80+
disabled: (!!start && value <= Number(start)) || (!!end && value >= Number(end)),
81+
};
82+
})
83+
: [];
84+
};
85+
86+
const selectTypes = ["absolute", "changes"] as const;
87+
type SelectType = (typeof selectTypes)[number];
88+
89+
const TemporalDatasetItem = ({ layer }: TemporalDatasetItemProps) => {
90+
const t = useTranslations();
91+
92+
const [selectType, setSelectedType] = useState<SelectType>("absolute");
93+
94+
const [layersSettings, setLayersSettings] = useSyncLayersSettings();
95+
const [layers] = useSyncLayers();
96+
97+
const { defaultStartYear, layerSlug } = useMemo(() => {
98+
const defaultStartYear = (
99+
layer?.layer?.data?.attributes?.params_config as Record<string, unknown>[]
100+
)?.find((p) => p.key === "startYear")?.default as number | undefined;
101+
102+
const layerSlug = layer?.layer?.data?.attributes?.slug;
103+
return { defaultStartYear, layerSlug };
104+
}, [layer?.layer?.data?.attributes]);
105+
106+
const { startYear, endYear, isDisabled } = useMemo(() => {
107+
const startYear = !!layerSlug
108+
? (layersSettings?.[layerSlug]?.startYear as number | undefined)
109+
: undefined;
110+
const endYear =
111+
selectType === "changes" && !!layerSlug
112+
? (layersSettings?.[layerSlug]?.endYear as number | undefined)
113+
: undefined;
114+
const isDisabled = !layerSlug || !layers?.includes(layerSlug);
115+
116+
return { startYear, endYear, isDisabled };
117+
}, [layerSlug, layers, layersSettings]);
118+
119+
const startYearOptions = useMemo(
120+
() => _getOptions(layer?.layer?.data?.attributes?.params_config, undefined, endYear),
121+
[layer, endYear],
122+
);
123+
const endYearOptions = useMemo(
124+
() => _getOptions(layer?.layer?.data?.attributes?.params_config, startYear),
125+
[layer, startYear],
126+
);
127+
128+
const defaultEndYear = useMemo(() => {
129+
return endYearOptions?.[endYearOptions.length - 1]?.value;
130+
}, [endYearOptions]);
131+
132+
const handleChangeValue = (value: string, key: string) => {
133+
setLayersSettings((prev) => {
134+
if (!layerSlug) return prev;
135+
return {
136+
...prev,
137+
[layerSlug]: {
138+
...(prev ? prev[layerSlug] : {}),
139+
[key]: value,
140+
},
141+
};
142+
});
143+
};
144+
145+
const handleSelectType = (value: SelectType) => {
146+
setSelectedType(value);
147+
};
148+
149+
useEffect(() => {
150+
if (selectType === "absolute") {
151+
setLayersSettings((prev) => {
152+
if (!layerSlug) return prev;
153+
return {
154+
...prev,
155+
[layerSlug]: {
156+
...(prev ? prev[layerSlug] : {}),
157+
endYear: 0,
158+
},
159+
};
160+
});
161+
} else {
162+
setLayersSettings((prev) => {
163+
if (!layerSlug) return prev;
164+
return {
165+
...prev,
166+
[layerSlug]: {
167+
...(prev ? prev[layerSlug] : {}),
168+
endYear: defaultEndYear,
169+
},
170+
};
171+
});
172+
}
173+
}, [selectType, layerSlug]);
174+
175+
return (
176+
<div className="space-y-4">
177+
<RadioGroup
178+
className="flex gap-4 text-xs"
179+
onValueChange={handleSelectType}
180+
disabled={isDisabled}
181+
defaultValue={selectType}
182+
>
183+
<div className="flex gap-2">
184+
<RadioGroupItem
185+
id="dataset-absolute"
186+
className="flex h-4 w-4 items-center justify-center rounded-full border border-foreground"
187+
value={selectTypes[0]}
188+
/>
189+
<label htmlFor="dataset-absolute" className="flex items-center gap-2">
190+
{t("See absolute value")}
191+
</label>
192+
</div>
193+
<div className="flex gap-2">
194+
<RadioGroupItem
195+
id="dataset-changes"
196+
className="flex h-4 w-4 items-center justify-center rounded-full border border-foreground"
197+
value={selectTypes[1]}
198+
/>
199+
<label htmlFor="dataset-changes" className="flex items-center gap-2">
200+
{t("See changes over time")}
201+
</label>
202+
</div>
203+
</RadioGroup>
204+
<div className="flex gap-2 text-xs">
205+
<div className="flex-1 space-y-1">
206+
{selectType === "changes" && <span>{t("Start date")}</span>}
207+
<TemporalDatasetItemSelect
208+
onChange={(v) => handleChangeValue(v, "startYear")}
209+
options={startYearOptions}
210+
disabled={isDisabled}
211+
value={startYear}
212+
defaultValue={defaultStartYear}
213+
/>
214+
</div>
215+
{selectType === "changes" && (
216+
<div className="flex-1 space-y-1">
217+
<span>{t("End date")}</span>
218+
<TemporalDatasetItemSelect
219+
onChange={(v) => handleChangeValue(v, "endYear")}
220+
options={endYearOptions}
221+
disabled={isDisabled}
222+
value={endYear}
223+
defaultValue={defaultEndYear}
224+
/>
225+
</div>
226+
)}
227+
</div>
228+
</div>
229+
);
230+
};
231+
232+
const TemporalChangesDataset = ({ layers }: TemporalDatasetProps) => {
233+
return layers?.map((layer) => {
234+
if (!layer?.layer?.data?.attributes?.slug) return null;
235+
return (
236+
<div key={layer.id} className="space-y-2">
237+
{layers.length > 1 && <h2>{layer.name}</h2>}
238+
<TemporalDatasetItem layer={layer} />
239+
</div>
240+
);
241+
});
242+
};
243+
244+
export default TemporalChangesDataset;

client/src/containers/datasets/item.tsx

+17-11
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { DatasetListResponseDataItem } from "@/types/generated/strapi.schemas";
55
import { useSyncDatasets, useSyncLayers, useSyncLayersSettings } from "@/store/map";
66
import { Switch } from "@/components/ui/switch";
77
import CitationsIcon from "@/svgs/citations.svg";
8-
import { cn } from "@/lib/utils";
8+
import { cn, getLayerSettings } from "@/lib/utils";
99
import { useTranslations } from "next-intl";
1010
import { RANGELAND_DATASET_SLUG } from "./constants";
1111
import { useMemo } from "react";
1212
import { LayerVisibility } from "@/components/map/legends/header/buttons";
1313
import GroupDataset from "./components/group";
1414
import TemporalDataset from "./components/temporal";
1515
import DatasetInfo from "./info";
16+
import TemporalChangesDataset from "./components/temporal-changes";
1617

1718
type DatasetsItemProps = DatasetListResponseDataItem & {
1819
className?: string;
@@ -73,13 +74,21 @@ const DatasetsItem = ({ attributes, className }: DatasetsItemProps) => {
7374
};
7475

7576
const datasetVisibility = useMemo(() => {
76-
if (!!datasetLayer?.slug) {
77-
return (
78-
!layersSettings?.[datasetLayer?.slug] ||
79-
(layersSettings?.[datasetLayer?.slug]?.visibility as boolean)
80-
);
77+
return getLayerSettings(datasetLayer, layersSettings)?.visibility;
78+
}, [datasetLayer, layersSettings]);
79+
80+
const COMPONENT = useMemo(() => {
81+
switch (attributes?.type) {
82+
case "Group":
83+
return <GroupDataset layers={attributes?.layers} slug={attributes?.slug} />;
84+
case "Temporal":
85+
return <TemporalDataset layers={attributes?.layers} />;
86+
case "Temporal-changes":
87+
return <TemporalChangesDataset layers={attributes?.layers} />;
88+
default:
89+
return null;
8190
}
82-
}, [datasetLayer?.slug, layersSettings]);
91+
}, [attributes?.type, attributes?.layers, attributes?.slug]);
8392

8493
return (
8594
<div className={cn("space-y-6", className)}>
@@ -131,10 +140,7 @@ const DatasetsItem = ({ attributes, className }: DatasetsItemProps) => {
131140
</div>
132141
</div>
133142

134-
{attributes?.type === "Group" && (
135-
<GroupDataset layers={attributes?.layers} slug={attributes?.slug} />
136-
)}
137-
{attributes?.type === "Temporal" && <TemporalDataset layers={attributes?.layers} />}
143+
{COMPONENT}
138144
</div>
139145
);
140146
};

client/src/containers/map/legends/item.tsx

+2-23
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import BasicLegend from "@/components/map/legends/content/basic";
99
import GradientLegend from "@/components/map/legends/content/gradient";
1010
import RangelandLegend from "@/components/map/legends/content/rangeland";
1111
import { LegendComponent } from "@/components/map/types";
12-
import { ParamsConfig } from "@/types/layers";
12+
import { getLayerSettings } from "@/lib/utils";
1313

1414
const LEGEND_CONTENT = {
1515
Basic: BasicLegend,
@@ -55,28 +55,7 @@ const LegendItem = ({ dataset }: LegendItemProps) => {
5555
}, [datasetLayer]);
5656

5757
const settings = useMemo(() => {
58-
const layerSettings =
59-
(!!datasetLayer?.layer?.data?.attributes?.slug &&
60-
layersSettings?.[datasetLayer?.layer?.data?.attributes?.slug]) ||
61-
{};
62-
63-
const params =
64-
(datasetLayer?.layer?.data?.attributes?.params_config as ParamsConfig)?.reduce(
65-
(acc, curr) => {
66-
if (curr.key === "colors") return acc;
67-
return {
68-
...acc,
69-
[curr.key]: curr.default,
70-
};
71-
},
72-
{},
73-
) || {};
74-
75-
const currSettings = Object.assign({}, params, layerSettings);
76-
return {
77-
visibility: (currSettings.visibility as boolean) ?? true,
78-
opacity: (currSettings.opacity as number) ?? 1,
79-
};
58+
return getLayerSettings(datasetLayer?.layer?.data?.attributes, layersSettings);
8059
}, [layersSettings, datasetLayer]);
8160

8261
const setLayerSettings = (key: string, value: boolean | number) => {

client/src/lib/json-converter/utils/setters.ts

+24
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@ const setRasterTiles = ({ src, searchparams = {} }: SetRasterTilesProps) => {
9595
return `${src}?${searchParams.toString()}`;
9696
};
9797

98+
type SetTimeChangeRasterTilesProps = {
99+
src: string;
100+
searchparams?: Record<string, string | number>;
101+
tilesetSingle?: string;
102+
tilesetChange?: string;
103+
};
104+
const setTimeChangeRasterTiles = ({
105+
src,
106+
searchparams = {},
107+
tilesetChange = "",
108+
tilesetSingle = "",
109+
}: SetTimeChangeRasterTilesProps) => {
110+
const searchParams = new URLSearchParams();
111+
Object.entries(searchparams).forEach(([key, value]) => {
112+
if (value) {
113+
searchParams.append(key, value as string);
114+
}
115+
});
116+
searchParams.append("tileset", searchparams.endYear ? tilesetChange : tilesetSingle);
117+
118+
return `${src}?${searchParams.toString()}`;
119+
};
120+
98121
type SetRangelandsColorProps = {
99122
colors: Record<string, string | number[]>;
100123
property: string;
@@ -121,6 +144,7 @@ const SETTERS = {
121144
setColor,
122145
setDataWithMapboxToken,
123146
setRasterTiles,
147+
setTimeChangeRasterTiles,
124148
setRangelandsColor,
125149
setFilterCategories,
126150
} as const;

0 commit comments

Comments
 (0)