parent
ea19b9938f
commit
c2900763c7
@ -0,0 +1,32 @@
|
||||
import { Layer } from "react-map-gl";
|
||||
import { approvePointLayer } from "./layers-config";
|
||||
import { useLayersVisibility } from "../../stores/useLayersVisibility";
|
||||
import { STATUSES } from "../../config";
|
||||
import { useRegionFilterExpression } from "./useRegionFilterExpression";
|
||||
import { LAYER_IDS } from "./constants";
|
||||
|
||||
const statusExpression = ["==", ["get", "status"], STATUSES.approve];
|
||||
|
||||
export const ApprovePoints = () => {
|
||||
const { isVisible } = useLayersVisibility();
|
||||
const regionFilterExpression = useRegionFilterExpression();
|
||||
|
||||
const filter = regionFilterExpression
|
||||
? ["all", statusExpression, regionFilterExpression]
|
||||
: statusExpression;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layer
|
||||
{...approvePointLayer}
|
||||
id={LAYER_IDS.approve}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
visibility: isVisible[LAYER_IDS.approve] ? "visible" : "none",
|
||||
}}
|
||||
filter={filter}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,114 @@
|
||||
import { Layer } from "react-map-gl";
|
||||
import {
|
||||
matchInitialPointLayer,
|
||||
unmatchInitialPointLayer,
|
||||
} from "./layers-config";
|
||||
import { useLayersVisibility } from "../../stores/useLayersVisibility";
|
||||
import { useFilters } from "../../stores/useFilters";
|
||||
import { usePointSelection } from "../../stores/usePointSelection";
|
||||
import { STATUSES } from "../../config";
|
||||
import { useRegionFilterExpression } from "./useRegionFilterExpression";
|
||||
import { LAYER_IDS } from "./constants";
|
||||
|
||||
const statusExpression = ["==", ["get", "status"], STATUSES.initial];
|
||||
|
||||
const useFilterExpression = () => {
|
||||
const { filters } = useFilters();
|
||||
const { prediction, categories, region } = filters;
|
||||
const { selection } = usePointSelection();
|
||||
const includedArr = [...selection.included];
|
||||
const excludedArr = [...selection.excluded];
|
||||
|
||||
const regionExpression = useRegionFilterExpression();
|
||||
|
||||
const includedExpression = [
|
||||
"in",
|
||||
["get", "location_id"],
|
||||
["literal", includedArr],
|
||||
];
|
||||
const excludedExpression = [
|
||||
"in",
|
||||
["get", "location_id"],
|
||||
["literal", excludedArr],
|
||||
];
|
||||
const predictionExpression = [
|
||||
[">=", ["get", "prediction_current"], prediction[0]],
|
||||
["<=", ["get", "prediction_current"], prediction[1]],
|
||||
];
|
||||
|
||||
const categoryExpression =
|
||||
categories.length > 0
|
||||
? ["in", ["get", "category"], ["literal", categories]]
|
||||
: true;
|
||||
|
||||
const matchFilterExpression = [
|
||||
"all",
|
||||
statusExpression,
|
||||
["!", excludedExpression],
|
||||
[
|
||||
"any",
|
||||
region
|
||||
? ["all", ...predictionExpression, categoryExpression, regionExpression]
|
||||
: ["all", ...predictionExpression, categoryExpression],
|
||||
includedExpression,
|
||||
],
|
||||
];
|
||||
|
||||
const unmatchFilterExpression = [
|
||||
"all",
|
||||
statusExpression,
|
||||
["!", includedExpression],
|
||||
[
|
||||
"any",
|
||||
[
|
||||
"!",
|
||||
region
|
||||
? [
|
||||
"all",
|
||||
...predictionExpression,
|
||||
categoryExpression,
|
||||
regionExpression,
|
||||
]
|
||||
: ["all", ...predictionExpression, categoryExpression],
|
||||
],
|
||||
excludedExpression,
|
||||
],
|
||||
];
|
||||
|
||||
return { match: matchFilterExpression, unmatch: unmatchFilterExpression };
|
||||
};
|
||||
|
||||
export const InitialPoints = () => {
|
||||
const { isVisible } = useLayersVisibility();
|
||||
const { match: matchFilterExpression, unmatch: unmatchFilterExpression } =
|
||||
useFilterExpression();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layer
|
||||
{...matchInitialPointLayer}
|
||||
id={LAYER_IDS["initial-unmatch"]}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
...matchInitialPointLayer.layout,
|
||||
visibility: isVisible[LAYER_IDS.initial] ? "visible" : "none",
|
||||
}}
|
||||
filter={unmatchFilterExpression}
|
||||
paint={unmatchInitialPointLayer.paint}
|
||||
/>
|
||||
<Layer
|
||||
{...matchInitialPointLayer}
|
||||
id={LAYER_IDS["initial-match"]}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
...matchInitialPointLayer.layout,
|
||||
visibility: isVisible[LAYER_IDS.initial] ? "visible" : "none",
|
||||
}}
|
||||
filter={matchFilterExpression}
|
||||
paint={matchInitialPointLayer.paint}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,9 +1,9 @@
|
||||
import { Points } from "./Points";
|
||||
import { Layer, Source } from "react-map-gl";
|
||||
import { aoLayer, rayonLayer, selectedRegionLayer } from "./layers-config";
|
||||
import { useLayersVisibility } from "../stores/useLayersVisibility";
|
||||
import { useFilters } from "../stores/useFilters";
|
||||
import { BASE_URL } from "../api";
|
||||
import { useLayersVisibility } from "../../stores/useLayersVisibility";
|
||||
import { useFilters } from "../../stores/useFilters";
|
||||
import { BASE_URL } from "../../api";
|
||||
|
||||
export const Layers = () => {
|
||||
const {
|
||||
@ -0,0 +1,27 @@
|
||||
import { Source } from "react-map-gl";
|
||||
import { BASE_URL } from "../../api";
|
||||
import { useUpdateLayerCounter } from "../../stores/useUpdateLayerCounter";
|
||||
import { InitialPoints } from "./InitialPoints";
|
||||
import { ApprovePoints } from "./ApprovePoints";
|
||||
import { WorkingPoints } from "./WorkingPoints";
|
||||
|
||||
export const Points = () => {
|
||||
const { updateCounter } = useUpdateLayerCounter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Source
|
||||
id="points"
|
||||
type="vector"
|
||||
key={`points-${updateCounter}`}
|
||||
tiles={[
|
||||
`${BASE_URL}/martin/public.service_placementpoint/{z}/{x}/{y}.pbf`,
|
||||
]}
|
||||
>
|
||||
<InitialPoints />
|
||||
<ApprovePoints />
|
||||
<WorkingPoints />
|
||||
</Source>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { Layer } from "react-map-gl";
|
||||
import { workingPointLayer } from "./layers-config";
|
||||
import { useLayersVisibility } from "../../stores/useLayersVisibility";
|
||||
import { STATUSES } from "../../config";
|
||||
import { useRegionFilterExpression } from "./useRegionFilterExpression";
|
||||
import { LAYER_IDS } from "./constants";
|
||||
|
||||
const statusExpression = ["==", ["get", "status"], STATUSES.working];
|
||||
|
||||
export const WorkingPoints = () => {
|
||||
const { isVisible } = useLayersVisibility();
|
||||
const regionFilterExpression = useRegionFilterExpression();
|
||||
|
||||
const filter = regionFilterExpression
|
||||
? ["all", statusExpression, regionFilterExpression]
|
||||
: statusExpression;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layer
|
||||
{...workingPointLayer}
|
||||
id={LAYER_IDS.working}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
visibility: isVisible[LAYER_IDS.working] ? "visible" : "none",
|
||||
}}
|
||||
filter={filter}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
export const LAYER_IDS = {
|
||||
initial: "initial",
|
||||
"initial-match": "initial-match-points",
|
||||
"initial-unmatch": "initial-unmatch-points",
|
||||
approve: "approve-points",
|
||||
working: "working-points",
|
||||
cancelled: "cancelled-points",
|
||||
atd: "atd",
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
const POINT_SIZE = 5;
|
||||
const UNMATCH_POINT_SIZE = 3;
|
||||
|
||||
const INITIAL_COLOR = "#CC2222";
|
||||
const CANCELLED_COLOR = "#CC2222";
|
||||
const APPROVE_COLOR = "#ff7d00";
|
||||
const WORKING_COLOR = "#006e01";
|
||||
const UNMATCHED_COLOR = "#b4b4b4";
|
||||
|
||||
const DEFAULT_POINT_CONFIG = {
|
||||
type: "circle",
|
||||
paint: {
|
||||
"circle-stroke-width": 0.4,
|
||||
"circle-stroke-color": "#fff",
|
||||
"circle-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
const getPointConfig = (color = INITIAL_COLOR, size = POINT_SIZE) => {
|
||||
return {
|
||||
...DEFAULT_POINT_CONFIG,
|
||||
paint: {
|
||||
...DEFAULT_POINT_CONFIG.paint,
|
||||
"circle-color": color,
|
||||
"circle-radius": size,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const matchInitialPointLayer = getPointConfig();
|
||||
export const unmatchInitialPointLayer = getPointConfig(
|
||||
UNMATCHED_COLOR,
|
||||
UNMATCH_POINT_SIZE
|
||||
);
|
||||
export const approvePointLayer = getPointConfig(APPROVE_COLOR);
|
||||
export const workingPointLayer = getPointConfig(WORKING_COLOR);
|
||||
export const cancelledPointLayer = getPointConfig(CANCELLED_COLOR);
|
||||
|
||||
const regionColor = "#676767";
|
||||
|
||||
export const aoLayer = {
|
||||
id: "ao",
|
||||
type: "line",
|
||||
source: "ao",
|
||||
"source-layer": "public.service_ao",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": regionColor,
|
||||
"line-width": 1.5,
|
||||
"line-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const rayonLayer = {
|
||||
id: "rayon",
|
||||
type: "line",
|
||||
source: "rayon",
|
||||
"source-layer": "public.service_rayon",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": regionColor,
|
||||
"line-width": 0.5,
|
||||
"line-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const selectedRegionLayer = {
|
||||
id: "selected-region",
|
||||
type: "line",
|
||||
source: "selected-region",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": "#47006e",
|
||||
"line-width": 2,
|
||||
"line-opacity": 1,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import { useFilters } from "../../stores/useFilters";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const REGION_FIELD_MAPPER = {
|
||||
ao: "okrug_id",
|
||||
rayon: "rayon_id",
|
||||
};
|
||||
|
||||
export const useRegionFilterExpression = () => {
|
||||
const {
|
||||
filters: { region },
|
||||
} = useFilters();
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
region
|
||||
? ["==", ["get", REGION_FIELD_MAPPER[region.type]], region.id]
|
||||
: null,
|
||||
[region]
|
||||
);
|
||||
};
|
||||
@ -1,121 +0,0 @@
|
||||
import { Layer, Source } from "react-map-gl";
|
||||
import { pointLayer, unmatchPointLayer } from "./layers-config";
|
||||
import { useLayersVisibility } from "../stores/useLayersVisibility";
|
||||
import { BASE_URL } from "../api";
|
||||
import { useFilters } from "../stores/useFilters";
|
||||
import { usePointSelection } from "../stores/usePointSelection";
|
||||
import { useUpdateLayerCounter } from "../stores/useUpdateLayerCounter";
|
||||
|
||||
const getRegionField = (selectedRegion) => {
|
||||
if (!selectedRegion) return null;
|
||||
if (selectedRegion.type === "ao") {
|
||||
return "okrug_id";
|
||||
}
|
||||
|
||||
return "rayon_id";
|
||||
};
|
||||
|
||||
export const Points = () => {
|
||||
const { isVisible } = useLayersVisibility();
|
||||
const { filters } = useFilters();
|
||||
const { prediction, status, categories, region } = filters;
|
||||
const { selection } = usePointSelection();
|
||||
const includedArr = [...selection.included];
|
||||
const excludedArr = [...selection.excluded];
|
||||
|
||||
const { updateCounter } = useUpdateLayerCounter();
|
||||
|
||||
const includedExpression = [
|
||||
"in",
|
||||
["get", "location_id"],
|
||||
["literal", includedArr],
|
||||
];
|
||||
const excludedExpression = [
|
||||
"in",
|
||||
["get", "location_id"],
|
||||
["literal", excludedArr],
|
||||
];
|
||||
const predictionExpression = [
|
||||
[">=", ["get", "prediction_current"], prediction[0]],
|
||||
["<=", ["get", "prediction_current"], prediction[1]],
|
||||
];
|
||||
const statusExpression = ["in", ["get", "status"], ["literal", status]];
|
||||
const categoryExpression =
|
||||
categories.length > 0
|
||||
? ["in", ["get", "category"], ["literal", categories]]
|
||||
: true;
|
||||
|
||||
const regionExpression = ["==", ["get", getRegionField(region)], region?.id];
|
||||
|
||||
const matchFilterExpression = [
|
||||
"all",
|
||||
statusExpression,
|
||||
["!", excludedExpression],
|
||||
[
|
||||
"any",
|
||||
region
|
||||
? ["all", ...predictionExpression, categoryExpression, regionExpression]
|
||||
: ["all", ...predictionExpression, categoryExpression],
|
||||
includedExpression,
|
||||
],
|
||||
];
|
||||
|
||||
const unmatchFilterExpression = [
|
||||
"all",
|
||||
statusExpression,
|
||||
["!", includedExpression],
|
||||
[
|
||||
"any",
|
||||
[
|
||||
"!",
|
||||
region
|
||||
? [
|
||||
"all",
|
||||
...predictionExpression,
|
||||
categoryExpression,
|
||||
regionExpression,
|
||||
]
|
||||
: ["all", ...predictionExpression, categoryExpression],
|
||||
],
|
||||
excludedExpression,
|
||||
],
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Source
|
||||
id="points"
|
||||
type="vector"
|
||||
key={`points-${updateCounter}`}
|
||||
tiles={[
|
||||
`${BASE_URL}/martin/public.service_placementpoint/{z}/{x}/{y}.pbf`,
|
||||
]}
|
||||
>
|
||||
<Layer
|
||||
{...pointLayer}
|
||||
id={"unmatch-points"}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
...pointLayer.layout,
|
||||
visibility: isVisible.points ? "visible" : "none",
|
||||
}}
|
||||
filter={unmatchFilterExpression}
|
||||
paint={unmatchPointLayer.paint}
|
||||
/>
|
||||
<Layer
|
||||
{...pointLayer}
|
||||
id={"match-points"}
|
||||
source={"points"}
|
||||
source-layer={"public.service_placementpoint"}
|
||||
layout={{
|
||||
...pointLayer.layout,
|
||||
visibility: isVisible.points ? "visible" : "none",
|
||||
}}
|
||||
filter={matchFilterExpression}
|
||||
paint={pointLayer.paint}
|
||||
/>
|
||||
</Source>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,109 +0,0 @@
|
||||
//kiosk - городские киоски
|
||||
// mfc - многофункциональные центры предоставления государственных и муниципальных услуг
|
||||
// library - библиотеки
|
||||
// dk - дома культуры и клубы
|
||||
// sport - спортивные объекты
|
||||
|
||||
import { CATEGORIES } from "../config";
|
||||
|
||||
export const pointColors = {
|
||||
[CATEGORIES.kiosk]: "#4561ff",
|
||||
[CATEGORIES.mfc]: "#932301",
|
||||
[CATEGORIES.library]: "#a51eda",
|
||||
[CATEGORIES.dk]: "#ff5204",
|
||||
[CATEGORIES.sport]: "#138c44",
|
||||
[CATEGORIES.residential]: "#f8e502",
|
||||
[CATEGORIES.retail]: "#002398",
|
||||
};
|
||||
|
||||
export const pointLayer = {
|
||||
type: "circle",
|
||||
layout: {},
|
||||
paint: {
|
||||
"circle-color": "#a51eda",
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["get", "prediction_current"],
|
||||
0,
|
||||
0,
|
||||
200,
|
||||
2,
|
||||
300,
|
||||
10,
|
||||
],
|
||||
"circle-stroke-width": 0.4,
|
||||
"circle-stroke-color": "#fff",
|
||||
"circle-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const unmatchPointLayer = {
|
||||
type: "circle",
|
||||
layout: {},
|
||||
paint: {
|
||||
"circle-color": "#b4b4b4",
|
||||
"circle-radius": 3,
|
||||
"circle-stroke-width": 0.4,
|
||||
"circle-stroke-color": "#fff",
|
||||
"circle-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const gridLayer = {
|
||||
type: "fill",
|
||||
layout: {},
|
||||
paint: {
|
||||
"fill-opacity": 0.5,
|
||||
"fill-outline-color": "transparent",
|
||||
},
|
||||
};
|
||||
|
||||
const regionColor = "#676767";
|
||||
|
||||
export const aoLayer = {
|
||||
id: "ao",
|
||||
type: "line",
|
||||
source: "ao",
|
||||
"source-layer": "public.service_ao",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": regionColor,
|
||||
"line-width": 1.5,
|
||||
"line-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const rayonLayer = {
|
||||
id: "rayon",
|
||||
type: "line",
|
||||
source: "rayon",
|
||||
"source-layer": "public.service_rayon",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": regionColor,
|
||||
"line-width": 0.5,
|
||||
"line-opacity": 0.8,
|
||||
},
|
||||
};
|
||||
|
||||
export const selectedRegionLayer = {
|
||||
id: "selected-region",
|
||||
type: "line",
|
||||
source: "selected-region",
|
||||
layout: {
|
||||
"line-join": "round",
|
||||
"line-cap": "round",
|
||||
},
|
||||
paint: {
|
||||
"line-color": "#47006e",
|
||||
"line-width": 2,
|
||||
"line-opacity": 1,
|
||||
},
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
import { api } from "../../api";
|
||||
|
||||
export const exportData = async (params) => {
|
||||
const { data } = await api.get(
|
||||
`/api/placement_points/to_excel?${params.toString()}`,
|
||||
{ responseType: "arraybuffer" }
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
@ -1,17 +1,31 @@
|
||||
import { create } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
import { LAYER_IDS } from "../Map/Layers/constants";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
points: true,
|
||||
atd: true,
|
||||
[LAYER_IDS.initial]: true,
|
||||
[LAYER_IDS.approve]: false,
|
||||
[LAYER_IDS.working]: false,
|
||||
[LAYER_IDS.atd]: true,
|
||||
};
|
||||
|
||||
const store = (set) => ({
|
||||
isVisible: INITIAL_STATE,
|
||||
|
||||
toggleVisibility: (layerId) =>
|
||||
set((state) => {
|
||||
state.isVisible[layerId] = !state.isVisible[layerId];
|
||||
}),
|
||||
|
||||
setLayersVisibility: (config) =>
|
||||
set((state) => {
|
||||
config.visible.forEach((layerId) => {
|
||||
state.isVisible[layerId] = true;
|
||||
});
|
||||
config.invisible.forEach((layerId) => {
|
||||
state.isVisible[layerId] = false;
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
export const useLayersVisibility = create(immer(store));
|
||||
|
||||
Loading…
Reference in new issue