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 { useMode } from "../../stores/useMode";
|
||||
|
||||
const modeOptions = [
|
||||
{
|
||||
label: "ЛКР",
|
||||
value: MODES.INITIAL,
|
||||
},
|
||||
{
|
||||
label: "ЛВР",
|
||||
value: MODES.APPROVE,
|
||||
},
|
||||
{
|
||||
label: "РП",
|
||||
value: MODES.WORKING,
|
||||
},
|
||||
];
|
||||
import {
|
||||
GiGlowingArtifact,
|
||||
IoAnalyticsSharp,
|
||||
TbTransform,
|
||||
} from "react-icons/all";
|
||||
|
||||
export const Header = () => {
|
||||
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 (
|
||||
<div className="mb-4">
|
||||
<Radio.Group
|
||||
options={modeOptions}
|
||||
onChange={handleModeChange}
|
||||
value={mode}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
LOGO
|
||||
<div className={"flex items-center gap-x-3"}>
|
||||
<Button
|
||||
type={getType(MODES.INITIAL)}
|
||||
className="flex items-center justify-center p-3"
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,60 +1,34 @@
|
||||
import { Button } from "antd";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Select } from "antd";
|
||||
import { Title } from "../../../components/Title";
|
||||
import { useFilters } from "../../../stores/useFilters";
|
||||
import { CATEGORIES } from "../../../config";
|
||||
|
||||
const SelectItem = ({ name, isActive, onClick, disabled }) => {
|
||||
return (
|
||||
<Button
|
||||
block
|
||||
type="text"
|
||||
className={twMerge(
|
||||
"text-left",
|
||||
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue"
|
||||
)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
);
|
||||
const options = Object.entries(CATEGORIES).map(([_key, value]) => {
|
||||
return {
|
||||
value,
|
||||
label: value,
|
||||
};
|
||||
});
|
||||
|
||||
export const CategoriesSelect = ({ disabled }) => {
|
||||
const { filters, setCategories } = useFilters();
|
||||
|
||||
const handleClick = (category) => setCategories(category);
|
||||
|
||||
const clear = () => setCategories([]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<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>
|
||||
<Title text={"Категории"} />
|
||||
|
||||
<div className="space-y-2">
|
||||
{Object.entries(CATEGORIES).map(([key, value]) => (
|
||||
<SelectItem
|
||||
key={key}
|
||||
name={value}
|
||||
isActive={filters.categories.includes(value)}
|
||||
onClick={() => handleClick(value)}
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
placeholder="Выберите категории локаций"
|
||||
onChange={setCategories}
|
||||
options={options}
|
||||
allowClear={true}
|
||||
value={filters.categories}
|
||||
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 { MODES } from "../../config";
|
||||
import { InitialTable } from "./InitialTable/InitialTable";
|
||||
import { ApproveTable } from "./ApproveTable/ApproveTable";
|
||||
import { ApproveAndWorkingTable } from "./ApproveAndWorkingTable/ApproveAndWorkingTable";
|
||||
import { WorkingTable } from "./WorkingTable/WorkingTable";
|
||||
|
||||
export const TableWrapper = () => {
|
||||
export const TableWrapper = ({ fullWidth }) => {
|
||||
const { mode } = useMode();
|
||||
|
||||
if (mode === MODES.APPROVE) {
|
||||
return <ApproveTable />;
|
||||
return <ApproveAndWorkingTable fullWidth={fullWidth} />;
|
||||
}
|
||||
|
||||
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