Add TakeToWork action

dev
Platon Yasev 3 years ago
parent c13d404776
commit cbe5aebb41

@ -9,11 +9,13 @@ import { SignOut } from "../SignOut";
import debounce from "lodash.debounce";
import { Table } from "../modules/Table/Table";
import { usePopup } from "../stores/usePopup";
import { useClickedPoint } from "../stores/useClickedPoint";
export const MapComponent = () => {
const mapRef = useRef(null);
const mapContainerRef = useRef(null);
const { popup, setPopup } = usePopup();
const { setClickedPoint } = useClickedPoint();
const handleClick = (event) => {
if (!event.features) {
@ -94,7 +96,10 @@ export const MapComponent = () => {
lat={popup.coordinates[1]}
lng={popup.coordinates[0]}
features={popup.features}
onClose={() => setPopup(null)}
onClose={() => {
setPopup(null);
setClickedPoint(null);
}}
/>
)}

@ -2,24 +2,95 @@ import { Popup } from "react-map-gl";
import { Button, Col, Row } from "antd";
import { twMerge } from "tailwind-merge";
import { usePointSelection } from "../stores/usePointSelection";
import { useState } from "react";
import { useEffect, useState } from "react";
import { CATEGORIES } from "../config";
import { useQuery } from "@tanstack/react-query";
import { api } from "../api";
import { useClickedPoint } from "../stores/useClickedPoint";
const popupConfig = [
{
name: "Id",
field: "id",
name: "id",
},
{
field: "name",
name: "Адрес",
field: "address",
},
{
name: "Район",
field: "rayon_id",
},
{
name: "Округ",
field: "okrug_id",
},
{
name: "Название",
field: "name",
},
{
name: "Категория",
field: "category",
name: "Тип",
},
{
name: "Статус",
field: "status",
},
{
name: "Прогнозный трафик",
field: "prediction_current",
},
];
const residentialPointConfig = [
{
name: "Id",
field: "id",
},
{
name: "Адрес",
field: "address",
},
{
name: "Район",
field: "rayon_id",
},
{
name: "Округ",
field: "okrug_id",
},
{
name: "Название",
field: "name",
},
{
name: "Категория",
field: "category",
},
{
name: "Статус",
field: "status",
},
{
name: "Прогнозный трафик",
field: "prediction_current",
},
{
name: "Кол-во квартир",
field: "flat_cnt",
},
{
name: "Год постройки",
field: "year_bld",
},
{
name: "Кол-во этажей",
field: "levels",
},
{
name: "Материал стен",
field: "mat_nes",
},
];
@ -28,9 +99,9 @@ const PopupWrapper = ({ lat, lng, onClose, children }) => {
<Popup
longitude={lng}
latitude={lat}
anchor="bottom"
onClose={onClose}
closeOnClick={false}
style={{ minWidth: "300px" }}
>
{children}
</Popup>
@ -39,17 +110,44 @@ const PopupWrapper = ({ lat, lng, onClose, children }) => {
const SingleFeaturePopup = ({ feature, onSelect }) => {
const { include, selection, exclude } = usePointSelection();
const { setClickedPoint } = useClickedPoint();
const doesMatchFilter = feature.layer.id === "match-points";
const featureId = feature.properties.id;
const locationId = feature.properties.location_id;
const { data } = useQuery(["clicked-point", locationId], async () => {
const params = new URLSearchParams({
"location_ids[]": [locationId],
});
const { data } = await api.get(
`/api/placement_points?${params.toString()}`
);
return data;
});
useEffect(() => {
if (!data) {
return;
}
setClickedPoint(data.results[0]);
}, [data]);
const isResidential = feature.properties.category === CATEGORIES.residential;
const config = isResidential ? residentialPointConfig : popupConfig;
const isSelected =
(doesMatchFilter || selection.included.has(feature.properties.id)) &&
!selection.excluded.has(feature.properties.id);
(doesMatchFilter || selection.included.has(featureId)) &&
!selection.excluded.has(featureId);
const handleSelect = () => {
if (isSelected) {
exclude(feature.properties.id);
exclude(featureId);
} else {
include(feature.properties.id);
include(featureId);
}
onSelect();
};
@ -57,13 +155,13 @@ const SingleFeaturePopup = ({ feature, onSelect }) => {
return (
<>
<div>
{popupConfig.map(({ field, name }) => {
{config.map(({ field, name }) => {
return (
<Row className={twMerge("p-1")} key={field}>
<Col className={"font-semibold"} span={15}>
<Col className={"font-semibold"} span={12}>
{name}
</Col>
<Col span={9}>{feature.properties[field]}</Col>
<Col span={12}>{feature.properties[field]}</Col>
</Row>
);
})}

@ -1,20 +1,15 @@
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 STATUSES = {
toWork: "К рассмотрению",
approve: "Согласование-установка",
working: "Работает",
};
export const CATEGORIES = {
kiosk: "Городской киоск",
mfc: "МФЦ",
library: "Библиотека",
sport: "Спортивный объект",
retail: "Ритейл",
residential: "Подъезд жилого дома",
culture: "Дом культуры/Клуб",
};

@ -21,6 +21,11 @@
@apply border-t-grey-light;
}
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip,
.maplibregl-popup-anchor-top .maplibregl-popup-tip {
@apply border-b-grey-light;
}
.ant-popover-inner {
@apply bg-white-background rounded-xl max-h-[calc(100vh-100px)] overflow-y-auto;
}

@ -3,22 +3,7 @@ import { twMerge } from "tailwind-merge";
import { Title } from "../../components/Title";
import { useFilters } from "../../stores/useFilters";
import { useHasManualEdits } from "../../stores/usePointSelection";
//kiosk - городские киоски
// mfc - многофункциональные центры предоставления государственных и муниципальных услуг
// library - библиотеки
// dk - дома культуры и клубы
// sport - спортивные объекты
const types = [
"Городской киоск",
"МФЦ",
"Библиотека",
"Спортивный объект",
"Ритейл",
"Подъезд жилого дома",
"Дом культуры/Клуб",
];
import { CATEGORIES } from "../../config";
const SelectItem = ({ name, isActive, onClick, disabled }) => {
return (
@ -48,7 +33,7 @@ export const ObjectTypesSelect = () => {
return (
<div>
<div className="flex justify-between items-center mb-1">
<Title text={"Тип объекта размещения"} />
<Title text={"Категория объекта размещения"} />
{filters.categories.length !== 0 && (
<Button
type="text"
@ -62,12 +47,12 @@ export const ObjectTypesSelect = () => {
</div>
<div className="space-y-2">
{types.map((type) => (
{Object.entries(CATEGORIES).map(([key, value]) => (
<SelectItem
key={type}
name={type}
isActive={filters.categories.includes(type)}
onClick={() => handleClick(type)}
key={key}
name={value}
isActive={filters.categories.includes(value)}
onClick={() => handleClick(value)}
disabled={hasManualEdits}
/>
))}

@ -8,11 +8,15 @@ import { useHasManualEdits } from "../../stores/usePointSelection";
export const PredictionSlider = () => {
const { filters, setPrediction } = useFilters();
const hasManualEdits = useHasManualEdits();
const { data } = useQuery(["max-min"], async () => {
const { data } = useQuery(
["max-min"],
async () => {
const { data } = await api.get(`/api/placement_points/filters`);
return data;
});
},
{ enabled: false }
);
const handleAfterChange = (range) => setPrediction(range);
@ -29,7 +33,7 @@ export const PredictionSlider = () => {
return (
<Slider
title={"Прогнозный трафик, чел."}
title={"Прогнозный трафик"}
value={filters.prediction}
onAfterChange={handleAfterChange}
min={200}

@ -37,9 +37,7 @@ export const RegionSelect = () => {
const getRegions = async () => {
setLoading(true);
try {
const response = await api.get(
"https://postnet-dev.selftech.ru/api/ao_and_rayons"
);
const response = await api.get("/api/ao_and_rayons");
setData(response.data);
} catch (err) {
console.error(err);

@ -10,6 +10,7 @@ import {
useHasManualEdits,
usePointSelection,
} from "../../stores/usePointSelection";
import { TakeToWorkButton } from "./TakeToWorkButton";
function download(filename, data) {
const downloadLink = window.document.createElement("a");
@ -83,7 +84,7 @@ export const Sidebar = () => {
};
return (
<div className="absolute top-[20px] left-[20px] bg-white-background w-[300px] rounded-xl p-3 max-h-[calc(100%-40px)] overflow-y-auto z-10">
<div className="absolute top-[20px] left-[20px] bg-white-background w-[320px] rounded-xl p-3 max-h-[calc(100%-40px)] overflow-y-auto z-10">
<div className="space-y-5">
<LayersVisibility />
<RegionSelect />
@ -100,9 +101,7 @@ export const Sidebar = () => {
>
Экспорт данных
</Button>
<Button type="primary" block className={"mt-2"} disabled={true}>
Взять в работу
</Button>
<TakeToWorkButton />
{hasManualEdits ? (
<Button
type="text"

@ -0,0 +1,40 @@
import { Button } from "antd";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../../api";
import { useFilters } from "../../stores/useFilters";
import { usePointSelection } from "../../stores/usePointSelection";
import { STATUSES } from "../../config";
export const TakeToWorkButton = () => {
const { filters } = useFilters();
const { prediction, categories } = filters;
const { selection } = usePointSelection();
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: () => {
const params = new URLSearchParams({
status: STATUSES.toWork,
"prediction_current[]": prediction,
"categories[]": categories,
"included[]": [...selection.included],
"excluded[]": [...selection.excluded],
});
return api.put(
`/api/placement_points/update_status?${params.toString()}`
);
},
onSuccess: () => queryClient.invalidateQueries(["table", 1, filters]),
});
const takeToWork = () => {
mutate();
};
return (
<Button type="primary" block className={"mt-2"} onClick={takeToWork}>
Взять в работу
</Button>
);
};

@ -7,75 +7,61 @@ import parse from "wellknown";
import { useMap } from "react-map-gl";
import { usePointSelection } from "../../stores/usePointSelection";
import { useFilters } from "../../stores/useFilters";
import { useClickedPoint } from "../../stores/useClickedPoint";
const columns = [
{
title: "Id",
dataIndex: "id",
key: "id",
width: "20px",
width: 50,
ellipsis: true,
},
{
title: "Статус",
dataIndex: "status",
key: "status",
title: "Адрес",
dataIndex: "address",
key: "address",
width: 200,
},
{
title: "Район",
dataIndex: "rayon",
key: "rayon",
width: "120px",
ellipsis: true,
},
{
title: "Категория",
dataIndex: "category",
key: "category",
title: "Округ",
dataIndex: "okrug",
key: "okrug",
width: "120px",
ellipsis: true,
},
{
title: "Прогноз",
dataIndex: "prediction_current",
key: "prediction_current",
title: "Название",
dataIndex: "name",
key: "name",
width: "120px",
ellipsis: true,
},
// {
// title: "Зрелость",
// dataIndex: "age",
// key: "age",
// width: "120px",
// ellipsis: true,
// },
// {
// title: "План",
// dataIndex: "plan",
// key: "plan",
// width: "120px",
// ellipsis: true,
// },
// {
// title: "Факт",
// dataIndex: "fact",
// key: "fact",
// width: "120px",
// ellipsis: true,
// },
// {
// title: "Дельта",
// dataIndex: "delta",
// key: "delta",
// width: "120px",
// ellipsis: true,
// },
{
title: "АО",
dataIndex: "okrug",
key: "okrug",
title: "Категория",
dataIndex: "category",
key: "category",
width: "120px",
ellipsis: true,
},
{
title: "Район",
dataIndex: "rayon",
key: "rayon",
title: "Статус",
dataIndex: "status",
key: "status",
width: "120px",
ellipsis: true,
},
{
title: "Прогнозный трафик",
dataIndex: "prediction_current",
key: "prediction_current",
width: "120px",
ellipsis: true,
},
@ -91,6 +77,9 @@ export const Table = React.memo(({ height = 200 }) => {
const { filters } = useFilters();
const { prediction, status, categories } = filters;
const { include, selection, exclude } = usePointSelection();
const { point } = useClickedPoint();
console.log(point);
const { data } = useQuery(
["table", page, filters],

@ -0,0 +1,18 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const store = (set) => ({
point: null,
setClickedPoint: (point) => {
set((state) => {
if (!point) {
state.point = null;
return state;
}
state.point = point;
});
},
});
export const useClickedPoint = create(immer(store));
Loading…
Cancel
Save