parent
9a8dacc38a
commit
2b1494993c
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,22 @@
|
||||
import { Logo } from "../icons/Logo";
|
||||
import { AddressSearch } from "../modules/Sidebar/AddressSearch";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ModeSelector } from "../components/ModeSelector";
|
||||
|
||||
export const MapHeader = ({ isSidebarCollapsed }) => {
|
||||
return (
|
||||
<div className="absolute top-[20px] left-[30px] flex items-center gap-x-10">
|
||||
<div
|
||||
className={twMerge(
|
||||
"hidden",
|
||||
isSidebarCollapsed && "flex items-center gap-x-3 "
|
||||
)}
|
||||
>
|
||||
<Logo />
|
||||
<ModeSelector />
|
||||
</div>
|
||||
|
||||
<AddressSearch />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,215 +0,0 @@
|
||||
import { Popup } from "react-map-gl";
|
||||
import { Button, Col, Row } from "antd";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { usePointSelection } from "../stores/usePointSelection";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CATEGORIES, MODES } from "../config";
|
||||
import { useClickedPointConfig } from "../stores/useClickedPointConfig";
|
||||
import { useMode } from "../stores/useMode";
|
||||
import { useUpdateLayerCounter } from "../stores/useUpdateLayerCounter";
|
||||
import { LAYER_IDS } from "./Layers/constants";
|
||||
|
||||
const popupConfig = [
|
||||
{
|
||||
name: "Id",
|
||||
field: "location_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",
|
||||
},
|
||||
];
|
||||
|
||||
const residentialPointConfig = [
|
||||
{
|
||||
name: "Id",
|
||||
field: "location_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",
|
||||
},
|
||||
];
|
||||
|
||||
const PopupWrapper = ({ lat, lng, onClose, children }) => {
|
||||
return (
|
||||
<Popup
|
||||
longitude={lng}
|
||||
latitude={lat}
|
||||
onClose={onClose}
|
||||
closeOnClick={false}
|
||||
style={{ minWidth: "300px" }}
|
||||
>
|
||||
{children}
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
const SingleFeaturePopup = ({ feature }) => {
|
||||
const { include, selection, exclude } = usePointSelection();
|
||||
const { setClickedPointConfig } = useClickedPointConfig();
|
||||
const { mode } = useMode();
|
||||
const doesMatchFilter = feature.layer.id === LAYER_IDS["initial-match"];
|
||||
const featureId = feature.properties.location_id;
|
||||
|
||||
const { updateCounter } = useUpdateLayerCounter();
|
||||
|
||||
useEffect(() => setClickedPointConfig(featureId, doesMatchFilter), [feature]);
|
||||
|
||||
const isResidential = feature.properties.category === CATEGORIES.residential;
|
||||
|
||||
const config = isResidential ? residentialPointConfig : popupConfig;
|
||||
|
||||
const isSelected =
|
||||
(doesMatchFilter || selection.included.has(featureId)) &&
|
||||
!selection.excluded.has(featureId);
|
||||
|
||||
const handleSelect = () => {
|
||||
if (isSelected) {
|
||||
exclude(featureId);
|
||||
} else {
|
||||
include(featureId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div key={`popup-${updateCounter}`}>
|
||||
{config.map(({ field, name }) => {
|
||||
return (
|
||||
<Row className={twMerge("p-1")} key={field}>
|
||||
<Col className={"font-semibold"} span={12}>
|
||||
{name}
|
||||
</Col>
|
||||
<Col span={12}>{feature.properties[field]}</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{mode === MODES.INITIAL && (
|
||||
<Button
|
||||
type="primary"
|
||||
className="mt-2 mx-auto"
|
||||
block
|
||||
onClick={handleSelect}
|
||||
>
|
||||
{isSelected ? "Исключить из выборки" : "Добавить в выборку"}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MultipleFeaturesPopup = ({ features, onSelect }) => {
|
||||
return (
|
||||
<div className="space-y-2 p-1">
|
||||
{features.map((feature) => {
|
||||
return (
|
||||
<Button
|
||||
type={
|
||||
feature.layer.id === LAYER_IDS["initial-match"] ? "primary" : ""
|
||||
}
|
||||
className="flex items-center gap-x-1"
|
||||
block
|
||||
onClick={() => onSelect(feature)}
|
||||
key={feature.properties.location_id}
|
||||
>
|
||||
<span>{feature.properties.location_id}</span>
|
||||
<span>{feature.properties.category}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MapPopup = ({ features, lat, lng, onClose }) => {
|
||||
const [selectedFeature, setSelectedFeature] = useState(null);
|
||||
|
||||
const getContent = () => {
|
||||
if (features.length === 1) {
|
||||
return <SingleFeaturePopup feature={features[0]} />;
|
||||
}
|
||||
|
||||
if (selectedFeature) {
|
||||
return <SingleFeaturePopup feature={selectedFeature} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultipleFeaturesPopup
|
||||
features={features}
|
||||
onSelect={setSelectedFeature}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupWrapper lat={lat} lng={lng} onClose={onClose}>
|
||||
{getContent()}
|
||||
</PopupWrapper>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,73 @@
|
||||
import { Button } from "antd";
|
||||
import { useState } from "react";
|
||||
import { MODES } from "../../config";
|
||||
import { useMode } from "../../stores/useMode";
|
||||
import { LAYER_IDS } from "../Layers/constants";
|
||||
import { PopupWrapper } from "./PopupWrapper";
|
||||
import { InitialPointPopup } from "./mode-popup/InitialPointPopup";
|
||||
import { ApproveWorkingPointPopup } from "./mode-popup/ApproveWorkingPointPopup";
|
||||
import { WorkingPointPopup } from "./mode-popup/WorkingPointPopup";
|
||||
|
||||
const SingleFeaturePopup = ({ feature }) => {
|
||||
const { mode } = useMode();
|
||||
|
||||
if (mode === MODES.APPROVE_WORKING) {
|
||||
return <ApproveWorkingPointPopup feature={feature} />;
|
||||
}
|
||||
|
||||
if (mode === MODES.WORKING) {
|
||||
return <WorkingPointPopup feature={feature} />;
|
||||
}
|
||||
|
||||
return <InitialPointPopup feature={feature} />;
|
||||
};
|
||||
|
||||
const MultipleFeaturesPopup = ({ features, onSelect }) => {
|
||||
return (
|
||||
<div className="space-y-2 p-1">
|
||||
{features.map((feature) => {
|
||||
return (
|
||||
<Button
|
||||
type={
|
||||
feature.layer.id === LAYER_IDS["initial-match"] ? "primary" : ""
|
||||
}
|
||||
className="flex items-center gap-x-1"
|
||||
block
|
||||
onClick={() => onSelect(feature)}
|
||||
key={feature.properties.location_id}
|
||||
>
|
||||
<span>{feature.properties.location_id}</span>
|
||||
<span>{feature.properties.category}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MapPopup = ({ features, lat, lng, onClose }) => {
|
||||
const [selectedFeature, setSelectedFeature] = useState(null);
|
||||
|
||||
const getContent = () => {
|
||||
if (features.length === 1) {
|
||||
return <SingleFeaturePopup feature={features[0]} />;
|
||||
}
|
||||
|
||||
if (selectedFeature) {
|
||||
return <SingleFeaturePopup feature={selectedFeature} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultipleFeaturesPopup
|
||||
features={features}
|
||||
onSelect={setSelectedFeature}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupWrapper lat={lat} lng={lng} onClose={onClose}>
|
||||
{getContent()}
|
||||
</PopupWrapper>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { Popup } from "react-map-gl";
|
||||
|
||||
export const PopupWrapper = ({ lat, lng, onClose, children }) => {
|
||||
return (
|
||||
<Popup
|
||||
longitude={lng}
|
||||
latitude={lat}
|
||||
onClose={onClose}
|
||||
closeOnClick={false}
|
||||
style={{ minWidth: "300px" }}
|
||||
>
|
||||
{children}
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { useClickedPointConfig } from "../../../stores/useClickedPointConfig";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FeatureProperties } from "./FeatureProperties";
|
||||
import { Title } from "../../../components/Title";
|
||||
import { StatusSelect } from "../../../modules/Table/ApproveAndWorkingTable/ApproveAndWorkingTable";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useUpdateStatus } from "../../../hooks/useUpdateStatus";
|
||||
|
||||
export const ApproveWorkingPointPopup = ({ feature }) => {
|
||||
const featureId = feature.properties.location_id;
|
||||
const { setClickedPointConfig } = useClickedPointConfig();
|
||||
const [status, setStatus] = useState(feature.properties.status);
|
||||
|
||||
useEffect(() => setClickedPointConfig(featureId, false), [feature]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: updateStatus } = useUpdateStatus({
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(["approve-working-points"]);
|
||||
},
|
||||
});
|
||||
|
||||
const handleStatusChange = (value) => {
|
||||
setStatus(value);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
status: value,
|
||||
"location_ids[]": [featureId],
|
||||
});
|
||||
|
||||
updateStatus(params);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeatureProperties feature={feature} dynamicStatus={status} />
|
||||
<div className="flex justify-center mt-4">
|
||||
<div className={"flex flex-col items-center"}>
|
||||
<Title text="Сменить статус" />
|
||||
<StatusSelect value={status} onChange={handleStatusChange} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { CATEGORIES } from "../../../config";
|
||||
import { popupConfig, residentialPointConfig } from "./config";
|
||||
import { Col, Row } from "antd";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const FeatureProperties = ({ feature, dynamicStatus }) => {
|
||||
const isResidential = feature.properties.category === CATEGORIES.residential;
|
||||
const config = isResidential ? residentialPointConfig : popupConfig;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{config.map(({ field, name }) => {
|
||||
const value =
|
||||
dynamicStatus && field === "status"
|
||||
? dynamicStatus
|
||||
: feature.properties[field];
|
||||
|
||||
return (
|
||||
<Row className={twMerge("p-1")} key={field}>
|
||||
<Col className={"font-semibold"} span={12}>
|
||||
{name}
|
||||
</Col>
|
||||
<Col span={12}>{value}</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
import { usePointSelection } from "../../../stores/usePointSelection";
|
||||
import { useClickedPointConfig } from "../../../stores/useClickedPointConfig";
|
||||
import { LAYER_IDS } from "../../Layers/constants";
|
||||
import { useEffect } from "react";
|
||||
import { FeatureProperties } from "./FeatureProperties";
|
||||
import { Button } from "antd";
|
||||
|
||||
export const InitialPointPopup = ({ feature }) => {
|
||||
const { include, selection, exclude } = usePointSelection();
|
||||
const { setClickedPointConfig } = useClickedPointConfig();
|
||||
const doesMatchFilter = feature.layer.id === LAYER_IDS["initial-match"];
|
||||
const featureId = feature.properties.location_id;
|
||||
|
||||
useEffect(() => setClickedPointConfig(featureId, doesMatchFilter), [feature]);
|
||||
|
||||
const isSelected =
|
||||
(doesMatchFilter || selection.included.has(featureId)) &&
|
||||
!selection.excluded.has(featureId);
|
||||
|
||||
const handleSelect = () => {
|
||||
if (isSelected) {
|
||||
exclude(featureId);
|
||||
} else {
|
||||
include(featureId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeatureProperties feature={feature} />
|
||||
<Button
|
||||
type="primary"
|
||||
className="mt-2 mx-auto"
|
||||
block
|
||||
onClick={handleSelect}
|
||||
>
|
||||
{isSelected ? "Исключить из выборки" : "Добавить в выборку"}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import { useClickedPointConfig } from "../../../stores/useClickedPointConfig";
|
||||
import { useEffect } from "react";
|
||||
import { FeatureProperties } from "./FeatureProperties";
|
||||
|
||||
export const WorkingPointPopup = ({ feature }) => {
|
||||
const featureId = feature.properties.location_id;
|
||||
const { setClickedPointConfig } = useClickedPointConfig();
|
||||
|
||||
useEffect(() => setClickedPointConfig(featureId, false), [feature]);
|
||||
|
||||
return <FeatureProperties feature={feature} dynamicStatus={status} />;
|
||||
};
|
||||
@ -0,0 +1,85 @@
|
||||
export const popupConfig = [
|
||||
{
|
||||
name: "Id",
|
||||
field: "location_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",
|
||||
},
|
||||
];
|
||||
|
||||
export const residentialPointConfig = [
|
||||
{
|
||||
name: "Id",
|
||||
field: "location_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",
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,43 @@
|
||||
import { useMode } from "../stores/useMode";
|
||||
import { Button } from "antd";
|
||||
import { AIIcon } from "../icons/AIIcon";
|
||||
import { MODES } from "../config";
|
||||
import { ApproveIcon } from "../icons/ApproveIcon";
|
||||
import { WorkingIcon } from "../icons/WorkingIcon";
|
||||
|
||||
export const ModeSelector = () => {
|
||||
const { mode, setMode } = useMode();
|
||||
|
||||
const handleClick = (selectedMode) => {
|
||||
setMode(selectedMode);
|
||||
};
|
||||
|
||||
const getType = (currentMode) => {
|
||||
if (currentMode === mode) return "primary";
|
||||
|
||||
return "default";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon={<AIIcon />}
|
||||
type={getType(MODES.INITIAL)}
|
||||
onClick={() => handleClick(MODES.INITIAL)}
|
||||
title="Локации к рассмотрению"
|
||||
/>
|
||||
<Button
|
||||
icon={<ApproveIcon />}
|
||||
type={getType(MODES.APPROVE_WORKING)}
|
||||
onClick={() => handleClick(MODES.APPROVE_WORKING)}
|
||||
title="Локации на согласовании"
|
||||
/>
|
||||
<Button
|
||||
icon={<WorkingIcon />}
|
||||
type={getType(MODES.WORKING)}
|
||||
onClick={() => handleClick(MODES.WORKING)}
|
||||
title="Локации в работе"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
export const AIIcon = ({ width = 24, height = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<polygon
|
||||
className="ai-st0"
|
||||
points="11.4,3.1 11.4,4.6 11.4,17.8 11.1,18.6 10.9,19.2 10.4,19.8 9.9,20.1 9.2,20.3 8.6,20.3 8.2,20.3
|
||||
7.6,20.3 7,19.8 6.6,19.3 6.2,18.7 5.4,18.7 4.9,18.4 4.4,18 4.1,17.6 3.9,17.1 3.9,16 3.4,15.4 3,14.9 2,13.4 1.8,12.6 1.4,11.8
|
||||
1.4,9.9 1.4,9 1.9,8.3 2.5,7.7 3,7.3 2.9,6.5 2.8,5.7 3,5.1 3.4,4.7 4.1,4.3 5,4.1 5.6,4 5.6,3.3 5.8,2.7 6.4,1.9 7,1.3 7.6,1.1
|
||||
8.4,0.9 8.9,0.9 9.4,1.1 10.1,1.5 10.8,2.1 "
|
||||
/>
|
||||
<polyline className="ai-st0" points="17.4,3 16.1,4.7 11.5,4.6 " />
|
||||
<polyline className="ai-st0" points="11.2,8.7 16,8.6 19.8,12.3 " />
|
||||
<line className="ai-st0" x1="11.2" y1="12.2" x2="13.4" y2="12.2" />
|
||||
<polyline className="ai-st0" points="11.4,16.6 14.8,16.6 16.4,18.2 " />
|
||||
<g>
|
||||
<circle className="ai-st1" cx="18.1" cy="2" r="1.9" />
|
||||
<circle className="ai-st2" cx="18.1" cy="1.9" r="1.1" />
|
||||
</g>
|
||||
<g>
|
||||
<circle className="ai-st1" cx="18.5" cy="6.5" r="1.9" />
|
||||
<circle className="ai-st2" cx="18.5" cy="6.4" r="1.1" />
|
||||
</g>
|
||||
<g>
|
||||
<circle className="ai-st1" cx="20.8" cy="13.3" r="1.9" />
|
||||
<circle className="ai-st2" cx="20.9" cy="13.3" r="1.1" />
|
||||
</g>
|
||||
<g>
|
||||
<circle className="ai-st1" cx="15" cy="12.2" r="1.9" />
|
||||
<circle className="ai-st2" cx="15" cy="12.1" r="1.1" />
|
||||
</g>
|
||||
<g>
|
||||
<circle className="ai-st1" cx="17.4" cy="19.2" r="1.9" />
|
||||
<circle className="ai-st2" cx="17.4" cy="19.2" r="1.1" />
|
||||
</g>
|
||||
<g>
|
||||
<circle className="ai-st1" cx="13" cy="22" r="1.9" />
|
||||
<circle className="ai-st2" cx="13.1" cy="22" r="1.1" />
|
||||
</g>
|
||||
<polyline
|
||||
className="ai-st0"
|
||||
points="5.9,8.1 4.5,9.5 4.4,11.2 4.8,12.4 6.5,14.8 "
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import "./styles.css";
|
||||
|
||||
export const ApproveIcon = ({ width = 24, height = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-293 385 24 24"
|
||||
width={width}
|
||||
height={height}
|
||||
className="ml-[2px] mt-[2px]"
|
||||
>
|
||||
<polyline
|
||||
className="approve-st0"
|
||||
points="-273.7,390.2 -273.7,388.3 -274,387.5 -274.7,387 -275.8,387 -290.4,387 -291.1,387.2 -291.8,387.6
|
||||
-292.2,388.3 -292.3,403.6 -292.2,404.4 -291.9,404.9 -291.3,405.2 -290.8,405.3 -275.9,405.3 -275.1,405.2 -274.3,404.9
|
||||
-273.8,404.3 -273.8,402.3 "
|
||||
/>
|
||||
<path
|
||||
className="approve-st1"
|
||||
d="M-287.2,397h-1.2c-0.4,0-0.7-0.3-0.7-0.7v-1.2c0-0.4,0.3-0.7,0.7-0.7h1.2c0.4,0,0.7,0.3,0.7,0.7v1.2
|
||||
C-286.6,396.7-286.9,397-287.2,397z"
|
||||
/>
|
||||
<path
|
||||
className="approve-st1"
|
||||
d="M-287.2,400.5h-1.2c-0.4,0-0.7-0.3-0.7-0.7v-1.2c0-0.4,0.3-0.7,0.7-0.7h1.2c0.4,0,0.7,0.3,0.7,0.7v1.2
|
||||
C-286.5,400.2-286.8,400.5-287.2,400.5z"
|
||||
/>
|
||||
<polygon
|
||||
className="approve-st2"
|
||||
points="-288.7,390.7 -288.3,391.2 -288.2,391.3 -288,391.3 -287.6,391.2 -287.2,390.7 -286.7,390.3
|
||||
-286.5,390.3 -286.2,390.3 -286,390.5 -285.9,390.9 -286,391.1 -286.4,391.6 -287.9,392.9 -288.1,392.9 -288.3,392.9 -288.6,392.6
|
||||
-289.6,391.6 -289.8,391.4 -289.8,391 -289.6,390.7 -289.4,390.6 -289.1,390.5 -288.9,390.5 "
|
||||
/>
|
||||
<polygon
|
||||
className="approve-st3"
|
||||
points="-283.9,390.9 -279.1,390.9 -278.8,391.1 -278.7,391.3 -278.6,391.8 -278.7,392 -279,392.4
|
||||
-279.2,392.5 -279.6,392.5 -284,392.5 -284.2,392.5 -284.5,392.3 -284.7,391.9 -284.7,391.7 -284.6,391.5 -284.3,391.1
|
||||
-284.1,390.9 "
|
||||
/>
|
||||
<polygon
|
||||
className="approve-st3"
|
||||
points="-284,394.4 -280,394.4 -279.8,394.6 -279.7,394.8 -279.6,395.2 -279.7,395.5 -279.9,395.9 -280.1,396
|
||||
-280.4,396 -284.1,396 -284.3,395.9 -284.5,395.7 -284.7,395.4 -284.7,395.2 -284.6,394.9 -284.4,394.6 -284.2,394.4 "
|
||||
/>
|
||||
<polygon
|
||||
className="approve-st3"
|
||||
points="-284.1,397.9 -280.9,397.9 -280.7,398.1 -280.6,398.3 -280.6,398.7 -280.6,399 -280.8,399.4
|
||||
-280.9,399.5 -281.2,399.5 -284.2,399.5 -284.3,399.4 -284.5,399.2 -284.7,398.9 -284.7,398.7 -284.6,398.4 -284.4,398.1
|
||||
-284.3,397.9 "
|
||||
/>
|
||||
<polygon
|
||||
className="approve-st2"
|
||||
points="-274.2,391.7 -273.4,391.6 -273.1,391.7 -273,392.7 -272.6,392.8 -272.1,393 -271.6,393.2
|
||||
-271.1,392.6 -270.7,392.8 -270.4,393.1 -270.1,393.5 -270,393.7 -270.6,394.2 -270.5,394.6 -270.4,394.9 -270.3,395.4
|
||||
-269.5,395.4 -269.4,395.7 -269.4,396.2 -269.4,396.4 -269.6,396.7 -269.6,396.9 -270.4,396.8 -270.5,397.3 -270.6,397.7
|
||||
-270.8,397.9 -270.1,398.6 -270.3,398.9 -270.6,399.2 -271.1,399.5 -271.2,399.6 -271.7,399 -271.7,398.9 -272.1,399 -272.4,399.1
|
||||
-272.8,399.2 -273,399.3 -273,400 -273.2,400.2 -274.2,400.2 -274.4,400 -274.4,399.3 -274.4,399.1 -275,398.9 -275.5,398.7
|
||||
-275.9,399.4 -276.2,399.5 -276.5,399.3 -277.1,398.8 -277,398.5 -276.4,397.9 -276.8,397.5 -276.9,397.2 -277.1,396.8
|
||||
-277.9,396.8 -278,396.3 -278,395.6 -277.8,395.3 -277,395.3 -276.7,394.6 -276.6,394.2 -276.9,393.9 -277.1,393.6 -277.2,393.5
|
||||
-277.1,393.2 -276.7,392.7 -276.5,392.6 -276.2,392.7 -275.4,393.2 -275.1,392.9 -274.7,392.8 -274.5,392.7 -274.5,391.8 "
|
||||
/>
|
||||
<circle className="approve-st4" cx="-273.8" cy="396" r="1.8" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
export const FiltersIcon = ({ width = 24, height = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<polygon points="23.7,4.1 23.7,6.1 0.1,6.2 0.1,4.1 " />
|
||||
<g>
|
||||
<circle cx="6.3" cy="5.2" r="3.1" />
|
||||
<circle fill="#fff" cx="6.3" cy="5.2" r="1.4" />
|
||||
</g>
|
||||
<polygon points="23.6,11.1 23.7,13.1 0,13.1 0,11 " />
|
||||
<g>
|
||||
<circle cx="18.6" cy="12.1" r="3.1" />
|
||||
<circle fill="#fff" cx="18.6" cy="12.1" r="1.4" />
|
||||
</g>
|
||||
<polygon points="23.8,18.2 23.9,20.1 0.2,20.2 0.2,18.2 " />
|
||||
<g>
|
||||
<circle cx="10.2" cy="19.1" r="3.1" />
|
||||
<circle fill="#fff" cx="10.2" cy="19.1" r="1.4" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
export const Logo = ({ width = 24, height = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<g>
|
||||
<polygon
|
||||
style={{ fill: "#3B555E" }}
|
||||
points="19.6,24 21.4,24 22.5,23.9 23,23.8 23.5,23.4 23.8,22.9 24,22.3 24,20.4 24,1.9 23.9,1.4 23.6,0.8
|
||||
23.3,0.4 22.9,0.1 22.3,0 14.2,0 8.9,0 11.2,4.6 19.5,4.5 19.6,4.5 "
|
||||
/>
|
||||
<polygon
|
||||
style={{ fill: "#E63941" }}
|
||||
points="13.6,6.4 17.7,6.4 16.2,9.4 14.1,14.2 12.3,18.4 11.7,18.4 9.8,14.5 6.8,8.8 4.5,4.5 4.7,23.9 1.7,24
|
||||
1,23.8 0.5,23.2 0.2,22.7 0,22.1 0,1.8 0.1,1.3 0.4,0.8 0.8,0.4 1.1,0.2 1.4,0.1 2,0.1 7,0.1 11.9,10.6 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
export const WorkingIcon = ({ width = 24, height = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-293 385 24 24"
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<path className="working-st0" d="M-279.1,402.7" />
|
||||
<polyline
|
||||
className="working-st1"
|
||||
points="-275,391.8 -275,390.5 -275.3,389.9 -276,389.6 -277,389.5 -290.8,389.6 -291.4,389.7 -292.1,390
|
||||
-292.5,390.4 -292.5,401.4 -292.5,401.9 -292.1,402.3 -291.6,402.5 -291.1,402.6 -280,402.5 "
|
||||
/>
|
||||
<polyline
|
||||
className="working-st2"
|
||||
points="-290.8,396 -289.3,396 -288.7,398.4 -287.6,394.3 -286.1,399.7 -285.1,392 -284.2,397 -283.2,395.9
|
||||
-281.8,396 "
|
||||
/>
|
||||
<circle className="working-st3" cx="-277" cy="397.6" r="4.6" />
|
||||
<circle className="working-st4" cx="-277.1" cy="397.5" r="2.9" />
|
||||
<polygon
|
||||
className="working-st3"
|
||||
points="-270.9,405.7 -269,403.8 -273.5,399.6 -275.4,401 "
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,75 @@
|
||||
.ai-st0 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.ai-st1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.ai-st2 {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.approve-st0 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.approve-st1 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 0.5;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.approve-st2 {
|
||||
fill: #fa0000;
|
||||
stroke: currentColor;
|
||||
stroke-width: 0.5;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.approve-st3 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 0.5;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.approve-st4 {
|
||||
fill: #ffffff;
|
||||
stroke: currentColor;
|
||||
stroke-width: 0.5;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.working-st0 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 7;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.working-st1 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.working-st2 {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 0.5;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.working-st3 {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.working-st4 {
|
||||
fill: white;
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
import { Button } from "antd";
|
||||
import { MenuOutlined } from "@ant-design/icons";
|
||||
import { FiltersIcon } from "../../icons/FiltersIcon";
|
||||
|
||||
export const FiltersButton = ({ toggleCollapse }) => {
|
||||
return (
|
||||
<Button
|
||||
icon={<MenuOutlined />}
|
||||
icon={<FiltersIcon width={16} height={16} />}
|
||||
onClick={toggleCollapse}
|
||||
className="border-l-0 rounded-bl-none rounded-tl-none absolute top-[100px]"
|
||||
className="border-l-0 rounded-bl-none rounded-tl-none absolute top-[100px] flex items-center justify-center"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import {
|
||||
useGetFilteredInitialPointsCount,
|
||||
useGetTotalInitialPointsCount,
|
||||
} from "../../../api";
|
||||
import { usePointSelection } from "../../../stores/usePointSelection";
|
||||
import { Spin } from "antd";
|
||||
|
||||
export const SelectedLocations = () => {
|
||||
const { data: totalCount, isInitialLoading: isTotalLoading } =
|
||||
useGetTotalInitialPointsCount();
|
||||
const { data: filteredCount, isInitialLoading: isFilteredLoading } =
|
||||
useGetFilteredInitialPointsCount();
|
||||
|
||||
const showSpinner = isTotalLoading || isFilteredLoading;
|
||||
|
||||
const {
|
||||
selection: { excluded },
|
||||
} = usePointSelection();
|
||||
|
||||
return (
|
||||
<div className={"flex items-center justify-between"}>
|
||||
<span>Отобрано локаций</span>
|
||||
|
||||
{showSpinner ? (
|
||||
<Spin />
|
||||
) : (
|
||||
<span>{`${filteredCount - excluded.size} / ${totalCount}`}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in new issue