You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
7.1 KiB

import React, { useCallback, useEffect, useRef, useState } from "react";
import { Collapse, Empty, Table as AntdTable } from "antd";
import "./Table.css";
import { useQuery } from "@tanstack/react-query";
import { api } from "../../api";
import parse from "wellknown";
import { useMap } from "react-map-gl";
import { usePointSelection } from "../../stores/usePointSelection";
import { useFilters } from "../../stores/useFilters";
import { useClickedPointConfig } from "../../stores/useClickedPointConfig";
import scrollIntoView from "scroll-into-view-if-needed";
const columns = [
{
title: "Id",
dataIndex: "location_id",
key: "location_id",
width: 50,
ellipsis: true,
},
{
title: "Адрес",
dataIndex: "address",
key: "address",
width: 200,
},
{
title: "Район",
dataIndex: "rayon",
key: "rayon",
width: "120px",
ellipsis: true,
},
{
title: "Округ",
dataIndex: "okrug",
key: "okrug",
width: "120px",
ellipsis: true,
},
{
title: "Название",
dataIndex: "name",
key: "name",
width: "120px",
ellipsis: true,
},
{
title: "Категория",
dataIndex: "category",
key: "category",
width: "120px",
ellipsis: true,
},
{
title: "Статус",
dataIndex: "status",
key: "status",
width: "120px",
ellipsis: true,
},
{
title: "Прогнозный трафик",
dataIndex: "prediction_current",
key: "prediction_current",
width: "120px",
ellipsis: true,
},
];
const PAGE_SIZE = 30;
const useTableData = (page) => {
const [pageSize, setPageSize] = useState(PAGE_SIZE);
const { filters } = useFilters();
const { prediction, status, categories, region } = filters;
const { selection } = usePointSelection();
const { clickedPointConfig } = useClickedPointConfig();
const [finalData, setFinalData] = useState();
const { data } = useQuery(
["table", page, filters, selection],
async () => {
const params = new URLSearchParams({
page,
page_size: pageSize,
"prediction_current[]": prediction,
"status[]": status,
"categories[]": categories,
"included[]": [...selection.included],
});
if (region) {
if (region.type === "ao") {
params.append("ao[]", region.id);
}
if (region.type === "rayon") {
params.append("rayon[]", region.id);
}
}
const { data } = await api.get(
`/api/placement_points?${params.toString()}`
);
return data;
},
{ keepPreviousData: true }
);
useEffect(() => {
if (!data) return;
setFinalData(data);
}, [data]);
const [shouldLoadClickedPoint, setShouldLoadClickedPoint] = useState(false);
useEffect(() => {
if (!data || clickedPointConfig === null) return;
const clickedPoint = data.results.find(
(item) => item.location_id === clickedPointConfig.id
);
if (clickedPoint) {
return;
}
setShouldLoadClickedPoint(true);
}, [data, clickedPointConfig]);
const {
data: remoteClickedPoint,
isInitialLoading,
isFetching,
} = useQuery(
["clicked-point", clickedPointConfig],
async () => {
const params = new URLSearchParams({
"location_ids[]": [clickedPointConfig.id],
});
const { data } = await api.get(
`/api/placement_points?${params.toString()}`
);
return data;
},
{
enabled: shouldLoadClickedPoint,
onSuccess: () => setShouldLoadClickedPoint(false),
}
);
useEffect(() => {
if (!remoteClickedPoint) return;
setPageSize((prevState) => prevState + 1);
setFinalData((prevState) => ({
...prevState,
count: prevState.count + 1,
results: [remoteClickedPoint.results[0], ...prevState.results],
}));
}, [remoteClickedPoint]);
useEffect(() => {
if (clickedPointConfig === null) {
setPageSize(PAGE_SIZE);
setFinalData(data);
}
}, [clickedPointConfig]);
return {
data: finalData,
pageSize,
isClickedPointLoading: isInitialLoading || isFetching,
};
};
export const Table = React.memo(({ height = 200 }) => {
const { map } = useMap();
const tableRef = useRef(null);
const [page, setPage] = useState(1);
const { selection, include, exclude } = usePointSelection();
const { clickedPointConfig } = useClickedPointConfig();
const { data, pageSize, isClickedPointLoading } = useTableData(page);
const SCROLL = {
y: `${height}px`,
x: "max-content",
};
const handlePageChange = useCallback((page) => setPage(page), []);
const getSelectedRowKeys = useCallback(() => {
const ids = data?.results.map((item) => item.location_id) ?? [];
const clickedPoint = data?.results.find(
(item) => item.location_id === clickedPointConfig?.id
);
const inExcludedList = (id) => selection.excluded.has(id);
const shouldNotSelect = (id) =>
id === clickedPoint?.id && clickedPointConfig?.shouldSelect === false;
return [
...ids.filter((id) => {
return !inExcludedList(id) && !shouldNotSelect(id);
}),
...selection.included,
];
}, [data, clickedPointConfig, selection]);
const rowSelection = {
selectedRowKeys: getSelectedRowKeys(),
onSelect: (record, selected) => {
const { location_id: id } = record;
if (selected) {
include(id);
} else {
exclude(id);
}
},
hideSelectAll: true,
};
useEffect(() => {
if (clickedPointConfig === null || isClickedPointLoading) return;
const row = document.querySelector(".scroll-row");
if (row) {
scrollIntoView(row, { behavior: "smooth" });
}
}, [clickedPointConfig, data]);
return (
<div className="w-screen">
<Collapse>
<Collapse.Panel key="1" header="Таблица атрибутов">
<AntdTable
ref={tableRef}
className="table"
size="small"
// rowSelection={rowSelection}
locale={{ emptyText: <Empty description="Нет данных" /> }}
// loading={isLoading}
pagination={{
pageSize,
current: page,
onChange: handlePageChange,
total: data?.count,
showSizeChanger: false,
position: "bottomCenter",
}}
dataSource={data?.results}
columns={columns}
rowKey="location_id"
scroll={SCROLL}
sticky={true}
onRow={(record) => {
return {
onClick: () => {
const geometry = parse(record.geometry);
map.flyTo({
center: [geometry.coordinates[0], geometry.coordinates[1]],
zoom: 15,
speed: 5,
});
},
};
}}
rowSelection={rowSelection}
rowClassName={(record) =>
record.location_id === clickedPointConfig?.id ? "scroll-row" : ""
}
/>
</Collapse.Panel>
</Collapse>
</div>
);
});