diff --git a/src/Map/Points.jsx b/src/Map/Points.jsx index 7437f34..07b0fa4 100644 --- a/src/Map/Points.jsx +++ b/src/Map/Points.jsx @@ -39,10 +39,11 @@ export const Points = () => { const unmatchFilterExpression = [ "all", statusExpression, + ["!", includedExpression], [ "any", - excludedExpression, ["!", ["all", ...predictionExpression, categoryExpression]], + excludedExpression, ], ]; diff --git a/src/Map/Popup.jsx b/src/Map/Popup.jsx index 425f606..7d66e58 100644 --- a/src/Map/Popup.jsx +++ b/src/Map/Popup.jsx @@ -157,6 +157,7 @@ const SingleFeaturePopup = ({ feature }) => { }; const MultipleFeaturesPopup = ({ features, onSelect }) => { + console.log(features); return (
{features.map((feature) => { diff --git a/src/components/SliderComponent.jsx b/src/components/SliderComponent.jsx index 864a4e7..9dcee42 100644 --- a/src/components/SliderComponent.jsx +++ b/src/components/SliderComponent.jsx @@ -32,6 +32,8 @@ export const SliderComponent = ({ range = false, step = 1, disabled = false, + onMouseEnter, + onMouseLeave, }) => { const fullRangeMarks = { [min]: , @@ -71,7 +73,7 @@ export const SliderComponent = ({ }; return ( -
+
<Slider range={range} diff --git a/src/config.js b/src/config.js index 0e4507d..fa698fb 100644 --- a/src/config.js +++ b/src/config.js @@ -1,5 +1,5 @@ export const STATUSES = { - toWork: "К рассмотрению", + initial: "К рассмотрению", approve: "Согласование-установка", working: "Работает", }; @@ -13,3 +13,6 @@ export const CATEGORIES = { residential: "Подъезд жилого дома", dk: "Дом культуры/Клуб", }; + +export const DISABLED_FILTER_TEXT = + "Фильтр заблокирован - было ручное редактирование"; diff --git a/src/modules/Sidebar/ObjectTypesSelect.jsx b/src/modules/Sidebar/CategoriesSelect.jsx similarity index 86% rename from src/modules/Sidebar/ObjectTypesSelect.jsx rename to src/modules/Sidebar/CategoriesSelect.jsx index bc08ffd..822807f 100644 --- a/src/modules/Sidebar/ObjectTypesSelect.jsx +++ b/src/modules/Sidebar/CategoriesSelect.jsx @@ -2,7 +2,6 @@ import { Button } from "antd"; import { twMerge } from "tailwind-merge"; import { Title } from "../../components/Title"; import { useFilters } from "../../stores/useFilters"; -import { useHasManualEdits } from "../../stores/usePointSelection"; import { CATEGORIES } from "../../config"; const SelectItem = ({ name, isActive, onClick, disabled }) => { @@ -22,9 +21,8 @@ const SelectItem = ({ name, isActive, onClick, disabled }) => { ); }; -export const ObjectTypesSelect = () => { +export const CategoriesSelect = ({ disabled }) => { const { filters, setCategories } = useFilters(); - const hasManualEdits = useHasManualEdits(); const handleClick = (category) => setCategories(category); @@ -39,7 +37,7 @@ export const ObjectTypesSelect = () => { type="text" className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit" onClick={clear} - disabled={hasManualEdits} + disabled={disabled} > сбросить </Button> @@ -53,7 +51,7 @@ export const ObjectTypesSelect = () => { name={value} isActive={filters.categories.includes(value)} onClick={() => handleClick(value)} - disabled={hasManualEdits} + disabled={disabled} /> ))} </div> diff --git a/src/modules/Sidebar/ExportButton.jsx b/src/modules/Sidebar/ExportButton.jsx new file mode 100644 index 0000000..f40f254 --- /dev/null +++ b/src/modules/Sidebar/ExportButton.jsx @@ -0,0 +1,74 @@ +import { useFilters } from "../../stores/useFilters"; +import { usePointSelection } from "../../stores/usePointSelection"; +import { useQuery } from "@tanstack/react-query"; +import { api } from "../../api"; +import { Button } from "antd"; +import { useState } from "react"; + +function download(filename, data) { + const downloadLink = window.document.createElement("a"); + downloadLink.href = window.URL.createObjectURL( + new Blob([data], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }) + ); + downloadLink.download = filename; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} + +const useExportData = (enabled, onSuccess, onSettled) => { + const { filters } = useFilters(); + const { prediction, status, categories } = filters; + const { selection } = usePointSelection(); + + return useQuery( + ["export", filters, selection], + async () => { + const params = new URLSearchParams({ + "prediction_current[]": prediction, + "status[]": status, + "categories[]": categories, + "included[]": [...selection.included], + "excluded[]": [...selection.excluded], + }); + + const { data } = await api.get( + `/api/placement_points/to_excel?${params.toString()}`, + { responseType: "arraybuffer" } + ); + + return data; + }, + { enabled, onSuccess, onSettled } + ); +}; + +export const ExportButton = () => { + const [startExport, setStartExport] = useState(false); + useExportData( + startExport, + (data) => { + download("postamates.xlsx", data); + }, + () => setStartExport(false) + ); + + const handleExport = () => { + setStartExport(true); + }; + + return ( + <Button + type="primary" + block + className={"mt-2"} + onClick={handleExport} + loading={startExport} + disabled={startExport} + > + Экспорт данных + </Button> + ); +}; diff --git a/src/modules/Sidebar/Filters.jsx b/src/modules/Sidebar/Filters.jsx new file mode 100644 index 0000000..46c231a --- /dev/null +++ b/src/modules/Sidebar/Filters.jsx @@ -0,0 +1,39 @@ +import { RegionSelect } from "./RegionSelect"; +import { CategoriesSelect } from "./CategoriesSelect"; +import { PredictionSlider } from "./PredictionSlider"; +import { useEffect, useState } from "react"; +import { Tooltip } from "antd"; +import { DISABLED_FILTER_TEXT } from "../../config"; + +export const Filters = ({ disabled }) => { + const [hover, setHover] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => setHover(false), 1500); + + return () => clearTimeout(timer); + }, [hover]); + + const handleMouseEnter = () => setHover(true); + const handleMouseLeave = () => { + setHover(false); + }; + + return ( + <Tooltip + title={DISABLED_FILTER_TEXT} + placement="right" + open={disabled && hover} + > + <div + className="space-y-5" + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + > + <RegionSelect disabled={disabled} /> + <CategoriesSelect disabled={disabled} /> + <PredictionSlider disabled={disabled} /> + </div> + </Tooltip> + ); +}; diff --git a/src/modules/Sidebar/PredictionSlider.jsx b/src/modules/Sidebar/PredictionSlider.jsx index 44cc5c9..1275e92 100644 --- a/src/modules/Sidebar/PredictionSlider.jsx +++ b/src/modules/Sidebar/PredictionSlider.jsx @@ -3,15 +3,14 @@ import { useFilters } from "../../stores/useFilters"; import { useQuery } from "@tanstack/react-query"; import { api } from "../../api"; import { useEffect } from "react"; -import { useHasManualEdits } from "../../stores/usePointSelection"; -export const PredictionSlider = () => { +export const PredictionSlider = ({ disabled }) => { const { filters, setPrediction } = useFilters(); - const hasManualEdits = useHasManualEdits(); + const { data } = useQuery( ["max-min"], async () => { - const { data } = await api.get(`/api/placement_points/filters`); + const { data } = await api.get(`/api/placement_points/filters/`); return data; }, @@ -39,7 +38,7 @@ export const PredictionSlider = () => { min={200} max={299} range - disabled={hasManualEdits} + disabled={disabled} /> ); }; diff --git a/src/modules/Sidebar/RegionSelect.jsx b/src/modules/Sidebar/RegionSelect.jsx index 9a439cd..d892fca 100644 --- a/src/modules/Sidebar/RegionSelect.jsx +++ b/src/modules/Sidebar/RegionSelect.jsx @@ -23,7 +23,7 @@ const normalizeRegions = (rawRegions) => { }, {}); }; -export const RegionSelect = () => { +export const RegionSelect = ({ disabled }) => { const { current: map } = useMap(); const { filters: { region }, @@ -38,6 +38,7 @@ export const RegionSelect = () => { setLoading(true); try { const response = await api.get("/api/ao_and_rayons"); + console.log(response.data); setData(response.data); } catch (err) { console.error(err); @@ -93,6 +94,7 @@ export const RegionSelect = () => { description={"Не найдено"} /> } + disabled={disabled} > {data?.map((parent) => { return ( diff --git a/src/modules/Sidebar/Sidebar.jsx b/src/modules/Sidebar/Sidebar.jsx index 7d657ae..5c5e4ad 100644 --- a/src/modules/Sidebar/Sidebar.jsx +++ b/src/modules/Sidebar/Sidebar.jsx @@ -1,106 +1,24 @@ -import { RegionSelect } from "./RegionSelect"; import { Button } from "antd"; -import { ObjectTypesSelect } from "./ObjectTypesSelect"; -import { PredictionSlider } from "./PredictionSlider"; import { LayersVisibility } from "./LayersVisibility"; -import { useState } from "react"; -import { api } from "../../api"; -import { useFilters } from "../../stores/useFilters"; import { useHasManualEdits, usePointSelection, } from "../../stores/usePointSelection"; import { TakeToWorkButton } from "./TakeToWorkButton"; - -function download(filename, data) { - const downloadLink = window.document.createElement("a"); - downloadLink.href = window.URL.createObjectURL( - new Blob([data], { - type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }) - ); - downloadLink.download = filename; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); -} - -const getRegionParam = (regionId) => { - if (!regionId) return null; - - const [type, id] = regionId.split("-"); - - if (type === "ao") { - return { msk_ao: Number(id) }; - } - - return { msk_rayon: Number(id) - 1 }; -}; +import { Filters } from "./Filters"; +import { ExportButton } from "./ExportButton"; export const Sidebar = () => { - const { - filters: { prediction, region, categories }, - } = useFilters(); - - const [isExporting, setIsExporting] = useState(false); const hasManualEdits = useHasManualEdits(); const { reset: resetSelection } = usePointSelection(); - const handleExport = async () => { - setIsExporting(true); - try { - const params = { - filters: { - rate_from: prediction[0], - rate_to: prediction[1], - }, - }; - - if (region) { - params.filters = { - ...params.filters, - ...getRegionParam(region), - }; - } - - if (categories.length) { - params.filters = { - ...params.filters, - category: categories, - }; - } - - const resp = await api.post("/api/raschet/", params, { - responseType: "arraybuffer", - }); - const blob = resp.data; - - download("postamates.xlsx", blob); - } catch (err) { - console.log("Произошла ошибка"); - } finally { - setIsExporting(false); - } - }; - return ( <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 /> - <ObjectTypesSelect /> - <PredictionSlider /> + <Filters disabled={hasManualEdits} /> <div> - <Button - type="primary" - block - className={"mt-2"} - onClick={handleExport} - loading={isExporting} - disabled={true} - > - Экспорт данных - </Button> + <ExportButton /> <TakeToWorkButton /> {hasManualEdits ? ( <Button diff --git a/src/modules/Sidebar/TakeToWorkButton.jsx b/src/modules/Sidebar/TakeToWorkButton.jsx index 1fd57f0..f63d803 100644 --- a/src/modules/Sidebar/TakeToWorkButton.jsx +++ b/src/modules/Sidebar/TakeToWorkButton.jsx @@ -14,7 +14,7 @@ export const TakeToWorkButton = () => { const { mutate } = useMutation({ mutationFn: () => { const params = new URLSearchParams({ - status: STATUSES.toWork, + status: STATUSES.approve, "prediction_current[]": prediction, "categories[]": categories, "included[]": [...selection.included],