parent
581a312238
commit
f73e799271
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
function s(r,i){for(var o=0;o<i.length;o++){const e=i[o];if(typeof e!="string"&&!Array.isArray(e)){for(const t in e)if(t!=="default"&&!(t in r)){const n=Object.getOwnPropertyDescriptor(e,t);n&&Object.defineProperty(r,t,n.get?n:{enumerable:!0,get:()=>e[t]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}var a={},c=a.printMsg=function(){console.log("This is a message from the demo package")};const f=s({__proto__:null,printMsg:c,default:a},[a]);export{f as i};
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 15 KiB |
@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8"/>
|
|
||||||
<link href="/vite.svg" rel="icon" type="image/svg+xml"/>
|
|
||||||
<link href="/favicon.ico" rel="icon"/>
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
|
||||||
<title>PostNet by Spatial</title>
|
|
||||||
<script crossorigin src="/assets/index.17466bb7.js" type="module"></script>
|
|
||||||
<link href="/assets/index.ca3dcc1b.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,52 @@
|
|||||||
|
import { Layer, Source } from "react-map-gl";
|
||||||
|
import { selectedRegionLayer } from "./layers-config";
|
||||||
|
import { usePendingPointsFilters } from "../../stores/usePendingPointsFilters";
|
||||||
|
import { useOnApprovalPointsFilters } from "../../stores/useOnApprovalPointsFilters";
|
||||||
|
import { useWorkingPointsFilters } from "../../stores/useWorkingPointsFilters";
|
||||||
|
import { useMode } from "../../stores/useMode";
|
||||||
|
import { MODES } from "../../config";
|
||||||
|
|
||||||
|
const SelectedRegionLayer = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<Source id="selected-region" type="geojson" data={data}>
|
||||||
|
<Layer {...selectedRegionLayer} />
|
||||||
|
</Source>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectedRegion = () => {
|
||||||
|
const {
|
||||||
|
filters: { region: pendingRegion },
|
||||||
|
} = usePendingPointsFilters();
|
||||||
|
const {
|
||||||
|
filters: { region: onApprovalRegion },
|
||||||
|
} = useOnApprovalPointsFilters();
|
||||||
|
const {
|
||||||
|
filters: { region: workingRegion },
|
||||||
|
} = useWorkingPointsFilters();
|
||||||
|
|
||||||
|
const { mode } = useMode();
|
||||||
|
|
||||||
|
const shouldRenderPendingRegion =
|
||||||
|
mode === MODES.PENDING && pendingRegion?.geometry;
|
||||||
|
const shouldRenderOnApprovalRegion =
|
||||||
|
mode === MODES.ON_APPROVAL && onApprovalRegion?.geometry;
|
||||||
|
const shouldRenderWorkingRegion =
|
||||||
|
mode === MODES.WORKING && workingRegion?.geometry;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shouldRenderPendingRegion && (
|
||||||
|
<SelectedRegionLayer data={pendingRegion.geometry} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldRenderOnApprovalRegion && (
|
||||||
|
<SelectedRegionLayer data={onApprovalRegion.geometry} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldRenderWorkingRegion && (
|
||||||
|
<SelectedRegionLayer data={workingRegion.geometry} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
|
||||||
|
export const ClearFiltersButton = ({ onClick, disabled }) => {
|
||||||
|
return (
|
||||||
|
<Button block className={"mt-2"} onClick={onClick} disabled={disabled}>
|
||||||
|
Сбросить фильтры
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import { RegionSelect } from "./RegionSelect";
|
|
||||||
import { CategoriesSelect } from "./InitialSidebar/CategoriesSelect";
|
|
||||||
import { PredictionSlider } from "./InitialSidebar/PredictionSlider";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Tooltip } from "antd";
|
|
||||||
import { DISABLED_FILTER_TEXT, MODES } from "../../config";
|
|
||||||
import { useMode } from "../../stores/useMode";
|
|
||||||
import { DeltaTrafficSlider } from "./WorkingFilters/DeltaSlider";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { api } from "../../api";
|
|
||||||
import { FactTrafficSlider } from "./WorkingFilters/FactTrafficSlider";
|
|
||||||
import { AgeSlider } from "./WorkingFilters/AgeSlider";
|
|
||||||
|
|
||||||
export const Filters = ({ disabled }) => {
|
|
||||||
const [hover, setHover] = useState(false);
|
|
||||||
const { mode } = useMode();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => setHover(false), 1500);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [hover]);
|
|
||||||
|
|
||||||
const { data: fullRange } = useQuery(["max-min"], async () => {
|
|
||||||
const { data } = await api.get(`/api/placement_points/filters/`);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setHover(true);
|
|
||||||
};
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setHover(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
title={DISABLED_FILTER_TEXT}
|
|
||||||
placement="right"
|
|
||||||
open={disabled && hover}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<div className="space-y-5">
|
|
||||||
<RegionSelect disabled={disabled} />
|
|
||||||
{mode === MODES.INITIAL && (
|
|
||||||
<>
|
|
||||||
<CategoriesSelect disabled={disabled} />
|
|
||||||
<PredictionSlider disabled={disabled} fullRange={fullRange} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{mode === MODES.WORKING && (
|
|
||||||
<div className={"space-y-12"}>
|
|
||||||
<DeltaTrafficSlider fullRange={fullRange} />
|
|
||||||
<FactTrafficSlider fullRange={fullRange} />
|
|
||||||
<AgeSlider fullRange={fullRange} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { Logo } from "../../icons/Logo";
|
|
||||||
import { ModeSelector } from "../../components/ModeSelector";
|
|
||||||
|
|
||||||
export const Header = () => {
|
|
||||||
return (
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<Logo />
|
|
||||||
<div className={"flex items-center gap-x-3"}>
|
|
||||||
<ModeSelector />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { Title } from "../../../components/Title";
|
||||||
|
import { Checkbox } from "antd";
|
||||||
|
import { LAYER_IDS } from "../../../Map/Layers/constants";
|
||||||
|
import { RegionSelect } from "../../../components/RegionSelect";
|
||||||
|
import { useOnApprovalPointsFilters } from "../../../stores/useOnApprovalPointsFilters";
|
||||||
|
import { useLayersVisibility } from "../../../stores/useLayersVisibility";
|
||||||
|
import { ClearFiltersButton } from "../../../components/ClearFiltersButton";
|
||||||
|
|
||||||
|
export const OnApprovalPointsFilters = () => {
|
||||||
|
const {
|
||||||
|
filters: { region },
|
||||||
|
setRegion,
|
||||||
|
clear,
|
||||||
|
} = useOnApprovalPointsFilters();
|
||||||
|
const { isVisible, toggleVisibility, showLayers } = useLayersVisibility();
|
||||||
|
|
||||||
|
const hasActiveFilters =
|
||||||
|
region ||
|
||||||
|
!isVisible[LAYER_IDS.approve] ||
|
||||||
|
!isVisible[LAYER_IDS.working] ||
|
||||||
|
!isVisible[LAYER_IDS.cancelled];
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
clear();
|
||||||
|
showLayers([LAYER_IDS.approve, LAYER_IDS.working, LAYER_IDS.cancelled]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<RegionSelect value={region?.id} onChange={setRegion} />
|
||||||
|
<div>
|
||||||
|
<Title text="Статусы" />
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<Checkbox
|
||||||
|
onChange={() => toggleVisibility(LAYER_IDS.approve)}
|
||||||
|
checked={isVisible[LAYER_IDS.approve]}
|
||||||
|
>
|
||||||
|
Согласование-установка
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
className={"!ml-0"}
|
||||||
|
onChange={() => toggleVisibility(LAYER_IDS.working)}
|
||||||
|
checked={isVisible[LAYER_IDS.working]}
|
||||||
|
>
|
||||||
|
Работает
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
className={"!ml-0"}
|
||||||
|
onChange={() => toggleVisibility(LAYER_IDS.cancelled)}
|
||||||
|
checked={isVisible[LAYER_IDS.cancelled]}
|
||||||
|
>
|
||||||
|
Отменен
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{hasActiveFilters && <ClearFiltersButton onClick={clearFilters} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
import { DISABLED_FILTER_TEXT } from "../../../config";
|
||||||
|
import { Button, Tooltip } from "antd";
|
||||||
|
import { SelectedLocations } from "./SelectedLocations";
|
||||||
|
import { TakeToWorkButton } from "./TakeToWorkButton";
|
||||||
|
import { RegionSelect } from "../../../components/RegionSelect";
|
||||||
|
import { CategoriesSelect } from "./CategoriesSelect";
|
||||||
|
import { PredictionSlider } from "./PredictionSlider";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
useHasManualEdits,
|
||||||
|
usePointSelection,
|
||||||
|
} from "../../../stores/usePointSelection";
|
||||||
|
import { usePendingPointsFilters } from "../../../stores/usePendingPointsFilters";
|
||||||
|
import { ClearFiltersButton } from "../../../components/ClearFiltersButton";
|
||||||
|
import { getDynamicActiveFilters } from "../utils";
|
||||||
|
|
||||||
|
export const PendingPointsFilters = ({ fullRange }) => {
|
||||||
|
const hasManualEdits = useHasManualEdits();
|
||||||
|
const { reset: resetPointSelection } = usePointSelection();
|
||||||
|
const { filters, setRegion, clear } = usePendingPointsFilters();
|
||||||
|
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => setHover(false), 1500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [hover]);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setHover(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setHover(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeDynamicFilters = getDynamicActiveFilters(filters, fullRange, [
|
||||||
|
"prediction",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const clearFilters = () => clear(fullRange?.prediction);
|
||||||
|
|
||||||
|
const hasActiveFilters =
|
||||||
|
filters.region ||
|
||||||
|
activeDynamicFilters.prediction ||
|
||||||
|
filters.categories.length !== 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col flex-1 justify-between">
|
||||||
|
<div>
|
||||||
|
<Tooltip
|
||||||
|
title={DISABLED_FILTER_TEXT}
|
||||||
|
placement="right"
|
||||||
|
open={hasManualEdits && hover}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<div className="space-y-5">
|
||||||
|
<RegionSelect
|
||||||
|
disabled={hasManualEdits}
|
||||||
|
value={filters.region?.id}
|
||||||
|
onChange={setRegion}
|
||||||
|
/>
|
||||||
|
<CategoriesSelect disabled={hasManualEdits} />
|
||||||
|
<PredictionSlider disabled={hasManualEdits} fullRange={fullRange} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<ClearFiltersButton
|
||||||
|
onClick={clearFilters}
|
||||||
|
disabled={hasManualEdits}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{hasManualEdits ? (
|
||||||
|
<Button block className={"mt-2"} onClick={resetPointSelection}>
|
||||||
|
Отменить ручное редактирование
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<SelectedLocations />
|
||||||
|
<TakeToWorkButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,20 +1,23 @@
|
|||||||
import { INITIAL, useFilters } from "../../../stores/useFilters";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { SliderComponent as Slider } from "../../../components/SliderComponent";
|
import { SliderComponent as Slider } from "../../../components/SliderComponent";
|
||||||
|
import {
|
||||||
|
INITIAL,
|
||||||
|
useWorkingPointsFilters,
|
||||||
|
} from "../../../stores/useWorkingPointsFilters";
|
||||||
|
|
||||||
export const FactTrafficSlider = ({ fullRange }) => {
|
export const FactTrafficSlider = ({ fullRange }) => {
|
||||||
const {
|
const {
|
||||||
filters: { factTraffic },
|
filters: { factTraffic },
|
||||||
setFactTraffic,
|
setFactTraffic,
|
||||||
} = useFilters();
|
} = useWorkingPointsFilters();
|
||||||
|
|
||||||
const handleAfterChange = (range) => setFactTraffic(range);
|
const handleAfterChange = (range) => setFactTraffic(range);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fullRange) return;
|
if (!fullRange) return;
|
||||||
|
|
||||||
const min = fullRange.fact[0];
|
const min = fullRange.factTraffic[0];
|
||||||
const max = fullRange.fact[1];
|
const max = fullRange.factTraffic[1];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
factTraffic[0] === INITIAL.factTraffic[0] &&
|
factTraffic[0] === INITIAL.factTraffic[0] &&
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { RegionSelect } from "../../../components/RegionSelect";
|
||||||
|
import { useWorkingPointsFilters } from "../../../stores/useWorkingPointsFilters";
|
||||||
|
import { DeltaTrafficSlider } from "./DeltaSlider";
|
||||||
|
import { FactTrafficSlider } from "./FactTrafficSlider";
|
||||||
|
import { AgeSlider } from "./AgeSlider";
|
||||||
|
import { ClearFiltersButton } from "../../../components/ClearFiltersButton";
|
||||||
|
import { getDynamicActiveFilters } from "../utils";
|
||||||
|
|
||||||
|
export const WorkingPointsFilters = ({ fullRange }) => {
|
||||||
|
const { filters, setRegion, clear } = useWorkingPointsFilters();
|
||||||
|
|
||||||
|
const activeDynamicFilters = getDynamicActiveFilters(filters, fullRange, [
|
||||||
|
"deltaTraffic",
|
||||||
|
"factTraffic",
|
||||||
|
"age",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hasActiveFilters =
|
||||||
|
filters.region ||
|
||||||
|
activeDynamicFilters.deltaTraffic ||
|
||||||
|
activeDynamicFilters.factTraffic ||
|
||||||
|
activeDynamicFilters.age;
|
||||||
|
|
||||||
|
const handleClear = () => clear(fullRange);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RegionSelect value={filters.region?.id} onChange={setRegion} />
|
||||||
|
<div className={"space-y-12 mt-4"}>
|
||||||
|
<DeltaTrafficSlider fullRange={fullRange} />
|
||||||
|
<FactTrafficSlider fullRange={fullRange} />
|
||||||
|
<AgeSlider fullRange={fullRange} />
|
||||||
|
</div>
|
||||||
|
{hasActiveFilters && <ClearFiltersButton onClick={handleClear} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
export const getDynamicActiveFilters = (filters, fullRange, props) => {
|
||||||
|
if (!fullRange || !props) return false;
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
props.forEach((prop) => {
|
||||||
|
result[prop] =
|
||||||
|
filters[prop][0] !== fullRange[prop][0] ||
|
||||||
|
filters[prop][1] !== fullRange[prop][1];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@ -1,134 +0,0 @@
|
|||||||
import { Table } from "../Table";
|
|
||||||
import { columns } from "../InitialTable/columns";
|
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { getPoints } from "../../../api";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { PAGE_SIZE } from "../constants";
|
|
||||||
import { STATUSES } from "../../../config";
|
|
||||||
import { useMergeTableData } from "../useMergeTableData";
|
|
||||||
import { Button } from "antd";
|
|
||||||
import { useUpdateStatus } from "../../../hooks/useUpdateStatus";
|
|
||||||
import { HeaderWrapper } from "../HeaderWrapper";
|
|
||||||
import { useExportApproveAndWorkingData } from "./useExportApproveAndWorkingData";
|
|
||||||
import { useFilters } from "../../../stores/useFilters";
|
|
||||||
import { usePopup } from "../../../stores/usePopup";
|
|
||||||
import { StatusSelect } from "../../../components/StatusSelect";
|
|
||||||
|
|
||||||
const ChangeStatusButton = ({ selectedIds, selectedStatus }) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { setPopup } = usePopup();
|
|
||||||
|
|
||||||
const { mutate: updateStatus } = useUpdateStatus({
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(["approve-working-points"]);
|
|
||||||
setPopup(null);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClick = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
status: selectedStatus,
|
|
||||||
"location_ids[]": selectedIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateStatus(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button type="primary" onClick={handleClick}>
|
|
||||||
Обновить статус
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Header = ({ selectedIds, onClearSelected }) => {
|
|
||||||
const [status, setStatus] = useState(STATUSES.initial);
|
|
||||||
|
|
||||||
const handleClear = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onClearSelected();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HeaderWrapper
|
|
||||||
leftColumn={
|
|
||||||
selectedIds.length > 0 && (
|
|
||||||
<>
|
|
||||||
<StatusSelect value={status} onChange={setStatus} />
|
|
||||||
<ChangeStatusButton
|
|
||||||
selectedIds={selectedIds}
|
|
||||||
selectedStatus={status}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rightColumn={
|
|
||||||
selectedIds.length > 0 && (
|
|
||||||
<Button onClick={handleClear}>Очистить все</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
classes={{
|
|
||||||
leftColumn: "flex items-center gap-x-4",
|
|
||||||
rightColumn: "flex item-center gap-x-4",
|
|
||||||
}}
|
|
||||||
exportProvider={useExportApproveAndWorkingData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ApproveAndWorkingTable = ({ fullWidth }) => {
|
|
||||||
const [pageSize, setPageSize] = useState(PAGE_SIZE);
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [selectedIds, setSelectedIds] = useState([]);
|
|
||||||
const {
|
|
||||||
filters: { region },
|
|
||||||
} = useFilters();
|
|
||||||
|
|
||||||
const clearSelected = () => setSelectedIds([]);
|
|
||||||
|
|
||||||
const { data, isInitialLoading } = useQuery(
|
|
||||||
["approve-working-points", page, region],
|
|
||||||
async () => {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
page,
|
|
||||||
page_size: pageSize,
|
|
||||||
"status[]": [STATUSES.approve, STATUSES.working, STATUSES.cancelled],
|
|
||||||
});
|
|
||||||
|
|
||||||
return await getPoints(params, region);
|
|
||||||
},
|
|
||||||
{ keepPreviousData: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: mergedData, isClickedPointLoading } = useMergeTableData(
|
|
||||||
data,
|
|
||||||
setPageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePageChange = useCallback((page) => setPage(page), []);
|
|
||||||
|
|
||||||
const rowSelection = {
|
|
||||||
selectedRowKeys: selectedIds,
|
|
||||||
onChange: (selectedRowKeys) => setSelectedIds(selectedRowKeys),
|
|
||||||
hideSelectAll: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
header={
|
|
||||||
<Header selectedIds={selectedIds} onClearSelected={clearSelected} />
|
|
||||||
}
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
data={mergedData}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
page={page}
|
|
||||||
pageSize={pageSize}
|
|
||||||
isClickedPointLoading={isClickedPointLoading}
|
|
||||||
columns={columns}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
loading={isInitialLoading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { usePopup } from "../../../stores/usePopup";
|
||||||
|
import { useUpdateStatus } from "../../../hooks/useUpdateStatus";
|
||||||
|
import { Button } from "antd";
|
||||||
|
|
||||||
|
export const ChangeStatusButton = ({ selectedIds, selectedStatus }) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { setPopup } = usePopup();
|
||||||
|
|
||||||
|
const { mutate: updateStatus } = useUpdateStatus({
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(["on-approval-points"]);
|
||||||
|
setPopup(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
status: selectedStatus,
|
||||||
|
"location_ids[]": selectedIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateStatus(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button type="primary" onClick={handleClick}>
|
||||||
|
Обновить статус
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { STATUSES } from "../../../config";
|
||||||
|
import { HeaderWrapper } from "../HeaderWrapper";
|
||||||
|
import { StatusSelect } from "../../../components/StatusSelect";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import { useExportOnApprovalData } from "./useExportOnApprovalData";
|
||||||
|
import { ChangeStatusButton } from "./ChangeStatusButton";
|
||||||
|
|
||||||
|
export const Header = ({ selectedIds, onClearSelected }) => {
|
||||||
|
const [status, setStatus] = useState(STATUSES.pending);
|
||||||
|
|
||||||
|
const handleClear = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClearSelected();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeaderWrapper
|
||||||
|
leftColumn={
|
||||||
|
selectedIds.length > 0 && (
|
||||||
|
<>
|
||||||
|
<StatusSelect value={status} onChange={setStatus} />
|
||||||
|
<ChangeStatusButton
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
selectedStatus={status}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightColumn={
|
||||||
|
selectedIds.length > 0 && (
|
||||||
|
<Button onClick={handleClear}>Очистить все</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
classes={{
|
||||||
|
leftColumn: "flex items-center gap-x-4",
|
||||||
|
rightColumn: "flex item-center gap-x-4",
|
||||||
|
}}
|
||||||
|
exportProvider={useExportOnApprovalData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import { Table } from "../Table";
|
||||||
|
import { columns } from "../InitialTable/columns";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getPoints } from "../../../api";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { PAGE_SIZE } from "../constants";
|
||||||
|
import { STATUSES } from "../../../config";
|
||||||
|
import { useMergeTableData } from "../useMergeTableData";
|
||||||
|
import { Header } from "./Header";
|
||||||
|
import { useOnApprovalPointsFilters } from "../../../stores/useOnApprovalPointsFilters";
|
||||||
|
|
||||||
|
export const OnApprovalTable = ({ fullWidth }) => {
|
||||||
|
const [pageSize, setPageSize] = useState(PAGE_SIZE);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [selectedIds, setSelectedIds] = useState([]);
|
||||||
|
const {
|
||||||
|
filters: { region },
|
||||||
|
} = useOnApprovalPointsFilters();
|
||||||
|
|
||||||
|
const clearSelected = () => setSelectedIds([]);
|
||||||
|
|
||||||
|
const { data, isInitialLoading } = useQuery(
|
||||||
|
["on-approval-points", page, region],
|
||||||
|
async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page,
|
||||||
|
page_size: pageSize,
|
||||||
|
"status[]": [STATUSES.onApproval, STATUSES.working, STATUSES.cancelled],
|
||||||
|
});
|
||||||
|
|
||||||
|
return await getPoints(params, region);
|
||||||
|
},
|
||||||
|
{ keepPreviousData: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: mergedData, isClickedPointLoading } = useMergeTableData(
|
||||||
|
data,
|
||||||
|
setPageSize
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback((page) => setPage(page), []);
|
||||||
|
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowKeys: selectedIds,
|
||||||
|
onChange: (selectedRowKeys) => setSelectedIds(selectedRowKeys),
|
||||||
|
hideSelectAll: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
header={
|
||||||
|
<Header selectedIds={selectedIds} onClearSelected={clearSelected} />
|
||||||
|
}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
data={mergedData}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
page={page}
|
||||||
|
pageSize={pageSize}
|
||||||
|
isClickedPointLoading={isClickedPointLoading}
|
||||||
|
columns={columns}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
loading={isInitialLoading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { useFilters } from "../../../stores/useFilters";
|
|
||||||
import { usePointSelection } from "../../../stores/usePointSelection";
|
import { usePointSelection } from "../../../stores/usePointSelection";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { exportPoints } from "../../../api";
|
import { exportPoints } from "../../../api";
|
||||||
import { handleExportSuccess } from "../ExportButton";
|
import { handleExportSuccess } from "../ExportButton";
|
||||||
|
import { usePendingPointsFilters } from "../../../stores/usePendingPointsFilters";
|
||||||
|
|
||||||
export const useExportInitialData = (enabled, onSettled) => {
|
export const useExportPendingData = (enabled, onSettled) => {
|
||||||
const { filters } = useFilters();
|
const { filters } = usePendingPointsFilters();
|
||||||
const { prediction, status, categories, region } = filters;
|
const { prediction, status, categories, region } = filters;
|
||||||
const { selection } = usePointSelection();
|
const { selection } = usePointSelection();
|
||||||
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
|
const INITIAL = {
|
||||||
|
region: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = (set) => ({
|
||||||
|
filters: INITIAL,
|
||||||
|
|
||||||
|
setRegion: (value) =>
|
||||||
|
set((state) => {
|
||||||
|
state.filters.region = value;
|
||||||
|
}),
|
||||||
|
|
||||||
|
clear: () =>
|
||||||
|
set((state) => {
|
||||||
|
state.filters = INITIAL;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useOnApprovalPointsFilters = create(immer(store));
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
|
export const INITIAL = {
|
||||||
|
prediction: [0, 0],
|
||||||
|
categories: [],
|
||||||
|
region: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = (set) => ({
|
||||||
|
filters: INITIAL,
|
||||||
|
setPrediction: (value) => {
|
||||||
|
set((state) => {
|
||||||
|
state.filters.prediction = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setCategories: (categories) =>
|
||||||
|
set((state) => {
|
||||||
|
state.filters.categories = categories;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setRegion: (value) =>
|
||||||
|
set((state) => {
|
||||||
|
state.filters.region = value;
|
||||||
|
}),
|
||||||
|
|
||||||
|
clear: (predictionFullRange) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!predictionFullRange) {
|
||||||
|
state.filters = INITIAL;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.filters = {
|
||||||
|
...INITIAL,
|
||||||
|
prediction: [predictionFullRange[0], predictionFullRange[1]],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usePendingPointsFilters = create(immer(store));
|
||||||
Loading…
Reference in new issue