Add address search

dev
Platon Yasev 3 years ago
parent 958726e783
commit 057a9416b5

@ -11,6 +11,7 @@ import { Table } from "../modules/Table/Table";
import { usePopup } from "../stores/usePopup"; import { usePopup } from "../stores/usePopup";
import { useClickedPointConfig } from "../stores/useClickedPointConfig"; import { useClickedPointConfig } from "../stores/useClickedPointConfig";
import { Legend } from "../modules/Sidebar/Legend"; import { Legend } from "../modules/Sidebar/Legend";
import { AddressSearch } from "../modules/Sidebar/AddressSearch";
export const MapComponent = () => { export const MapComponent = () => {
const mapRef = useRef(null); const mapRef = useRef(null);
@ -110,6 +111,7 @@ export const MapComponent = () => {
<Layers /> <Layers />
<Sidebar /> <Sidebar />
<AddressSearch />
<Legend /> <Legend />
<SignOut /> <SignOut />

@ -16,3 +16,9 @@ export const CATEGORIES = {
export const DISABLED_FILTER_TEXT = export const DISABLED_FILTER_TEXT =
"Фильтр заблокирован - было ручное редактирование"; "Фильтр заблокирован - было ручное редактирование";
export const MODES = {
INITIAL: "INITIAL",
APPROVE: "APPROVE",
WORKING: "WORKING",
};

@ -30,6 +30,10 @@
@apply bg-white-background rounded-xl max-h-[calc(100vh-100px)] overflow-y-auto; @apply bg-white-background rounded-xl max-h-[calc(100vh-100px)] overflow-y-auto;
} }
.ant-modal-header {
border-bottom: none;
}
.mapboxgl-ctrl-group, .mapboxgl-ctrl-group,
.maplibregl-ctrl-group { .maplibregl-ctrl-group {
@apply bg-white-background; @apply bg-white-background;

@ -0,0 +1,82 @@
import { AutoComplete, Input } from "antd";
import { useQuery } from "@tanstack/react-query";
import { SearchOutlined } from "@ant-design/icons";
import { api } from "../../api";
import { useEffect, useMemo, useState } from "react";
import { useMap } from "react-map-gl";
import parse from "wellknown";
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
export const AddressSearch = () => {
const { map } = useMap();
const [value, setValue] = useState("");
const debouncedValue = useDebounce(value);
const { data } = useQuery(["address", debouncedValue], async () => {
const result = await api.get(
`/api/placement_points/search_address?page_size=100&address=${debouncedValue}`
);
return result.data;
});
const options = useMemo(() => {
if (!data) return [];
return data.results.map((item) => ({
label: item.address,
value: `${item.address}$${item.location_id}`,
item: item,
}));
}, [data]);
const handleChange = (value) => {
if (!value) {
setValue(value);
} else {
setValue(value.split("$")[0]);
}
};
const handleSelect = (_value, option) => {
const geometry = parse(option.item.geometry);
map.flyTo({
center: [geometry.coordinates[0], geometry.coordinates[1]],
zoom: 15,
speed: 5,
});
};
return (
<div className="absolute top-[20px] left-[360px]">
<AutoComplete
options={options}
style={{
width: 300,
}}
value={value}
onChange={handleChange}
onSelect={handleSelect}
allowClear={true}
onClear={() => setValue("")}
>
<Input
prefix={<SearchOutlined />}
placeholder="Введите адрес точки"
className="text-ellipsis"
/>
</AutoComplete>
</div>
);
};

@ -0,0 +1,36 @@
import { Radio } from "antd";
import { MODES } from "../../config";
import { useMode } from "../../stores/useMode";
const modeOptions = [
{
label: "ЛКР",
value: MODES.INITIAL,
},
{
label: "ЛВР",
value: MODES.APPROVE,
},
{
label: "РП",
value: MODES.WORKING,
},
];
export const Header = () => {
const { mode, setMode } = useMode();
const handleModeChange = ({ target: { value } }) => setMode(value);
return (
<div className="mb-4">
<Radio.Group
options={modeOptions}
onChange={handleModeChange}
value={mode}
optionType="button"
buttonStyle="solid"
/>
</div>
);
};

@ -7,6 +7,7 @@ import {
import { TakeToWorkButton } from "./TakeToWorkButton"; import { TakeToWorkButton } from "./TakeToWorkButton";
import { Filters } from "./Filters"; import { Filters } from "./Filters";
import { ExportButton } from "./ExportButton"; import { ExportButton } from "./ExportButton";
import { Header } from "./Header";
export const Sidebar = () => { export const Sidebar = () => {
const hasManualEdits = useHasManualEdits(); const hasManualEdits = useHasManualEdits();
@ -14,6 +15,7 @@ export const Sidebar = () => {
return ( 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="absolute top-[20px] left-[20px] bg-white-background w-[320px] rounded-xl p-3 max-h-[calc(100%-40px)] overflow-y-auto z-10">
<Header />
<div className="space-y-5"> <div className="space-y-5">
<LayersVisibility /> <LayersVisibility />
<Filters disabled={hasManualEdits} /> <Filters disabled={hasManualEdits} />

@ -1,10 +1,11 @@
import { Button } from "antd"; import { Alert, Button, Modal } from "antd";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../../api"; import { api } from "../../api";
import { useFilters } from "../../stores/useFilters"; import { useFilters } from "../../stores/useFilters";
import { usePointSelection } from "../../stores/usePointSelection"; import { usePointSelection } from "../../stores/usePointSelection";
import { STATUSES } from "../../config"; import { STATUSES } from "../../config";
import { useUpdateLayerCounter } from "../../stores/useUpdateLayerCounter"; import { useUpdateLayerCounter } from "../../stores/useUpdateLayerCounter";
import { useState } from "react";
export const TakeToWorkButton = () => { export const TakeToWorkButton = () => {
const { filters } = useFilters(); const { filters } = useFilters();
@ -13,6 +14,8 @@ export const TakeToWorkButton = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { toggleUpdateCounter } = useUpdateLayerCounter(); const { toggleUpdateCounter } = useUpdateLayerCounter();
const [isSuccessModalOpened, setIsSuccessModalOpened] = useState(false);
const { mutate } = useMutation({ const { mutate } = useMutation({
mutationFn: () => { mutationFn: () => {
const params = new URLSearchParams({ const params = new URLSearchParams({
@ -30,6 +33,7 @@ export const TakeToWorkButton = () => {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries(["table", 1, filters]); queryClient.invalidateQueries(["table", 1, filters]);
toggleUpdateCounter(); toggleUpdateCounter();
setIsSuccessModalOpened(true);
}, },
}); });
@ -38,8 +42,32 @@ export const TakeToWorkButton = () => {
}; };
return ( return (
<Button type="primary" block className={"mt-2"} onClick={takeToWork}> <>
Взять в работу <Button type="primary" block className={"mt-2"} onClick={takeToWork}>
</Button> Взять в работу
</Button>
<Modal
title={" "}
centered
open={isSuccessModalOpened}
footer={[
<Button
key="ok-button"
type="primary"
onClick={() => setIsSuccessModalOpened(false)}
>
Хорошо
</Button>,
]}
>
<Alert
message="Успешно"
description="Выбранные точки отправлены на согласование. Посмотреть на них можно во второй
вкладке"
type="success"
showIcon
/>
</Modal>
</>
); );
}; };

@ -0,0 +1,15 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
import { MODES } from "../config";
const store = (set) => ({
mode: MODES.INITIAL,
setMode: (mode) => {
set((state) => {
state.mode = mode;
});
},
});
export const useMode = create(immer(store));
Loading…
Cancel
Save