parent
740c85390a
commit
525a63dfb0
@ -0,0 +1,17 @@
|
|||||||
|
import { Button, Popover } from "antd";
|
||||||
|
import { BiLayer } from "react-icons/all";
|
||||||
|
import { LayersVisibility } from "./LayersVisibility";
|
||||||
|
|
||||||
|
export const LayersControl = () => {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
content={<LayersVisibility />}
|
||||||
|
trigger="click"
|
||||||
|
placement={"leftBottom"}
|
||||||
|
>
|
||||||
|
<Button className="absolute bottom-[20px] right-[20px] flex items-center justify-center p-3">
|
||||||
|
<BiLayer className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { Title } from "../components/Title";
|
||||||
|
|
||||||
|
export function Legend() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-[10px] left-[10px] text-xs text-grey z-10">
|
||||||
|
<Title
|
||||||
|
text={"Размер кружка пропорционален прогнозному трафику"}
|
||||||
|
className={"text-center mb-1"}
|
||||||
|
classNameText={"lowercase"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,74 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import { MenuOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
export const FiltersButton = ({ toggleCollapse }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
icon={<MenuOutlined />}
|
||||||
|
onClick={toggleCollapse}
|
||||||
|
className="border-l-0 rounded-bl-none rounded-tl-none absolute top-[100px]"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,36 +1,51 @@
|
|||||||
import { Radio } from "antd";
|
import { Button } from "antd";
|
||||||
import { MODES } from "../../config";
|
import { MODES } from "../../config";
|
||||||
import { useMode } from "../../stores/useMode";
|
import { useMode } from "../../stores/useMode";
|
||||||
|
import {
|
||||||
const modeOptions = [
|
GiGlowingArtifact,
|
||||||
{
|
IoAnalyticsSharp,
|
||||||
label: "ЛКР",
|
TbTransform,
|
||||||
value: MODES.INITIAL,
|
} from "react-icons/all";
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "ЛВР",
|
|
||||||
value: MODES.APPROVE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "РП",
|
|
||||||
value: MODES.WORKING,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const { mode, setMode } = useMode();
|
const { mode, setMode } = useMode();
|
||||||
|
|
||||||
const handleModeChange = ({ target: { value } }) => setMode(value);
|
const handleClick = (selectedMode) => {
|
||||||
|
setMode(selectedMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getType = (currentMode) => {
|
||||||
|
if (currentMode === mode) return "primary";
|
||||||
|
|
||||||
|
return "default";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<Radio.Group
|
LOGO
|
||||||
options={modeOptions}
|
<div className={"flex items-center gap-x-3"}>
|
||||||
onChange={handleModeChange}
|
<Button
|
||||||
value={mode}
|
type={getType(MODES.INITIAL)}
|
||||||
optionType="button"
|
className="flex items-center justify-center p-3"
|
||||||
buttonStyle="solid"
|
onClick={() => handleClick(MODES.INITIAL)}
|
||||||
/>
|
>
|
||||||
|
<GiGlowingArtifact />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={getType(MODES.APPROVE)}
|
||||||
|
className="flex items-center justify-center p-3"
|
||||||
|
onClick={() => handleClick(MODES.APPROVE)}
|
||||||
|
>
|
||||||
|
<TbTransform />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={getType(MODES.WORKING)}
|
||||||
|
className="flex items-center justify-center p-3"
|
||||||
|
onClick={() => handleClick(MODES.WORKING)}
|
||||||
|
>
|
||||||
|
<IoAnalyticsSharp />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,60 +1,34 @@
|
|||||||
import { Button } from "antd";
|
import { Select } from "antd";
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
import { Title } from "../../../components/Title";
|
import { Title } from "../../../components/Title";
|
||||||
import { useFilters } from "../../../stores/useFilters";
|
import { useFilters } from "../../../stores/useFilters";
|
||||||
import { CATEGORIES } from "../../../config";
|
import { CATEGORIES } from "../../../config";
|
||||||
|
|
||||||
const SelectItem = ({ name, isActive, onClick, disabled }) => {
|
const options = Object.entries(CATEGORIES).map(([_key, value]) => {
|
||||||
return (
|
return {
|
||||||
<Button
|
value,
|
||||||
block
|
label: value,
|
||||||
type="text"
|
};
|
||||||
className={twMerge(
|
});
|
||||||
"text-left",
|
|
||||||
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue"
|
|
||||||
)}
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CategoriesSelect = ({ disabled }) => {
|
export const CategoriesSelect = ({ disabled }) => {
|
||||||
const { filters, setCategories } = useFilters();
|
const { filters, setCategories } = useFilters();
|
||||||
|
|
||||||
const handleClick = (category) => setCategories(category);
|
|
||||||
|
|
||||||
const clear = () => setCategories([]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-1">
|
<Title text={"Категории"} />
|
||||||
<Title text={"Категория объекта размещения"} />
|
|
||||||
{filters.categories.length !== 0 && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit"
|
|
||||||
onClick={clear}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
сбросить
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<Select
|
||||||
{Object.entries(CATEGORIES).map(([key, value]) => (
|
mode="tags"
|
||||||
<SelectItem
|
style={{
|
||||||
key={key}
|
width: "100%",
|
||||||
name={value}
|
}}
|
||||||
isActive={filters.categories.includes(value)}
|
placeholder="Выберите категории локаций"
|
||||||
onClick={() => handleClick(value)}
|
onChange={setCategories}
|
||||||
disabled={disabled}
|
options={options}
|
||||||
/>
|
allowClear={true}
|
||||||
))}
|
value={filters.categories}
|
||||||
</div>
|
disabled={disabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { Title } from "../../components/Title";
|
|
||||||
import { pointColors } from "../../Map/layers-config";
|
|
||||||
|
|
||||||
export function Legend() {
|
|
||||||
return (
|
|
||||||
<div className="absolute bottom-[20px] right-[20px] bg-white-background w-[250px] rounded-xl p-4 text-xs text-grey z-10">
|
|
||||||
<Title
|
|
||||||
text={"Категории объекта объекта размещения"}
|
|
||||||
className={"text-center"}
|
|
||||||
/>
|
|
||||||
<Title
|
|
||||||
text={"Размер кружка пропорционален прогнозному трафику"}
|
|
||||||
className={"text-center mb-1"}
|
|
||||||
classNameText={"lowercase"}
|
|
||||||
/>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{Object.entries(pointColors).map(([label, color]) => (
|
|
||||||
<div className="flex gap-2 items-center" key={label}>
|
|
||||||
<span
|
|
||||||
className="rounded-xl w-3 h-3 inline-block"
|
|
||||||
style={{ backgroundColor: color }}
|
|
||||||
/>
|
|
||||||
<span>{label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { download } from "../../../utils";
|
||||||
|
import { exportData } from "../utils";
|
||||||
|
import { STATUSES } from "../../../config";
|
||||||
|
|
||||||
|
export const useExportApproveAndWorkingData = (enabled, onSettled) => {
|
||||||
|
return useQuery(
|
||||||
|
["export-approve-working"],
|
||||||
|
async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"status[]": [STATUSES.approve, STATUSES.working],
|
||||||
|
});
|
||||||
|
|
||||||
|
return await exportData(params);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
download("postamates.xlsx", data);
|
||||||
|
},
|
||||||
|
onSettled,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import { DownloadOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
export const ExportButton = ({ provider }) => {
|
||||||
|
const [startExport, setStartExport] = useState(false);
|
||||||
|
|
||||||
|
provider(startExport, () => setStartExport(false));
|
||||||
|
|
||||||
|
const handleExport = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setStartExport(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={handleExport} loading={startExport} disabled={startExport}>
|
||||||
|
<DownloadOutlined />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { ExportButton } from "./ExportButton";
|
||||||
|
|
||||||
|
export const HeaderWrapper = ({
|
||||||
|
leftColumn,
|
||||||
|
rightColumn,
|
||||||
|
exportProvider,
|
||||||
|
classes,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex items-center w-full justify-between"}>
|
||||||
|
<div className={classes?.leftColumn}>
|
||||||
|
<span className="py-[5px]">Таблица атрибутов</span>
|
||||||
|
{leftColumn}
|
||||||
|
</div>
|
||||||
|
<div className={classes?.rightColumn}>
|
||||||
|
{rightColumn}
|
||||||
|
{exportProvider && <ExportButton provider={exportProvider} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { useFilters } from "../../../stores/useFilters";
|
||||||
|
import { usePointSelection } from "../../../stores/usePointSelection";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { download } from "../../../utils";
|
||||||
|
import { exportData } from "../utils";
|
||||||
|
|
||||||
|
export const useExportInitialData = (enabled, onSettled) => {
|
||||||
|
const { filters } = useFilters();
|
||||||
|
const { prediction, status, categories } = filters;
|
||||||
|
const { selection } = usePointSelection();
|
||||||
|
|
||||||
|
return useQuery(
|
||||||
|
["export-initial", filters, selection],
|
||||||
|
async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"prediction_current[]": prediction,
|
||||||
|
"status[]": status,
|
||||||
|
"categories[]": categories,
|
||||||
|
"included[]": [...selection.included],
|
||||||
|
"excluded[]": [...selection.excluded],
|
||||||
|
});
|
||||||
|
|
||||||
|
return await exportData(params);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
download("postamates.xlsx", data);
|
||||||
|
},
|
||||||
|
onSettled,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,19 +1,19 @@
|
|||||||
import { useMode } from "../../stores/useMode";
|
import { useMode } from "../../stores/useMode";
|
||||||
import { MODES } from "../../config";
|
import { MODES } from "../../config";
|
||||||
import { InitialTable } from "./InitialTable/InitialTable";
|
import { InitialTable } from "./InitialTable/InitialTable";
|
||||||
import { ApproveTable } from "./ApproveTable/ApproveTable";
|
import { ApproveAndWorkingTable } from "./ApproveAndWorkingTable/ApproveAndWorkingTable";
|
||||||
import { WorkingTable } from "./WorkingTable/WorkingTable";
|
import { WorkingTable } from "./WorkingTable/WorkingTable";
|
||||||
|
|
||||||
export const TableWrapper = () => {
|
export const TableWrapper = ({ fullWidth }) => {
|
||||||
const { mode } = useMode();
|
const { mode } = useMode();
|
||||||
|
|
||||||
if (mode === MODES.APPROVE) {
|
if (mode === MODES.APPROVE) {
|
||||||
return <ApproveTable />;
|
return <ApproveAndWorkingTable fullWidth={fullWidth} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === MODES.WORKING) {
|
if (mode === MODES.WORKING) {
|
||||||
return <WorkingTable />;
|
return <WorkingTable fullWidth={fullWidth} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <InitialTable />;
|
return <InitialTable fullWidth={fullWidth} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { download } from "../../../utils";
|
||||||
|
import { exportData } from "../utils";
|
||||||
|
import { STATUSES } from "../../../config";
|
||||||
|
|
||||||
|
export const useExportWorkingData = (enabled, onSettled) => {
|
||||||
|
return useQuery(
|
||||||
|
["export-working"],
|
||||||
|
async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"status[]": [STATUSES.working],
|
||||||
|
});
|
||||||
|
|
||||||
|
return await exportData(params);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
download("postamates.xlsx", data);
|
||||||
|
},
|
||||||
|
onSettled,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
export 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);
|
||||||
|
}
|
||||||
Loading…
Reference in new issue