Add settings window; make RangeSlider as a dumb component; move necessary data to stores

dev
Platon Yasev 3 years ago
parent f31b7a4c74
commit 93c3e06c52

@ -0,0 +1,29 @@
import { Layer, Source } from "react-map-gl";
import { gridLayer } from "./layers-config";
import { useGridSize } from "../stores/useGridSize";
import { useLayersVisibility } from "../stores/useLayersVisibility";
export const Grid = () => {
const { gridSize } = useGridSize();
const {
isVisible: { grid },
} = useLayersVisibility();
return (
<Source
id="grid"
type="vector"
tiles={[
`http://postamates.spatiality.website/martin/public.${gridSize}/{z}/{x}/{y}.pbf`,
]}
>
<Layer
{...gridLayer}
layout={{
...gridLayer.layout,
visibility: grid ? "visible" : "none",
}}
/>
</Source>
);
};

@ -1,47 +1,28 @@
import { Layer, Source } from "react-map-gl";
import { gridLayer, pointLayer } from "./layers-config";
import {
getGridVisibility,
getPointsVisibility,
useLayersStore,
} from "../stores/useLayersStore";
import { Grid } from "./Grid";
import { useRating } from "../stores/useRating";
export const Layers = () => {
const isPointsVisible = useLayersStore(getPointsVisibility);
const isGridVisible = useLayersStore(getGridVisibility);
const { rate } = useRating();
console.log(rate);
return (
<>
<Source
id="grid"
type="vector"
tiles={[
"https://business.spatiality.website/martin/public.service_polygon/{z}/{x}/{y}.pbf",
]}
>
<Layer
{...gridLayer}
layout={{
...gridLayer.layout,
visibility: isGridVisible ? "visible" : "none",
}}
/>
</Source>
<Source
id="points"
type="vector"
tiles={[
"https://business.spatiality.website/martin/public.service_point/{z}/{x}/{y}.pbf",
]}
>
<Layer
{...pointLayer}
layout={{
...pointLayer.layout,
visibility: isPointsVisible ? "visible" : "none",
}}
/>
</Source>
<Grid />
{/*<Source*/}
{/* id="points"*/}
{/* type="vector"*/}
{/* tiles={[*/}
{/* "https://property.spatiality.website/public.service_geofeature/{z}/{x}/{y}.pbf",*/}
{/* ]}*/}
{/*>*/}
{/* <Layer*/}
{/* {...pointLayer}*/}
{/* layout={{*/}
{/* ...pointLayer.layout,*/}
{/* visibility: isPointsVisible ? "visible" : "none",*/}
{/* }}*/}
{/* />*/}
{/*</Source>*/}
</>
);
};

@ -82,10 +82,10 @@ export const MapComponent = () => {
>
<ExportControl
position="top-left"
PageSize={Size.A3}
PageOrientation={PageOrientation.Portrait}
PageSize={Size.A4}
PageOrientation={PageOrientation.Landscape}
Format={Format.PNG}
DPI={DPI[96]}
DPI={DPI[200]}
Crosshair={true}
PrintableArea={true}
/>

@ -0,0 +1,52 @@
import { Title } from "./Title";
import { Slider } from "antd";
import { useState } from "react";
const Mark = ({ value }) => {
return <span className={"text-grey text-xs"}>{value}</span>;
};
export const SliderComponent = ({
title,
value: initialValue,
onChange,
onAfterChange,
min = 0,
max = 100,
}) => {
const fullRangeMarks = {
[min]: <Mark value={min} />,
[max]: <Mark value={max} />,
};
const [value, setValue] = useState(initialValue);
const [marks, setMarks] = useState(fullRangeMarks);
const handleAfterChange = (value) => {
const [min, max] = value;
setMarks({
...fullRangeMarks,
[min]: <Mark value={min} />,
[max]: <Mark value={max} />,
});
onAfterChange?.(value);
};
const handleChange = (value) => {
setValue(value);
onChange?.(value);
};
return (
<div>
<Title text={title} />
<Slider
range
value={value}
marks={marks}
onChange={handleChange}
onAfterChange={handleAfterChange}
/>
</div>
);
};

@ -5,8 +5,8 @@ const { Text } = Typography;
export const Title = ({ text, className }) => {
return (
<div className={className}>
<Text type="secondary" className={twMerge("uppercase")}>
<div className={twMerge("mb-1", className)}>
<Text type="secondary" className={twMerge("uppercase text-xs")}>
{text}
</Text>
</div>

@ -12,6 +12,6 @@
@apply border-t-white-background;
}
.ant-slider-with-marks {
@apply mb-9;
.ant-popover-inner {
@apply bg-white-background rounded-xl;
}

@ -0,0 +1,29 @@
import { Radio } from "antd";
import { Title } from "../../components/Title";
import { useGridSize } from "../../stores/useGridSize";
const options = [
{ label: "3 мин", value: "net3" },
{ label: "4 мин", value: "net4" },
{ label: "5 мин", value: "net5" },
];
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,15 +1,9 @@
import { Title } from "../../components/Title";
import { Checkbox } from "antd";
import {
getGridVisibility,
getPointsVisibility,
useLayersStore,
} from "../../stores/useLayersStore";
import { useLayersVisibility } from "../../stores/useLayersVisibility";
export const LayersVisibility = () => {
const { toggleVisibility } = useLayersStore();
const isPointsVisible = useLayersStore(getPointsVisibility);
const isGridVisible = useLayersStore(getGridVisibility);
const { toggleVisibility, isVisible } = useLayersVisibility();
return (
<div>
@ -17,7 +11,7 @@ export const LayersVisibility = () => {
<div className={"space-y-1"}>
<Checkbox
onChange={() => toggleVisibility("points")}
checked={isPointsVisible}
checked={isVisible.points}
>
Точки размещения постаматов
</Checkbox>
@ -25,7 +19,7 @@ export const LayersVisibility = () => {
<Checkbox
className={"!ml-0"}
onChange={() => toggleVisibility("grid")}
checked={isGridVisible}
checked={isVisible.grid}
>
Тепловая карта
</Checkbox>

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

@ -1,7 +1,7 @@
import { Button } from "antd";
import { twMerge } from "tailwind-merge";
import { useState } from "react";
import { Title } from "../../components/Title";
import { useActiveTypes } from "../../stores/useActiveTypes";
const types = [
{ id: "kiosk", name: "Городские киоски" },
@ -16,7 +16,7 @@ const SelectItem = ({ name, isActive, onClick }) => {
type="text"
className={twMerge(
"text-left",
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue "
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue"
)}
onClick={onClick}
>
@ -26,14 +26,9 @@ const SelectItem = ({ name, isActive, onClick }) => {
};
export const ObjectTypesSelect = () => {
const [activeTypes, setActiveTypes] = useState([]);
const { activeTypes, setActiveTypes } = useActiveTypes();
const handleClick = (typeId) =>
setActiveTypes((activeTypes) =>
activeTypes.includes(typeId)
? activeTypes.filter((id) => id !== typeId)
: [...activeTypes, typeId]
);
const handleClick = (type) => setActiveTypes(type);
const clear = () => setActiveTypes([]);

@ -1,34 +1,16 @@
import { Slider } from "antd";
import { Title } from "../../components/Title";
import { useState } from "react";
const Mark = ({ value }) => {
return <span className={"text-grey text-xs"}>{value}</span>;
};
const fullRangeMarks = { 0: <Mark value={0} />, 100: <Mark value={100} /> };
import { useRating } from "../../stores/useRating";
import { SliderComponent as Slider } from "../../components/Slider";
export const RatingSlider = () => {
const [marks, setMarks] = useState(fullRangeMarks);
const { rate, setRate } = useRating();
const handleAfterChange = (range) => {
const [min, max] = range;
setMarks({
...fullRangeMarks,
[min]: <Mark value={min} />,
[max]: <Mark value={max} />,
});
};
const handleAfterChange = (range) => setRate(range);
return (
<div>
<Title text={"Востребованность постамата, усл. ед."} />
<Slider
range
defaultValue={[0, 100]}
marks={marks}
onAfterChange={handleAfterChange}
/>
</div>
<Slider
title={"Востребованность постамата, усл. ед."}
value={rate}
onAfterChange={handleAfterChange}
/>
);
};

@ -1,47 +1,50 @@
import { TreeSelect } from "antd";
import { useState } from "react";
import { Title } from "../../components/Title";
import { useRegion } from "../../stores/useRegion";
const { TreeNode } = TreeSelect;
const mockRegions = [
{
id: "ЦАО",
id: "tsao",
name: "Центральный (ЦАО)",
children: [
{ id: "Арбат", name: "Арбат" },
{ id: "Хамовники", name: "Хамовники" },
{ id: "arbat", name: "Арбат" },
{ id: "hamovniki", name: "Хамовники" },
],
},
{
id: "ЮАО",
id: "yuao",
name: "Южный (ЮАО)",
children: [
{ id: "Даниловский", name: "Даниловский" },
{ id: "Нагорный", name: "Нагорный" },
{ id: "danilovsk", name: "Даниловский" },
{ id: "nagorn", name: "Нагорный" },
],
},
];
export const RegionSelect = () => {
const [value, setValue] = useState();
const { region, setRegion } = useRegion();
const onChange = (newValue) => {
setValue(newValue);
const onChange = (value) => {
console.log(value);
setRegion(value);
};
return (
<div>
<Title text={"АО / район"} className={"mb-1"} />
<Title text={"АО / район"} />
<TreeSelect
showSearch
// showSearch
style={{ width: "100%" }}
value={value}
value={region}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="Выберите АО или район"
allowClear
treeDefaultExpandAll
onChange={onChange}
// treeNodeFilterProp="name"
// filterTreeNode={true}
>
{mockRegions.map((parent) => {
return (

@ -0,0 +1,18 @@
import { SliderComponent as Slider } from "../../components/Slider";
import { Button } from "antd";
export const Settings = () => {
return (
<div className={"space-y-2 min-w-[300px]"}>
<Slider title={"Количество жителей"} />
<Slider title={"Количество станций метро"} />
<Slider title={"Количество существующих постаматов"} />
<Slider title={"Whatever"} />
<div>
<Button type="primary" block className={"mt-2"}>
Рассчитать
</Button>
</div>
</div>
);
};

@ -1,29 +1,42 @@
import { RegionSelect } from "./RegionSelect";
import { Button } from "antd";
import { Button, Popover } from "antd";
import { BsChevronLeft } from "react-icons/bs";
import { ObjectTypesSelect } from "./ObjectTypesSelect";
import { RatingSlider } from "./RatingSlider";
import { LayersVisibility } from "./LayersVisibility";
import { GridSizeSelect } from "./GridSizeSelect";
import { ModelSelect } from "./ModelSelect";
import { Settings } from "./Settings";
export const Sidebar = () => {
return (
<div className="absolute top-10 right-10 bg-white-background w-[300px] rounded-xl p-3">
<Button
type="text"
icon={<BsChevronLeft className="mr-3" />}
className="flex items-center p-0 pr-1 text-grey mb-2 hover:bg-transparent focus:bg-transparent"
<Popover
placement="leftTop"
title={"Веса факторов"}
content={Settings}
trigger="click"
>
Настройки
</Button>
<Button
type="text"
icon={<BsChevronLeft className="mr-3" />}
className="flex items-center p-0 pr-1 text-grey mb-2 hover:bg-transparent focus:bg-transparent"
>
Настройки
</Button>
</Popover>
<div className="space-y-5">
<ModelSelect />
<GridSizeSelect />
<LayersVisibility />
<RegionSelect />
<ObjectTypesSelect />
<RatingSlider />
<div className="flex items-center space-x-2">
<Button type="primary">Экспорт данных</Button>
<Button type="primary">Экспорт карты</Button>
<div>
<Button type="primary" block className={"mt-2"}>
Экспорт данных
</Button>
</div>
</div>
</div>

@ -0,0 +1,20 @@
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));

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

@ -1,29 +0,0 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const INITIAL_STATE = {
points: {
visible: true,
filter: {},
},
grid: {
visible: true,
filter: {},
},
};
const store = (set) => ({
layers: INITIAL_STATE,
toggleVisibility: (layerId) =>
set((state) => {
state.layers[layerId].visible = !state.layers[layerId].visible;
}),
});
export const useLayersStore = create(immer(store));
export const getLayerVisibility = (layerId) => (state) =>
state.layers[layerId].visible;
export const getPointsVisibility = getLayerVisibility("points");
export const getGridVisibility = getLayerVisibility("grid");

@ -0,0 +1,17 @@
import create from "zustand";
import { immer } from "zustand/middleware/immer";
const INITIAL_STATE = {
points: true,
grid: true,
};
const store = (set) => ({
isVisible: INITIAL_STATE,
toggleVisibility: (layerId) =>
set((state) => {
state.isVisible[layerId] = !state.isVisible[layerId];
}),
});
export const useLayersVisibility = create(immer(store));

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

@ -0,0 +1,12 @@
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));
Loading…
Cancel
Save