+
{config.map((item) => {
- const value = feature.properties[item.field];
+ const value = item.field
+ ? feature.properties[item.field]
+ : getRate(feature.properties, factors);
return (
-
-
+
+
{item.name}
-
+
{item.formatter ? item.formatter(value) : value}
diff --git a/src/Map/layers-config.js b/src/Map/layers-config.js
index 13c570e..eb20df8 100644
--- a/src/Map/layers-config.js
+++ b/src/Map/layers-config.js
@@ -13,10 +13,7 @@ const pointColors = {
};
export const pointLayer = {
- id: "points",
type: "circle",
- source: "points",
- "source-layer": "public.point3",
layout: {},
paint: {
"circle-color": [
@@ -45,18 +42,7 @@ export const gridLayer = {
type: "fill",
layout: {},
paint: {
- "fill-color": [
- "interpolate",
- ["linear"],
- ["get", "rate"],
- 0,
- "rgb(204,34,34)",
- 5,
- "rgb(255,221,52)",
- 10,
- "rgb(30,131,42)",
- ],
- "fill-opacity": 0.3,
+ "fill-opacity": 0.5,
"fill-outline-color": "transparent",
},
};
diff --git a/src/components/SliderComponent.jsx b/src/components/SliderComponent.jsx
index bb95397..3d9401f 100644
--- a/src/components/SliderComponent.jsx
+++ b/src/components/SliderComponent.jsx
@@ -30,6 +30,7 @@ export const SliderComponent = ({
min = 0,
max = 100,
range = false,
+ step = 1,
}) => {
const fullRangeMarks = {
[min]: ,
@@ -74,6 +75,7 @@ export const SliderComponent = ({
onAfterChange={handleAfterChange}
min={min}
max={max}
+ step={step}
/>
);
diff --git a/src/config.js b/src/config.js
index 69a2ba5..15a66c9 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,3 +1,5 @@
+import axios from "axios";
+
export const TYPE_MAPPER = {
kiosk: "Городской киоск",
mfc: "МФЦ",
@@ -5,3 +7,28 @@ export const TYPE_MAPPER = {
dk: "Дом культуры и отдыха",
sport: "Спортивный объект",
};
+
+export const factorsNameMapper = {
+ people: "Численность населения в 2021 г.",
+ people2025: "Численность населения в 2025 г. (прогноз)",
+ stops_ot: "Остановки общественного транспорта",
+ routes_ot: "Маршруты общетвенного транспорта",
+ in_metro: "Входы в ближайшее метро в месяц",
+ out_metro: "Выходы из ближайшего метро в месяц",
+ tc: "Тогровые центры",
+ empls: "Рабочие места",
+ walkers: "Трафик населения",
+ schools: "Школы и детские сады",
+ parking: "Парковочные места",
+ pvz: "Пункты выдачи заказов",
+ gov_place: "Рекомендованные пункты размещения постаматов",
+ bike_park: "Городская аренда велосипедов, шт.",
+ products: "Продовольственные магазины",
+ nonprod: "Непродовольственные магазины",
+ service: "Пункты оказания бытовых услуг",
+ vuz: "ВУЗы и техникумы",
+};
+
+export const api = axios.create({
+ baseURL: "https://postamates.spatiality.website",
+});
diff --git a/src/index.css b/src/index.css
index 46503db..b2a68cd 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,6 +2,11 @@
@tailwind components;
@tailwind utilities;
+.mapboxgl-popup,
+.maplibregl-popup {
+ @apply !max-w-[400px];
+}
+
.mapboxgl-popup-content,
.maplibregl-popup-content {
@apply bg-grey-light shadow-lg rounded-md;
@@ -13,7 +18,7 @@
}
.ant-popover-inner {
- @apply bg-white-background rounded-xl;
+ @apply bg-white-background rounded-xl max-h-[calc(100vh-100px)] overflow-y-auto;
}
.mapboxgl-ctrl-group,
diff --git a/src/modules/Sidebar/GridSizeSelect.jsx b/src/modules/Sidebar/GridSizeSelect.jsx
index 803d10e..d2af4cf 100644
--- a/src/modules/Sidebar/GridSizeSelect.jsx
+++ b/src/modules/Sidebar/GridSizeSelect.jsx
@@ -3,9 +3,9 @@ import { Title } from "../../components/Title";
import { useGridSize } from "../../stores/useGridSize";
const options = [
- { label: "3 мин", value: "net3" },
- { label: "4 мин", value: "net4" },
- { label: "5 мин", value: "net5" },
+ { label: "3 мин", value: "net_3" },
+ { label: "4 мин", value: "net_4" },
+ { label: "5 мин", value: "net_5" },
];
export const GridSizeSelect = () => {
diff --git a/src/modules/Sidebar/RatingSlider.jsx b/src/modules/Sidebar/RatingSlider.jsx
index d2dc684..005cd5f 100644
--- a/src/modules/Sidebar/RatingSlider.jsx
+++ b/src/modules/Sidebar/RatingSlider.jsx
@@ -11,7 +11,7 @@ export const RatingSlider = () => {
title={"Востребованность постамата, у.e."}
value={rate}
onAfterChange={handleAfterChange}
- max={10}
+ max={45}
range
/>
);
diff --git a/src/modules/Sidebar/RegionSelect.jsx b/src/modules/Sidebar/RegionSelect.jsx
index 839fc52..aa3d320 100644
--- a/src/modules/Sidebar/RegionSelect.jsx
+++ b/src/modules/Sidebar/RegionSelect.jsx
@@ -1,34 +1,63 @@
import { TreeSelect } from "antd";
import { Title } from "../../components/Title";
import { useRegion } from "../../stores/useRegion";
+import { useEffect, useMemo, useState } from "react";
+import { api } from "../../config";
+import { useMap } from "react-map-gl";
+import getBbox from "@turf/bbox";
+import { polygon as getPolygon } from "@turf/helpers";
const { TreeNode } = TreeSelect;
-const mockRegions = [
- {
- id: "tsao",
- name: "Центральный (ЦАО)",
- children: [
- { id: "arbat", name: "Арбат" },
- { id: "hamovniki", name: "Хамовники" },
- ],
- },
- {
- id: "yuao",
- name: "Южный (ЮАО)",
- children: [
- { id: "danilovsk", name: "Даниловский" },
- { id: "nagorn", name: "Нагорный" },
- ],
- },
-];
+const normalizeRegions = (rawRegions) => {
+ if (!rawRegions) return {};
+
+ return rawRegions.reduce((acc, raw) => {
+ acc[`ao-${raw.id}`] = raw;
+ if (raw.children) {
+ raw.children.forEach((child) => {
+ acc[`rayon-${child.id}`] = child;
+ });
+ }
+ return acc;
+ }, {});
+};
export const RegionSelect = () => {
+ const { current: map } = useMap();
const { region, setRegion } = useRegion();
+ const [data, setData] = useState([]);
+ const normalizedData = useMemo(() => normalizeRegions(data), [data]);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const getRegions = async () => {
+ setLoading(true);
+ try {
+ const response = await api.get("/api/ao_and_rayons");
+ setData(response.data);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ getRegions();
+ }, []);
const onChange = (value) => {
- console.log(value);
+ const selectedRegion = normalizedData[value];
+
+ const polygon = getPolygon(selectedRegion.geometry[0]);
+ const bbox = getBbox(polygon);
+
setRegion(value);
+
+ map.fitBounds([
+ [bbox[0], bbox[1]], // southwestern corner of the bounds
+ [bbox[2], bbox[3]], // northeastern corner of the bounds
+ ]);
};
return (
@@ -41,16 +70,25 @@ export const RegionSelect = () => {
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="Выберите АО или район"
allowClear
- treeDefaultExpandAll
+ treeDefaultExpandAll={false}
onChange={onChange}
+ loading={loading}
// treeNodeFilterProp="name"
// filterTreeNode={true}
>
- {mockRegions.map((parent) => {
+ {data?.map((parent) => {
return (
-
+
{parent.children?.map((child) => (
-
+
))}
);
diff --git a/src/modules/Sidebar/Settings.jsx b/src/modules/Sidebar/Settings.jsx
index e49eff6..50b4f71 100644
--- a/src/modules/Sidebar/Settings.jsx
+++ b/src/modules/Sidebar/Settings.jsx
@@ -1,13 +1,29 @@
import { SliderComponent as Slider } from "../../components/SliderComponent";
import { Button } from "antd";
+import { useFactors } from "../../stores/useFactors";
+import { factorsNameMapper } from "../../config";
export const Settings = () => {
+ const { factors, setWeight } = useFactors();
+
+ const handleAfterChange = (factor, value) => {
+ setWeight(factor, value);
+ };
+
return (
-
-
-
-
+ {Object.entries(factors).map(([field, value]) => {
+ return (
+
handleAfterChange(field, value)}
+ />
+ );
+ })}