Refactor stores; assemble filters store

dev
Platon Yasev 3 years ago
parent 225af8fef3
commit 9390b15d81

@ -28,17 +28,19 @@
"react-map-gl": "^7.0.19", "react-map-gl": "^7.0.19",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.8.1",
"tailwind-merge": "^1.7.0", "tailwind-merge": "^1.7.0",
"typescript": "^4.9.5",
"vite-plugin-svgr": "^2.4.0", "vite-plugin-svgr": "^2.4.0",
"wellknown": "^0.5.0", "wellknown": "^0.5.0",
"zustand": "^4.1.3" "zustand": "^4.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.22", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.7", "@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^2.2.0", "@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"less": "^4.1.3", "less": "^4.1.3",
"postcss": "^8.4.18", "postcss": "^8.4.18",
"simple-zustand-devtools": "^1.1.0",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.2.1",
"vite": "^3.2.0" "vite": "^3.2.0"
} }

@ -6,11 +6,19 @@ import { RegisterPage } from "./pages/Register";
import { MapPage } from "./pages/Map"; import { MapPage } from "./pages/Map";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { enableMapSet } from "immer"; import { enableMapSet } from "immer";
import { mountStoreDevtool } from "simple-zustand-devtools";
import { useFilters } from "./stores/useFilters";
import { usePointSelection } from "./stores/usePointSelection";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
enableMapSet(); enableMapSet();
if (import.meta.env.MODE === "development") {
mountStoreDevtool("Filters", useFilters);
mountStoreDevtool("PointSelection", usePointSelection);
}
function App() { function App() {
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>

@ -1,130 +0,0 @@
import { Layer, Source } from "react-map-gl";
import { gridLayer } from "./layers-config";
import { useGridSize } from "../stores/useGridSize";
import { useLayersVisibility } from "../stores/useLayersVisibility";
import { useMemo } from "react";
import { useRateExpression } from "./useRateExpression";
import { useModel } from "../stores/useModel";
const useGridColorScale = () => {
const rate = useRateExpression();
const { model } = useModel();
if (model === "ml") {
return [
"interpolate",
["linear"],
rate,
0,
"rgba(255,0,0,0.78)",
10,
"rgb(255,137,52)",
20,
"rgb(255,197,52)",
30,
"rgb(233,250,0)",
40,
"rgb(92,164,3)",
80,
"rgb(7,112,3)",
100,
"rgb(2,72,1)",
];
}
return [
"interpolate",
["linear"],
rate,
0,
"rgb(204,34,34)",
10,
"rgb(255,221,52)",
20,
"rgb(30,131,42)",
];
};
export const Grid = ({ rate: rateRange }) => {
const { gridSize } = useGridSize();
const {
isVisible: { grid },
} = useLayersVisibility();
const rate = useRateExpression();
const colorScale = useGridColorScale();
const filter = useMemo(() => {
return ["all", [">=", rate, rateRange[0]], ["<=", rate, rateRange[1]]];
}, [rate, rateRange]);
const paintConfig = {
...gridLayer.paint,
"fill-color": colorScale,
};
return (
<>
<Source
id="grid3"
type="vector"
tiles={[
`https://postamates.spatiality.website/martin/public.net_3/{z}/{x}/{y}.pbf`,
]}
>
<Layer
{...gridLayer}
id={"grid3"}
source={"grid3"}
source-layer={"public.net_3"}
layout={{
...gridLayer.layout,
visibility: grid && gridSize === "net_3" ? "visible" : "none",
}}
paint={paintConfig}
filter={filter}
/>
</Source>
<Source
id="grid4"
type="vector"
tiles={[
`https://postamates.spatiality.website/martin/public.net_4/{z}/{x}/{y}.pbf`,
]}
>
<Layer
{...gridLayer}
id={"grid4"}
source={"grid4"}
source-layer={"public.net_4"}
layout={{
...gridLayer.layout,
visibility: grid && gridSize === "net_4" ? "visible" : "none",
}}
filter={filter}
paint={paintConfig}
/>
</Source>
<Source
id="grid5"
type="vector"
tiles={[
`https://postamates.spatiality.website/martin/public.net_5/{z}/{x}/{y}.pbf`,
]}
>
<Layer
{...gridLayer}
id={"grid5"}
source={"grid5"}
source-layer={"public.net_5"}
layout={{
...gridLayer.layout,
visibility: grid && gridSize === "net_5" ? "visible" : "none",
}}
filter={filter}
paint={paintConfig}
/>
</Source>
</>
);
};

@ -1,13 +1,13 @@
import { useRating } from "../stores/useRating";
import { Points } from "./Points"; import { Points } from "./Points";
import { Layer, Source } from "react-map-gl"; import { Layer, Source } from "react-map-gl";
import { aoLayer, rayonLayer, selectedRegionLayer } from "./layers-config"; import { aoLayer, rayonLayer, selectedRegionLayer } from "./layers-config";
import { useRegionGeometry } from "../stores/useRegionGeometry";
import { useLayersVisibility } from "../stores/useLayersVisibility"; import { useLayersVisibility } from "../stores/useLayersVisibility";
import { useFilters } from "../stores/useFilters";
export const Layers = () => { export const Layers = () => {
const { rate } = useRating(); const {
const { geometry } = useRegionGeometry(); filters: { prediction, region },
} = useFilters();
const { isVisible } = useLayersVisibility(); const { isVisible } = useLayersVisibility();
return ( return (
@ -27,6 +27,7 @@ export const Layers = () => {
}} }}
/> />
</Source> </Source>
<Source <Source
id="rayon" id="rayon"
type="vector" type="vector"
@ -42,16 +43,20 @@ export const Layers = () => {
}} }}
/> />
</Source> </Source>
<Source id="selected-region" type="geojson" data={geometry}>
<Layer {region && region.geometry && (
{...selectedRegionLayer} <Source id="selected-region" type="geojson" data={region.geometry}>
layout={{ <Layer
...selectedRegionLayer.layout, {...selectedRegionLayer}
visibility: geometry ? "visible" : "none", layout={{
}} ...selectedRegionLayer.layout,
/> visibility: region ? "visible" : "none",
</Source> }}
<Points rate={rate} /> />
</Source>
)}
<Points prediction={prediction} />
</> </>
); );
}; };

@ -13,24 +13,21 @@ import { usePopup } from "../stores/usePopup";
export const MapComponent = () => { export const MapComponent = () => {
const mapRef = useRef(null); const mapRef = useRef(null);
const mapContainerRef = useRef(null); const mapContainerRef = useRef(null);
const { popup, setPopupPosition, setPopupFeature } = usePopup(); const { popup, setPopup } = usePopup();
const { coordinates: popupCoords, feature: popupFeature } = popup;
const handleClick = (event) => { const handleClick = (event) => {
if (!event.features) { if (!event.features) {
setPopupPosition(null); setPopup(null);
setPopupFeature(null);
return; return;
} }
const feature = event.features[0]; const feature = event.features[0];
if (!feature) { if (!feature) {
setPopupPosition(null); setPopup(null);
setPopupFeature(null);
return; return;
} }
const { lng: pointLng, lat: pointLat } = event.lngLat; const { lng: pointLng } = event.lngLat;
if (feature.geometry.type === "Point") { if (feature.geometry.type === "Point") {
const coordinates = feature.geometry.coordinates.slice(); const coordinates = feature.geometry.coordinates.slice();
@ -38,11 +35,11 @@ export const MapComponent = () => {
coordinates[0] += pointLng > coordinates[0] ? 360 : -360; coordinates[0] += pointLng > coordinates[0] ? 360 : -360;
} }
setPopupPosition(coordinates); const popupFeature = { ...feature };
} else { popupFeature.coordinates = coordinates;
setPopupPosition([pointLng, pointLat]);
setPopup(popupFeature);
} }
setPopupFeature(feature);
}; };
const handleMouseEnter = (event) => { const handleMouseEnter = (event) => {
@ -95,15 +92,12 @@ export const MapComponent = () => {
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
id="map" id="map"
> >
{popupFeature && popupCoords && ( {popup && (
<MapPopup <MapPopup
lat={popupCoords[1]} lat={popup.coordinates[1]}
lng={popupCoords[0]} lng={popup.coordinates[0]}
feature={popupFeature} feature={popup.feature}
onClose={() => { onClose={() => setPopup(null)}
setPopupPosition(null);
setPopupFeature(null);
}}
/> />
)} )}

@ -1,75 +1,15 @@
import { Layer, Source } from "react-map-gl"; import { Layer, Source } from "react-map-gl";
import { pointLayer } from "./layers-config"; import { pointLayer } from "./layers-config";
import { useLayersVisibility } from "../stores/useLayersVisibility"; import { useLayersVisibility } from "../stores/useLayersVisibility";
import { useRateExpression } from "./useRateExpression";
import { useModel } from "../stores/useModel";
import { BASE_URL } from "../api"; import { BASE_URL } from "../api";
const usePointSizeScale = () => { export const Points = () => {
const rate = useRateExpression();
const { model } = useModel();
if (model === "ml") {
return [
"interpolate",
["linear"],
rate,
0,
0,
10,
3,
20,
4,
30,
5,
40,
6,
80,
9,
100,
12,
];
}
return ["interpolate", ["linear"], rate, 0, 0, 10, 5, 50, 20];
};
export const Points = ({ rate: rateRange }) => {
// const { gridSize } = useGridSize();
const { isVisible } = useLayersVisibility(); const { isVisible } = useLayersVisibility();
// const { activeTypes } = useActiveTypes();
//
// const rate = useRateExpression();
// const sizeScale = usePointSizeScale();
//
// const filter = useMemo(() => {
// let result = [
// "all",
// [">=", rate, rateRange[0]],
// ["<=", rate, rateRange[1]],
// ];
//
// if (activeTypes.length) {
// result = [
// "all",
// ["in", ["get", "category"], ["literal", activeTypes]],
// [">=", rate, rateRange[0]],
// ["<=", rate, rateRange[1]],
// ];
// }
//
// return result;
// }, [rate, rateRange, activeTypes]);
const paintConfig = {
...pointLayer.paint,
// "circle-radius": sizeScale,
};
return ( return (
<> <>
<Source <Source
id="point3" id="points"
type="vector" type="vector"
tiles={[ tiles={[
`${BASE_URL}/martin/public.service_placementpoint/{z}/{x}/{y}.pbf`, `${BASE_URL}/martin/public.service_placementpoint/{z}/{x}/{y}.pbf`,
@ -84,8 +24,7 @@ export const Points = ({ rate: rateRange }) => {
...pointLayer.layout, ...pointLayer.layout,
visibility: isVisible.points ? "visible" : "none", visibility: isVisible.points ? "visible" : "none",
}} }}
// filter={filter} paint={pointLayer.paint}
paint={paintConfig}
/> />
</Source> </Source>
</> </>

@ -2,41 +2,8 @@ import { Popup } from "react-map-gl";
import { Button, Col, Row } from "antd"; import { Button, Col, Row } from "antd";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { TYPE_MAPPER } from "../config"; import { TYPE_MAPPER } from "../config";
import { useFactors } from "../stores/useFactors";
import { useMemo } from "react";
import { useModel } from "../stores/useModel";
import { usePointSelection } from "../stores/usePointSelection"; import { usePointSelection } from "../stores/usePointSelection";
const useRateValue = (feature) => {
const { factors: weights } = useFactors();
const { model } = useModel();
const result = useMemo(() => {
if (model === "ml") {
return feature.properties.model;
}
const weightedSum = Object.entries(weights).reduce(
(acc, [factor, weight]) => {
const value = Number(feature.properties[factor]);
const weightedValue = value * weight;
acc += weightedValue;
return acc;
},
0
);
const weightSum = Object.values(weights).reduce((acc, weight) => {
acc += weight;
return acc;
}, 0);
return weightedSum / weightSum;
}, [weights, feature, model]);
return result;
};
const pointConfig = [ const pointConfig = [
{ {
field: "name", field: "name",
@ -52,21 +19,7 @@ const pointConfig = [
}, },
]; ];
const gridConfig = [
{
name: "Востребованность, у.е.",
formatter: (value) => Math.round(value),
},
];
export const MapPopup = ({ feature, lat, lng, onClose }) => { export const MapPopup = ({ feature, lat, lng, onClose }) => {
const isPoint = feature.geometry.type === "Point";
const config = isPoint ? pointConfig : gridConfig;
const layout = isPoint
? { keyCol: 15, valueCol: 9 }
: { keyCol: 20, valueCol: 4 };
const rate = useRateValue(feature);
const { include, selection, exclude } = usePointSelection(); const { include, selection, exclude } = usePointSelection();
const isSelected = selection.included.has(feature.properties.id); const isSelected = selection.included.has(feature.properties.id);

@ -1,38 +0,0 @@
import { useMemo } from "react";
import { useFactors } from "../stores/useFactors";
import { useModel } from "../stores/useModel";
const getWeightedValueExpression = (factor, weight) => {
return ["*", ["to-number", ["get", factor]], weight];
};
export const useRateExpression = () => {
const { factors } = useFactors();
const { model } = useModel();
const result = useMemo(() => {
if (model === "ml") {
return ["get", "model"];
}
const weightSum = Object.entries(factors).reduce(
(acc, [_factor, weight]) => {
acc += weight;
return acc;
},
0
);
const weightedValuesSum = Object.entries(factors).reduce(
(acc, [factor, weight]) => {
acc.push(getWeightedValueExpression(factor, weight));
return acc;
},
["+"]
);
return ["/", weightedValuesSum, weightSum];
}, [factors, model]);
return result;
};

@ -1,29 +0,0 @@
import { Radio } from "antd";
import { Title } from "../../components/Title";
import { useGridSize } from "../../stores/useGridSize";
const options = [
{ label: "3 мин", value: "net_3" },
{ label: "4 мин", value: "net_4" },
{ label: "5 мин", value: "net_5" },
];
export const GridSizeSelect = () => {
const { gridSize, setGridSize } = useGridSize();
const onChange = ({ target: { value } }) => {
setGridSize(value);
};
return (
<div className={"flex flex-col items-center"}>
<Title text={"Зона пешей доступности"} />
<Radio.Group
options={options}
onChange={onChange}
value={gridSize}
optionType="button"
buttonStyle={"solid"}
/>
</div>
);
};

@ -1,25 +0,0 @@
import { Select } from "antd";
import { Title } from "../../components/Title";
import { useModel } from "../../stores/useModel";
const options = [
{ value: "statistic", label: "Обычная" },
{ value: "ml", label: "Основанная на ML" },
];
export const ModelSelect = () => {
const { model, setModel } = useModel();
const handleChange = (newValue) => setModel(newValue);
return (
<div className={"flex flex-col items-center"}>
<Title text={"Модель расчета"} />
<Select
className={"w-full"}
value={model}
onChange={handleChange}
options={options}
/>
</div>
);
};

@ -1,7 +1,7 @@
import { Button } from "antd"; import { Button } from "antd";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { Title } from "../../components/Title"; import { Title } from "../../components/Title";
import { useActiveTypes } from "../../stores/useActiveTypes"; import { useFilters } from "../../stores/useFilters";
//kiosk - городские киоски //kiosk - городские киоски
// mfc - многофункциональные центры предоставления государственных и муниципальных услуг // mfc - многофункциональные центры предоставления государственных и муниципальных услуг
@ -34,17 +34,17 @@ const SelectItem = ({ name, isActive, onClick }) => {
}; };
export const ObjectTypesSelect = () => { export const ObjectTypesSelect = () => {
const { activeTypes, setActiveTypes } = useActiveTypes(); const { filters, setCategories } = useFilters();
const handleClick = (type) => setActiveTypes(type); const handleClick = (category) => setCategories(category);
const clear = () => setActiveTypes([]); const clear = () => setCategories([]);
return ( return (
<div> <div>
<div className="flex justify-between items-center mb-1"> <div className="flex justify-between items-center mb-1">
<Title text={"Тип объекта размещения"} /> <Title text={"Тип объекта размещения"} />
{activeTypes.length !== 0 && ( {filters.categories.length !== 0 && (
<Button <Button
type="text" type="text"
className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit" className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit"
@ -60,7 +60,7 @@ export const ObjectTypesSelect = () => {
<SelectItem <SelectItem
key={type.id} key={type.id}
name={type.name} name={type.name}
isActive={activeTypes.includes(type.id)} isActive={filters.categories.includes(type.id)}
onClick={() => handleClick(type.id)} onClick={() => handleClick(type.id)}
/> />
))} ))}

@ -0,0 +1,19 @@
import { SliderComponent as Slider } from "../../components/SliderComponent";
import { useFilters } from "../../stores/useFilters";
export const PredictionSlider = () => {
const { filters, setPrediction } = useFilters();
const handleAfterChange = (range) => setPrediction(range);
return (
<Slider
title={"Прогнозный трафик, чел."}
value={filters.prediction}
onAfterChange={handleAfterChange}
min={1}
max={100}
range
/>
);
};

@ -1,19 +0,0 @@
import { useRating } from "../../stores/useRating";
import { SliderComponent as Slider } from "../../components/SliderComponent";
export const RatingSlider = () => {
const { rate, setRate } = useRating();
const handleAfterChange = (range) => setRate(range);
return (
<Slider
title={"Востребованность постамата, у.e."}
value={rate}
onAfterChange={handleAfterChange}
min={1}
max={100}
range
/>
);
};

@ -1,12 +1,11 @@
import { Empty, TreeSelect } from "antd"; import { Empty, TreeSelect } from "antd";
import { Title } from "../../components/Title"; import { Title } from "../../components/Title";
import { useRegion } from "../../stores/useRegion";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useMap } from "react-map-gl"; import { useMap } from "react-map-gl";
import getBbox from "@turf/bbox"; import getBbox from "@turf/bbox";
import { polygon as getPolygon } from "@turf/helpers"; import { polygon as getPolygon } from "@turf/helpers";
import { useRegionGeometry } from "../../stores/useRegionGeometry";
import { api } from "../../api"; import { api } from "../../api";
import { useFilters } from "../../stores/useFilters";
const { TreeNode } = TreeSelect; const { TreeNode } = TreeSelect;
@ -26,8 +25,10 @@ const normalizeRegions = (rawRegions) => {
export const RegionSelect = () => { export const RegionSelect = () => {
const { current: map } = useMap(); const { current: map } = useMap();
const { region, setRegion } = useRegion(); const {
const { setRegionGeometry } = useRegionGeometry(); filters: { region },
setRegion,
} = useFilters();
const [data, setData] = useState([]); const [data, setData] = useState([]);
const normalizedData = useMemo(() => normalizeRegions(data), [data]); const normalizedData = useMemo(() => normalizeRegions(data), [data]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -58,8 +59,7 @@ export const RegionSelect = () => {
const polygon = getPolygon(selectedRegion.geometry[0]); const polygon = getPolygon(selectedRegion.geometry[0]);
const bbox = getBbox(polygon); const bbox = getBbox(polygon);
setRegionGeometry(polygon); setRegion({ id: value, geometry: polygon });
setRegion(value);
map.fitBounds( map.fitBounds(
[ [
@ -72,10 +72,7 @@ export const RegionSelect = () => {
); );
}; };
const handleClear = () => { const handleClear = () => setRegion(null);
setRegion(null);
setRegionGeometry(null);
};
return ( return (
<div> <div>
@ -83,7 +80,7 @@ export const RegionSelect = () => {
<TreeSelect <TreeSelect
showSearch showSearch
style={{ width: "100%" }} style={{ width: "100%" }}
value={region} value={region?.id}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }} dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="Выберите АО или район" placeholder="Выберите АО или район"
allowClear allowClear

@ -1,31 +0,0 @@
import { SliderComponent as Slider } from "../../components/SliderComponent";
import { useFactors } from "../../stores/useFactors";
import { factorsNameMapper } from "../../config";
import { useModel } from "../../stores/useModel";
export const Settings = () => {
const { factors, setWeight } = useFactors();
const { model } = useModel();
const handleAfterChange = (factor, value) => {
setWeight(factor, value);
};
return (
<div className={"space-y-2 min-w-[300px]"}>
{Object.entries(factors).map(([field, value]) => {
return (
<Slider
title={factorsNameMapper[field]}
value={value}
key={field}
max={1}
step={0.01}
onAfterChange={(value) => handleAfterChange(field, value)}
disabled={model === "ml"}
/>
);
})}
</div>
);
};

@ -1,22 +1,11 @@
import { RegionSelect } from "./RegionSelect"; import { RegionSelect } from "./RegionSelect";
import { Button } from "antd"; import { Button } from "antd";
import { ObjectTypesSelect } from "./ObjectTypesSelect"; import { ObjectTypesSelect } from "./ObjectTypesSelect";
import { RatingSlider } from "./RatingSlider"; import { PredictionSlider } from "./PredictionSlider";
import { LayersVisibility } from "./LayersVisibility"; import { LayersVisibility } from "./LayersVisibility";
import { useFactors } from "../../stores/useFactors";
import { useActiveTypes } from "../../stores/useActiveTypes";
import { useRegion } from "../../stores/useRegion";
import { useGridSize } from "../../stores/useGridSize";
import { useRating } from "../../stores/useRating";
import { useState } from "react"; import { useState } from "react";
import { useModel } from "../../stores/useModel";
import { api } from "../../api"; import { api } from "../../api";
import { useFilters } from "../../stores/useFilters";
const activeTablesMapper = {
net_3: ["point3", "net_3"],
net_4: ["point4", "net_4"],
net_5: ["point5", "net_5"],
};
function download(filename, data) { function download(filename, data) {
const downloadLink = window.document.createElement("a"); const downloadLink = window.document.createElement("a");
@ -44,12 +33,9 @@ const getRegionParam = (regionId) => {
}; };
export const Sidebar = () => { export const Sidebar = () => {
const { factors } = useFactors(); const {
const { activeTypes } = useActiveTypes(); filters: { prediction, region, categories },
const { region } = useRegion(); } = useFilters();
const { gridSize } = useGridSize();
const { rate: rateRange } = useRating();
const { model } = useModel();
const [isExporting, setIsExporting] = useState(false); const [isExporting, setIsExporting] = useState(false);
@ -57,13 +43,10 @@ export const Sidebar = () => {
setIsExporting(true); setIsExporting(true);
try { try {
const params = { const params = {
koefs: factors,
tables: activeTablesMapper[gridSize],
filters: { filters: {
rate_from: rateRange[0], rate_from: prediction[0],
rate_to: rateRange[1], rate_to: prediction[1],
}, },
method: model === "ml" ? "model" : "rate",
}; };
if (region) { if (region) {
@ -73,10 +56,10 @@ export const Sidebar = () => {
}; };
} }
if (activeTypes.length) { if (categories.length) {
params.filters = { params.filters = {
...params.filters, ...params.filters,
category: activeTypes, category: categories,
}; };
} }
@ -99,7 +82,7 @@ export const Sidebar = () => {
<LayersVisibility /> <LayersVisibility />
<RegionSelect /> <RegionSelect />
<ObjectTypesSelect /> <ObjectTypesSelect />
<RatingSlider /> <PredictionSlider />
<div> <div>
<Button <Button
type="primary" type="primary"

@ -6,6 +6,7 @@ import { api } from "../../api";
import parse from "wellknown"; import parse from "wellknown";
import { useMap } from "react-map-gl"; import { useMap } from "react-map-gl";
import { usePointSelection } from "../../stores/usePointSelection"; import { usePointSelection } from "../../stores/usePointSelection";
import { useFilters } from "../../stores/useFilters";
const columns = [ const columns = [
{ {
@ -84,15 +85,17 @@ const PAGE_SIZE = 30;
export const Table = React.memo(({ height = 200 }) => { export const Table = React.memo(({ height = 200 }) => {
const { map } = useMap(); const { map } = useMap();
const { include, selection, exclude } = usePointSelection();
const tableRef = useRef(null); const tableRef = useRef(null);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const { filters } = useFilters();
const { include, selection, exclude } = usePointSelection();
const { data } = useQuery( const { data } = useQuery(
["table", page], ["table", page, filters],
async () => { async () => {
const { data } = await api.get( const { data } = await api.get(
`/api/placement_points?page=${page}&page_size=${PAGE_SIZE}` `/api/placement_points?page=${page}&page_size=${PAGE_SIZE}&prediction=${filters.prediction}`
); );
return data; return data;
@ -152,9 +155,9 @@ export const Table = React.memo(({ height = 200 }) => {
rowKey="id" rowKey="id"
scroll={SCROLL} scroll={SCROLL}
sticky={true} sticky={true}
onRow={(record, rowIndex) => { onRow={(record) => {
return { return {
onClick: (event) => { onClick: () => {
const geometry = parse(record.geometry); const geometry = parse(record.geometry);
// const feature = { // const feature = {
// properties: record, // properties: record,

@ -1,20 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
activeTypes: [],
setActiveTypes: (type) =>
set((state) => {
if (Array.isArray(type)) {
state.activeTypes = [];
return;
}
if (state.activeTypes.includes(type)) {
state.activeTypes = state.activeTypes.filter((id) => id !== type);
} else {
state.activeTypes.push(type);
}
}),
});
export const useActiveTypes = create(immer(store));

@ -1,20 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
import { factorsNameMapper } from "../config";
const DEFAULT_WEIGHT = 0.5;
const INITIAL_STATE = Object.keys(factorsNameMapper).reduce((acc, field) => {
acc[field] = DEFAULT_WEIGHT;
return acc;
}, {});
const store = (set) => ({
factors: INITIAL_STATE,
setWeight: (factor, value) =>
set((state) => {
state.factors[factor] = value;
}),
});
export const useFactors = create(immer(store));

@ -0,0 +1,39 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const INITIAL = {
prediction: [50, 100],
categories: [],
region: null,
};
const store = (set) => ({
filters: INITIAL,
setPrediction: (value) => {
set((state) => {
state.filters.prediction = value;
});
},
setCategories: (category) =>
set((state) => {
if (Array.isArray(category)) {
state.filters.categories = [];
return;
}
if (state.filters.categories.includes(category)) {
state.filters.categories = state.filters.categories.filter(
(id) => id !== category
);
} else {
state.filters.categories.push(category);
}
}),
setRegion: (value) =>
set((state) => {
state.filters.region = value;
}),
});
export const useFilters = create(immer(store));

@ -1,12 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
gridSize: "net_5",
setGridSize: (value) =>
set((state) => {
state.gridSize = value;
}),
});
export const useGridSize = create(immer(store));

@ -1,4 +1,4 @@
import create from "zustand"; import { create } from "zustand";
import { immer } from "zustand/middleware/immer"; import { immer } from "zustand/middleware/immer";
const INITIAL_STATE = { const INITIAL_STATE = {

@ -1,12 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
model: "statistic",
setModel: (value) =>
set((state) => {
state.model = value;
}),
});
export const useModel = create(immer(store));

@ -1,22 +1,19 @@
import { create } from "zustand"; import { create } from "zustand";
import { immer } from "zustand/middleware/immer"; import { immer } from "zustand/middleware/immer";
const INITIAL = {
coordinates: null,
feature: null,
};
const store = (set) => ({ const store = (set) => ({
popup: INITIAL, popup: null,
setPopupPosition: (coords) => {
set((state) => {
state.popup.coordinates = coords;
});
},
setPopupFeature: (feature) => { setPopup: (feature) => {
set((state) => { set((state) => {
state.popup.feature = feature; if (!feature) {
state.popup = null;
return state;
}
state.popup = {
feature,
coordinates: feature.coordinates,
};
}); });
}, },
}); });

@ -1,12 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
rate: [1, 100],
setRate: (value) =>
set((state) => {
state.rate = value;
}),
});
export const useRating = create(immer(store));

@ -1,12 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
region: null,
setRegion: (value) =>
set((state) => {
state.region = value;
}),
});
export const useRegion = create(immer(store));

@ -1,12 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
geometry: null,
setRegionGeometry: (value) =>
set((state) => {
state.geometry = value;
}),
});
export const useRegionGeometry = create(immer(store));

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

1
vite-env.d.ts vendored

@ -0,0 +1 @@
/// <reference types="vite/client" />

@ -618,14 +618,14 @@
resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2"
integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==
"@types/react-dom@^18.0.7": "@types/react-dom@^18.0.11":
version "18.0.11" version "18.0.11"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^18.0.22": "@types/react@*", "@types/react@^18.0.28":
version "18.0.28" version "18.0.28"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
@ -2403,6 +2403,11 @@ shallowequal@^1.1.0:
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
simple-zustand-devtools@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/simple-zustand-devtools/-/simple-zustand-devtools-1.1.0.tgz#bb080b30c9930d2997f33881f63d47405226fd7f"
integrity sha512-Axfcfr9L3YL3kto7aschCQLY2VUlXXMnIVtaTe9Y0qWbNmPsX/y7KsNprmxBZoB0pww5ZGs1u/ohcrvQ3tE6jA==
source-map-js@^1.0.2: source-map-js@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@ -2535,6 +2540,11 @@ typedarray@~0.0.5:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73"
integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ== integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==
typescript@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
update-browserslist-db@^1.0.10: update-browserslist-db@^1.0.10:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"

Loading…
Cancel
Save