From 9390b15d81125d2132d45e3cb2955da788a79db7 Mon Sep 17 00:00:00 2001 From: Platon Yasev Date: Fri, 24 Feb 2023 09:31:49 +0300 Subject: [PATCH] Refactor stores; assemble filters store --- package.json | 6 +- src/App.jsx | 8 ++ src/Map/Grid.jsx | 130 ---------------------- src/Map/Layers.jsx | 33 +++--- src/Map/MapComponent.jsx | 32 +++--- src/Map/Points.jsx | 67 +---------- src/Map/Popup.jsx | 47 -------- src/Map/useRateExpression.js | 38 ------- src/modules/Sidebar/GridSizeSelect.jsx | 29 ----- src/modules/Sidebar/ModelSelect.jsx | 25 ----- src/modules/Sidebar/ObjectTypesSelect.jsx | 12 +- src/modules/Sidebar/PredictionSlider.jsx | 19 ++++ src/modules/Sidebar/RatingSlider.jsx | 19 ---- src/modules/Sidebar/RegionSelect.jsx | 19 ++-- src/modules/Sidebar/Settings.jsx | 31 ------ src/modules/Sidebar/Sidebar.jsx | 37 ++---- src/modules/Table/Table.jsx | 15 ++- src/stores/useActiveTypes.js | 20 ---- src/stores/useFactors.js | 20 ---- src/stores/useFilters.js | 39 +++++++ src/stores/useGridSize.js | 12 -- src/stores/useLayersVisibility.js | 2 +- src/stores/useModel.js | 12 -- src/stores/usePopup.js | 23 ++-- src/stores/useRating.js | 12 -- src/stores/useRegion.js | 12 -- src/stores/useRegionGeometry.js | 12 -- tsconfig.json | 25 +++++ tsconfig.node.json | 9 ++ vite-env.d.ts | 1 + vite.config.js => vite.config.ts | 0 yarn.lock | 14 ++- 32 files changed, 196 insertions(+), 584 deletions(-) delete mode 100644 src/Map/Grid.jsx delete mode 100644 src/Map/useRateExpression.js delete mode 100644 src/modules/Sidebar/GridSizeSelect.jsx delete mode 100644 src/modules/Sidebar/ModelSelect.jsx create mode 100644 src/modules/Sidebar/PredictionSlider.jsx delete mode 100644 src/modules/Sidebar/RatingSlider.jsx delete mode 100644 src/modules/Sidebar/Settings.jsx delete mode 100644 src/stores/useActiveTypes.js delete mode 100644 src/stores/useFactors.js create mode 100644 src/stores/useFilters.js delete mode 100644 src/stores/useGridSize.js delete mode 100644 src/stores/useModel.js delete mode 100644 src/stores/useRating.js delete mode 100644 src/stores/useRegion.js delete mode 100644 src/stores/useRegionGeometry.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite-env.d.ts rename vite.config.js => vite.config.ts (100%) diff --git a/package.json b/package.json index f0394f4..90ea793 100644 --- a/package.json +++ b/package.json @@ -28,17 +28,19 @@ "react-map-gl": "^7.0.19", "react-router-dom": "^6.8.1", "tailwind-merge": "^1.7.0", + "typescript": "^4.9.5", "vite-plugin-svgr": "^2.4.0", "wellknown": "^0.5.0", "zustand": "^4.1.3" }, "devDependencies": { - "@types/react": "^18.0.22", - "@types/react-dom": "^18.0.7", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^2.2.0", "autoprefixer": "^10.4.13", "less": "^4.1.3", "postcss": "^8.4.18", + "simple-zustand-devtools": "^1.1.0", "tailwindcss": "^3.2.1", "vite": "^3.2.0" } diff --git a/src/App.jsx b/src/App.jsx index 735652b..91bbf59 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,11 +6,19 @@ import { RegisterPage } from "./pages/Register"; import { MapPage } from "./pages/Map"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { enableMapSet } from "immer"; +import { mountStoreDevtool } from "simple-zustand-devtools"; +import { useFilters } from "./stores/useFilters"; +import { usePointSelection } from "./stores/usePointSelection"; const queryClient = new QueryClient(); enableMapSet(); +if (import.meta.env.MODE === "development") { + mountStoreDevtool("Filters", useFilters); + mountStoreDevtool("PointSelection", usePointSelection); +} + function App() { return ( diff --git a/src/Map/Grid.jsx b/src/Map/Grid.jsx deleted file mode 100644 index fd3b637..0000000 --- a/src/Map/Grid.jsx +++ /dev/null @@ -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 ( - <> - - - - - - - - - - - ); -}; diff --git a/src/Map/Layers.jsx b/src/Map/Layers.jsx index fcc24c2..45b9a36 100644 --- a/src/Map/Layers.jsx +++ b/src/Map/Layers.jsx @@ -1,13 +1,13 @@ -import { useRating } from "../stores/useRating"; import { Points } from "./Points"; import { Layer, Source } from "react-map-gl"; import { aoLayer, rayonLayer, selectedRegionLayer } from "./layers-config"; -import { useRegionGeometry } from "../stores/useRegionGeometry"; import { useLayersVisibility } from "../stores/useLayersVisibility"; +import { useFilters } from "../stores/useFilters"; export const Layers = () => { - const { rate } = useRating(); - const { geometry } = useRegionGeometry(); + const { + filters: { prediction, region }, + } = useFilters(); const { isVisible } = useLayersVisibility(); return ( @@ -27,6 +27,7 @@ export const Layers = () => { }} /> + { }} /> - - - - + + {region && region.geometry && ( + + + + )} + + ); }; diff --git a/src/Map/MapComponent.jsx b/src/Map/MapComponent.jsx index 5fccd09..b1ae767 100644 --- a/src/Map/MapComponent.jsx +++ b/src/Map/MapComponent.jsx @@ -13,24 +13,21 @@ import { usePopup } from "../stores/usePopup"; export const MapComponent = () => { const mapRef = useRef(null); const mapContainerRef = useRef(null); - const { popup, setPopupPosition, setPopupFeature } = usePopup(); - const { coordinates: popupCoords, feature: popupFeature } = popup; + const { popup, setPopup } = usePopup(); const handleClick = (event) => { if (!event.features) { - setPopupPosition(null); - setPopupFeature(null); + setPopup(null); return; } const feature = event.features[0]; if (!feature) { - setPopupPosition(null); - setPopupFeature(null); + setPopup(null); return; } - const { lng: pointLng, lat: pointLat } = event.lngLat; + const { lng: pointLng } = event.lngLat; if (feature.geometry.type === "Point") { const coordinates = feature.geometry.coordinates.slice(); @@ -38,11 +35,11 @@ export const MapComponent = () => { coordinates[0] += pointLng > coordinates[0] ? 360 : -360; } - setPopupPosition(coordinates); - } else { - setPopupPosition([pointLng, pointLat]); + const popupFeature = { ...feature }; + popupFeature.coordinates = coordinates; + + setPopup(popupFeature); } - setPopupFeature(feature); }; const handleMouseEnter = (event) => { @@ -95,15 +92,12 @@ export const MapComponent = () => { onMouseLeave={handleMouseLeave} id="map" > - {popupFeature && popupCoords && ( + {popup && ( { - setPopupPosition(null); - setPopupFeature(null); - }} + lat={popup.coordinates[1]} + lng={popup.coordinates[0]} + feature={popup.feature} + onClose={() => setPopup(null)} /> )} diff --git a/src/Map/Points.jsx b/src/Map/Points.jsx index 35dd922..b2ef92a 100644 --- a/src/Map/Points.jsx +++ b/src/Map/Points.jsx @@ -1,75 +1,15 @@ import { Layer, Source } from "react-map-gl"; import { pointLayer } from "./layers-config"; import { useLayersVisibility } from "../stores/useLayersVisibility"; -import { useRateExpression } from "./useRateExpression"; -import { useModel } from "../stores/useModel"; import { BASE_URL } from "../api"; -const usePointSizeScale = () => { - 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(); +export const Points = () => { 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 ( <> { ...pointLayer.layout, visibility: isVisible.points ? "visible" : "none", }} - // filter={filter} - paint={paintConfig} + paint={pointLayer.paint} /> diff --git a/src/Map/Popup.jsx b/src/Map/Popup.jsx index 647d736..fedd1ca 100644 --- a/src/Map/Popup.jsx +++ b/src/Map/Popup.jsx @@ -2,41 +2,8 @@ import { Popup } from "react-map-gl"; import { Button, Col, Row } from "antd"; import { twMerge } from "tailwind-merge"; import { TYPE_MAPPER } from "../config"; -import { useFactors } from "../stores/useFactors"; -import { useMemo } from "react"; -import { useModel } from "../stores/useModel"; 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 = [ { field: "name", @@ -52,21 +19,7 @@ const pointConfig = [ }, ]; -const gridConfig = [ - { - name: "Востребованность, у.е.", - formatter: (value) => Math.round(value), - }, -]; - 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 isSelected = selection.included.has(feature.properties.id); diff --git a/src/Map/useRateExpression.js b/src/Map/useRateExpression.js deleted file mode 100644 index bdb9699..0000000 --- a/src/Map/useRateExpression.js +++ /dev/null @@ -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; -}; diff --git a/src/modules/Sidebar/GridSizeSelect.jsx b/src/modules/Sidebar/GridSizeSelect.jsx deleted file mode 100644 index d2af4cf..0000000 --- a/src/modules/Sidebar/GridSizeSelect.jsx +++ /dev/null @@ -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 ( -
- - <Radio.Group - options={options} - onChange={onChange} - value={gridSize} - optionType="button" - buttonStyle={"solid"} - /> - </div> - ); -}; diff --git a/src/modules/Sidebar/ModelSelect.jsx b/src/modules/Sidebar/ModelSelect.jsx deleted file mode 100644 index 6d7b081..0000000 --- a/src/modules/Sidebar/ModelSelect.jsx +++ /dev/null @@ -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> - ); -}; diff --git a/src/modules/Sidebar/ObjectTypesSelect.jsx b/src/modules/Sidebar/ObjectTypesSelect.jsx index 8a58ff4..85b1ce8 100644 --- a/src/modules/Sidebar/ObjectTypesSelect.jsx +++ b/src/modules/Sidebar/ObjectTypesSelect.jsx @@ -1,7 +1,7 @@ import { Button } from "antd"; import { twMerge } from "tailwind-merge"; import { Title } from "../../components/Title"; -import { useActiveTypes } from "../../stores/useActiveTypes"; +import { useFilters } from "../../stores/useFilters"; //kiosk - городские киоски // mfc - многофункциональные центры предоставления государственных и муниципальных услуг @@ -34,17 +34,17 @@ const SelectItem = ({ name, isActive, onClick }) => { }; 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 ( <div> <div className="flex justify-between items-center mb-1"> <Title text={"Тип объекта размещения"} /> - {activeTypes.length !== 0 && ( + {filters.categories.length !== 0 && ( <Button type="text" 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 key={type.id} name={type.name} - isActive={activeTypes.includes(type.id)} + isActive={filters.categories.includes(type.id)} onClick={() => handleClick(type.id)} /> ))} diff --git a/src/modules/Sidebar/PredictionSlider.jsx b/src/modules/Sidebar/PredictionSlider.jsx new file mode 100644 index 0000000..8118b93 --- /dev/null +++ b/src/modules/Sidebar/PredictionSlider.jsx @@ -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 + /> + ); +}; diff --git a/src/modules/Sidebar/RatingSlider.jsx b/src/modules/Sidebar/RatingSlider.jsx deleted file mode 100644 index 327f316..0000000 --- a/src/modules/Sidebar/RatingSlider.jsx +++ /dev/null @@ -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 - /> - ); -}; diff --git a/src/modules/Sidebar/RegionSelect.jsx b/src/modules/Sidebar/RegionSelect.jsx index 0eff9d1..28dbac3 100644 --- a/src/modules/Sidebar/RegionSelect.jsx +++ b/src/modules/Sidebar/RegionSelect.jsx @@ -1,12 +1,11 @@ import { Empty, TreeSelect } from "antd"; import { Title } from "../../components/Title"; -import { useRegion } from "../../stores/useRegion"; import { useEffect, useMemo, useState } from "react"; import { useMap } from "react-map-gl"; import getBbox from "@turf/bbox"; import { polygon as getPolygon } from "@turf/helpers"; -import { useRegionGeometry } from "../../stores/useRegionGeometry"; import { api } from "../../api"; +import { useFilters } from "../../stores/useFilters"; const { TreeNode } = TreeSelect; @@ -26,8 +25,10 @@ const normalizeRegions = (rawRegions) => { export const RegionSelect = () => { const { current: map } = useMap(); - const { region, setRegion } = useRegion(); - const { setRegionGeometry } = useRegionGeometry(); + const { + filters: { region }, + setRegion, + } = useFilters(); const [data, setData] = useState([]); const normalizedData = useMemo(() => normalizeRegions(data), [data]); const [loading, setLoading] = useState(false); @@ -58,8 +59,7 @@ export const RegionSelect = () => { const polygon = getPolygon(selectedRegion.geometry[0]); const bbox = getBbox(polygon); - setRegionGeometry(polygon); - setRegion(value); + setRegion({ id: value, geometry: polygon }); map.fitBounds( [ @@ -72,10 +72,7 @@ export const RegionSelect = () => { ); }; - const handleClear = () => { - setRegion(null); - setRegionGeometry(null); - }; + const handleClear = () => setRegion(null); return ( <div> @@ -83,7 +80,7 @@ export const RegionSelect = () => { <TreeSelect showSearch style={{ width: "100%" }} - value={region} + value={region?.id} dropdownStyle={{ maxHeight: 400, overflow: "auto" }} placeholder="Выберите АО или район" allowClear diff --git a/src/modules/Sidebar/Settings.jsx b/src/modules/Sidebar/Settings.jsx deleted file mode 100644 index a074087..0000000 --- a/src/modules/Sidebar/Settings.jsx +++ /dev/null @@ -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> - ); -}; diff --git a/src/modules/Sidebar/Sidebar.jsx b/src/modules/Sidebar/Sidebar.jsx index bf12683..66cd18f 100644 --- a/src/modules/Sidebar/Sidebar.jsx +++ b/src/modules/Sidebar/Sidebar.jsx @@ -1,22 +1,11 @@ import { RegionSelect } from "./RegionSelect"; import { Button } from "antd"; import { ObjectTypesSelect } from "./ObjectTypesSelect"; -import { RatingSlider } from "./RatingSlider"; +import { PredictionSlider } from "./PredictionSlider"; 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 { useModel } from "../../stores/useModel"; import { api } from "../../api"; - -const activeTablesMapper = { - net_3: ["point3", "net_3"], - net_4: ["point4", "net_4"], - net_5: ["point5", "net_5"], -}; +import { useFilters } from "../../stores/useFilters"; function download(filename, data) { const downloadLink = window.document.createElement("a"); @@ -44,12 +33,9 @@ const getRegionParam = (regionId) => { }; export const Sidebar = () => { - const { factors } = useFactors(); - const { activeTypes } = useActiveTypes(); - const { region } = useRegion(); - const { gridSize } = useGridSize(); - const { rate: rateRange } = useRating(); - const { model } = useModel(); + const { + filters: { prediction, region, categories }, + } = useFilters(); const [isExporting, setIsExporting] = useState(false); @@ -57,13 +43,10 @@ export const Sidebar = () => { setIsExporting(true); try { const params = { - koefs: factors, - tables: activeTablesMapper[gridSize], filters: { - rate_from: rateRange[0], - rate_to: rateRange[1], + rate_from: prediction[0], + rate_to: prediction[1], }, - method: model === "ml" ? "model" : "rate", }; if (region) { @@ -73,10 +56,10 @@ export const Sidebar = () => { }; } - if (activeTypes.length) { + if (categories.length) { params.filters = { ...params.filters, - category: activeTypes, + category: categories, }; } @@ -99,7 +82,7 @@ export const Sidebar = () => { <LayersVisibility /> <RegionSelect /> <ObjectTypesSelect /> - <RatingSlider /> + <PredictionSlider /> <div> <Button type="primary" diff --git a/src/modules/Table/Table.jsx b/src/modules/Table/Table.jsx index ff49043..f69b2d0 100644 --- a/src/modules/Table/Table.jsx +++ b/src/modules/Table/Table.jsx @@ -6,6 +6,7 @@ import { api } from "../../api"; import parse from "wellknown"; import { useMap } from "react-map-gl"; import { usePointSelection } from "../../stores/usePointSelection"; +import { useFilters } from "../../stores/useFilters"; const columns = [ { @@ -84,15 +85,17 @@ const PAGE_SIZE = 30; export const Table = React.memo(({ height = 200 }) => { const { map } = useMap(); - const { include, selection, exclude } = usePointSelection(); - const tableRef = useRef(null); const [page, setPage] = useState(1); + + const { filters } = useFilters(); + const { include, selection, exclude } = usePointSelection(); + const { data } = useQuery( - ["table", page], + ["table", page, filters], async () => { 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; @@ -152,9 +155,9 @@ export const Table = React.memo(({ height = 200 }) => { rowKey="id" scroll={SCROLL} sticky={true} - onRow={(record, rowIndex) => { + onRow={(record) => { return { - onClick: (event) => { + onClick: () => { const geometry = parse(record.geometry); // const feature = { // properties: record, diff --git a/src/stores/useActiveTypes.js b/src/stores/useActiveTypes.js deleted file mode 100644 index e580b5f..0000000 --- a/src/stores/useActiveTypes.js +++ /dev/null @@ -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)); diff --git a/src/stores/useFactors.js b/src/stores/useFactors.js deleted file mode 100644 index 6fd3a90..0000000 --- a/src/stores/useFactors.js +++ /dev/null @@ -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)); diff --git a/src/stores/useFilters.js b/src/stores/useFilters.js new file mode 100644 index 0000000..01126c1 --- /dev/null +++ b/src/stores/useFilters.js @@ -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)); diff --git a/src/stores/useGridSize.js b/src/stores/useGridSize.js deleted file mode 100644 index 039512b..0000000 --- a/src/stores/useGridSize.js +++ /dev/null @@ -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)); diff --git a/src/stores/useLayersVisibility.js b/src/stores/useLayersVisibility.js index e27d58b..eed799d 100644 --- a/src/stores/useLayersVisibility.js +++ b/src/stores/useLayersVisibility.js @@ -1,4 +1,4 @@ -import create from "zustand"; +import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; const INITIAL_STATE = { diff --git a/src/stores/useModel.js b/src/stores/useModel.js deleted file mode 100644 index eaf9036..0000000 --- a/src/stores/useModel.js +++ /dev/null @@ -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)); diff --git a/src/stores/usePopup.js b/src/stores/usePopup.js index 6ca558e..f68033a 100644 --- a/src/stores/usePopup.js +++ b/src/stores/usePopup.js @@ -1,22 +1,19 @@ import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; -const INITIAL = { - coordinates: null, - feature: null, -}; - const store = (set) => ({ - popup: INITIAL, - setPopupPosition: (coords) => { - set((state) => { - state.popup.coordinates = coords; - }); - }, + popup: null, - setPopupFeature: (feature) => { + setPopup: (feature) => { set((state) => { - state.popup.feature = feature; + if (!feature) { + state.popup = null; + return state; + } + state.popup = { + feature, + coordinates: feature.coordinates, + }; }); }, }); diff --git a/src/stores/useRating.js b/src/stores/useRating.js deleted file mode 100644 index eaf390c..0000000 --- a/src/stores/useRating.js +++ /dev/null @@ -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)); diff --git a/src/stores/useRegion.js b/src/stores/useRegion.js deleted file mode 100644 index ddb1316..0000000 --- a/src/stores/useRegion.js +++ /dev/null @@ -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)); diff --git a/src/stores/useRegionGeometry.js b/src/stores/useRegionGeometry.js deleted file mode 100644 index 624e385..0000000 --- a/src/stores/useRegionGeometry.js +++ /dev/null @@ -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)); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c7b9729 --- /dev/null +++ b/tsconfig.json @@ -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" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/vite.config.js b/vite.config.ts similarity index 100% rename from vite.config.js rename to vite.config.ts diff --git a/yarn.lock b/yarn.lock index e623ba2..ef6a956 100644 --- a/yarn.lock +++ b/yarn.lock @@ -618,14 +618,14 @@ resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== -"@types/react-dom@^18.0.7": +"@types/react-dom@^18.0.11": version "18.0.11" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.22": +"@types/react@*", "@types/react@^18.0.28": version "18.0.28" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== @@ -2403,6 +2403,11 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" 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: version "1.0.2" 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" 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: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"