Adjust to design

dev
Platon Yasev 3 years ago
parent 740c85390a
commit 525a63dfb0

@ -24,7 +24,7 @@
"nanostores": "^0.7.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.6.0",
"react-icons": "^4.8.0",
"react-map-gl": "^7.0.19",
"react-router-dom": "^6.8.1",
"scroll-into-view-if-needed": "^3.0.6",

@ -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,6 +1,6 @@
import maplibregl from "maplibre-gl";
import Map, { MapProvider } from "react-map-gl";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { Sidebar } from "../modules/Sidebar/Sidebar";
import { Layers } from "./Layers";
import { MapPopup } from "./Popup";
@ -9,12 +9,14 @@ import { SignOut } from "../SignOut";
import debounce from "lodash.debounce";
import { usePopup } from "../stores/usePopup";
import { useClickedPointConfig } from "../stores/useClickedPointConfig";
import { Legend } from "../modules/Sidebar/Legend";
import { Legend } from "./Legend";
import { AddressSearch } from "../modules/Sidebar/AddressSearch";
import { TableWrapper } from "../modules/Table/TableWrapper";
import { useFilters } from "../stores/useFilters";
import { useMode } from "../stores/useMode";
import { MODE_TO_STATUS_MAPPER } from "../config";
import { FiltersButton } from "../modules/Sidebar/FiltersButton";
import { LayersControl } from "./LayersControl/LayersControl";
export const MapComponent = () => {
const mapRef = useRef(null);
@ -84,49 +86,61 @@ export const MapComponent = () => {
};
}, [mapContainerRef.current]);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const toggleCollapseSidebar = () =>
setIsSidebarCollapsed((prevState) => !prevState);
return (
<MapProvider>
<div ref={mapContainerRef} className="w-full flex-1">
<Map
mapLib={maplibregl}
// mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
// style={{ width: "100%", height: "100%" }}
initialViewState={{
latitude: 55.7558,
longitude: 37.6173,
zoom: 9,
}}
dragRotate={false}
ref={mapRef}
interactiveLayerIds={["match-points", "unmatch-points"]}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
id="map"
>
{popup && (
<MapPopup
lat={popup.coordinates[1]}
lng={popup.coordinates[0]}
features={popup.features}
onClose={() => {
setPopup(null);
setClickedPointConfig(null);
}}
/>
)}
<Basemap />
<Layers />
<Sidebar />
<AddressSearch />
<Legend />
<SignOut />
</Map>
<Sidebar isCollapsed={isSidebarCollapsed} />
<div className="flex-1 h-screen flex flex-col ">
<div ref={mapContainerRef} className="flex-1">
<Map
mapLib={maplibregl}
// mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
// style={{ width: "100%", height: "100%" }}
initialViewState={{
latitude: 55.7558,
longitude: 37.6173,
zoom: 9,
}}
dragRotate={false}
ref={mapRef}
interactiveLayerIds={["match-points", "unmatch-points"]}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
id="map"
>
{popup && (
<MapPopup
lat={popup.coordinates[1]}
lng={popup.coordinates[0]}
features={popup.features}
onClose={() => {
setPopup(null);
setClickedPointConfig(null);
}}
/>
)}
<FiltersButton toggleCollapse={toggleCollapseSidebar} />
<Basemap />
<Layers />
<AddressSearch />
<Legend />
<SignOut />
<LayersControl />
</Map>
</div>
<div className="w-full">
<TableWrapper fullWidth={isSidebarCollapsed} />
</div>
</div>
<TableWrapper />
</MapProvider>
);
};

@ -20,26 +20,7 @@ export const pointLayer = {
type: "circle",
layout: {},
paint: {
"circle-color": [
"match",
["get", "category"],
CATEGORIES.kiosk,
pointColors[CATEGORIES.kiosk],
CATEGORIES.mfc,
pointColors[CATEGORIES.mfc],
CATEGORIES.library,
pointColors[CATEGORIES.library],
CATEGORIES.dk,
pointColors[CATEGORIES.dk],
CATEGORIES.sport,
pointColors[CATEGORIES.sport],
CATEGORIES.residential,
pointColors[CATEGORIES.residential],
CATEGORIES.retail,
pointColors[CATEGORIES.retail],
"black",
],
// "circle-color": "#a51eda",
"circle-color": "#a51eda",
"circle-radius": [
"interpolate",
["linear"],

@ -3,7 +3,7 @@
@tailwind utilities;
#root {
@apply h-screen w-screen flex flex-col overflow-hidden;
@apply h-screen w-screen flex overflow-hidden;
}
.mapboxgl-popup,

@ -59,7 +59,7 @@ export const AddressSearch = () => {
};
return (
<div className="absolute top-[20px] left-[360px]">
<div className="absolute top-[20px] left-[30px]">
<AutoComplete
options={options}
style={{

@ -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)}
disabled={disabled}
/>
))}
</div>
<Select
mode="tags"
style={{
width: "100%",
}}
placeholder="Выберите категории локаций"
onChange={setCategories}
options={options}
allowClear={true}
value={filters.categories}
disabled={disabled}
/>
</div>
);
};

@ -5,6 +5,7 @@ import { usePointSelection } from "../../../stores/usePointSelection";
import { STATUSES } from "../../../config";
import { useState } from "react";
import { useUpdateStatus } from "../../../hooks/useUpdateStatus";
import { ArrowRightOutlined } from "@ant-design/icons";
export const TakeToWorkButton = () => {
const { filters } = useFilters();
@ -46,7 +47,8 @@ export const TakeToWorkButton = () => {
return (
<>
<Button type="primary" block className={"mt-2"} onClick={takeToWork}>
Взять в работу
<span className="mr-1"> Взять в работу</span>
<ArrowRightOutlined />
</Button>
<Modal
title={" "}

@ -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>
);
}

@ -27,7 +27,7 @@ const normalizeRegions = (rawRegions) => {
};
export const RegionSelect = ({ disabled }) => {
const { current: map } = useMap();
const { map } = useMap();
const {
filters: { region },
setRegion,

@ -1,32 +1,32 @@
import { Button } from "antd";
import { LayersVisibility } from "./LayersVisibility";
import {
useHasManualEdits,
usePointSelection,
} from "../../stores/usePointSelection";
import { TakeToWorkButton } from "./InitialSidebar/TakeToWorkButton";
import { Filters } from "./InitialSidebar/Filters";
import { ExportButton } from "./ExportButton";
import { Header } from "./Header";
import { useMode } from "../../stores/useMode";
import { MODES } from "../../config";
import { twMerge } from "tailwind-merge";
export const Sidebar = () => {
export const Sidebar = ({ isCollapsed }) => {
const hasManualEdits = useHasManualEdits();
const { reset: resetSelection } = usePointSelection();
const { mode } = useMode();
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={twMerge(
"bg-white-background h-screen p-3 overflow-y-auto shrink-0 border-solid border-border border-0 border-r-[1px] flex flex-col transition-all",
isCollapsed ? "basis-0 p-0 -translate-x-[320px]" : "basis-[320px] p-3"
)}
>
<Header />
<div className="space-y-5">
<LayersVisibility />
<div className="space-y-5 flex flex-col justify-between flex-1">
<Filters disabled={hasManualEdits} />
<div>
<ExportButton />
{mode === MODES.INITIAL && <TakeToWorkButton />}
{mode === MODES.INITIAL && hasManualEdits ? (
<Button
type="text"
@ -37,6 +37,8 @@ export const Sidebar = () => {
Отменить ручное редактирование
</Button>
) : null}
{mode === MODES.INITIAL && <TakeToWorkButton />}
</div>
</div>
</div>

@ -8,6 +8,8 @@ import { STATUSES } from "../../../config";
import { useMergeTableData } from "../useMergeTableData";
import { Button, Select } from "antd";
import { useUpdateStatus } from "../../../hooks/useUpdateStatus";
import { HeaderWrapper } from "../HeaderWrapper";
import { useExportApproveAndWorkingData } from "./useExportApproveAndWorkingData";
const statusOptions = [
{ label: STATUSES.approve, value: STATUSES.approve },
@ -42,7 +44,7 @@ const ChangeStatusButton = ({ selectedIds, selectedStatus }) => {
const { mutate: updateStatus } = useUpdateStatus({
onSuccess: () => {
queryClient.invalidateQueries(["approve-points"]);
queryClient.invalidateQueries(["approve-working-points"]);
},
});
@ -73,10 +75,9 @@ const Header = ({ selectedIds, onClearSelected }) => {
};
return (
<div className={"flex items-center w-full justify-between"}>
<div className={"flex items-center gap-x-4"}>
<span className="py-[5px]">Таблица атрибутов</span>
{selectedIds.length > 0 && (
<HeaderWrapper
leftColumn={
selectedIds.length > 0 && (
<>
<StatusSelect value={status} onChange={setStatus} />
<ChangeStatusButton
@ -84,25 +85,32 @@ const Header = ({ selectedIds, onClearSelected }) => {
selectedStatus={status}
/>
</>
)}
</div>
{selectedIds.length > 0 && (
<Button type="text" onClick={handleClear}>
Очистить все
</Button>
)}
</div>
)
}
rightColumn={
selectedIds.length > 0 && (
<Button type="text" onClick={handleClear}>
Очистить все
</Button>
)
}
classes={{
leftColumn: "flex items-center gap-x-4",
rightColumn: "flex item-center gap-x-4",
}}
exportProvider={useExportApproveAndWorkingData}
/>
);
};
export const ApproveTable = () => {
export const ApproveAndWorkingTable = ({ fullWidth }) => {
const [pageSize, setPageSize] = useState(PAGE_SIZE);
const [page, setPage] = useState(1);
const [selectedIds, setSelectedIds] = useState([]);
const clearSelected = () => setSelectedIds([]);
const { data } = useQuery(["approve-points"], async () => {
const { data } = useQuery(["approve-working-points"], async () => {
const params = new URLSearchParams({
page,
page_size: pageSize,
@ -141,6 +149,7 @@ export const ApproveTable = () => {
pageSize={pageSize}
isClickedPointLoading={isClickedPointLoading}
columns={columns}
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 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>
);
};

@ -1,11 +1,13 @@
import { useCallback, useState } from "react";
import React, { useCallback, useState } from "react";
import { Table } from "../Table";
import { usePointSelection } from "../../../stores/usePointSelection";
import { useClickedPointConfig } from "../../../stores/useClickedPointConfig";
import { useInitialTableData } from "./useInitialTableData";
import { columns } from "./columns";
import { HeaderWrapper } from "../HeaderWrapper";
import { useExportInitialData } from "./useExportInitialData";
export const InitialTable = () => {
export const InitialTable = ({ fullWidth }) => {
const { selection, include, exclude } = usePointSelection();
const { clickedPointConfig } = useClickedPointConfig();
const [page, setPage] = useState(1);
@ -54,6 +56,8 @@ export const InitialTable = () => {
pageSize={pageSize}
isClickedPointLoading={isClickedPointLoading}
columns={columns}
fullWidth={fullWidth}
header={<HeaderWrapper exportProvider={useExportInitialData} />}
/>
);
};

@ -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,
}
);
};

@ -3,6 +3,10 @@
}
.ant-table {
@apply max-w-[calc(100vw-320px)];
}
.ant-table[data-fullwidth="true"] {
@apply max-w-[100vw];
}
@ -43,26 +47,34 @@
padding: 4px;
}
.ant-table-tbody > tr.ant-table-row-selected.scroll-row > td {
@apply bg-lime-200;
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
@apply !items-center;
}
.ant-table-tbody > tr.ant-table-row.scroll-row > td {
@apply bg-lime-200;
.ant-collapse-header-text {
@apply flex items-center;
}
.ant-table-tbody > tr.ant-table-row-selected.scroll-row:hover > td {
@apply bg-lime-200;
.ant-table-tbody > tr.ant-table-row-selected > td {
background-color: transparent !important;
}
.ant-table-tbody > tr.ant-table-row.scroll-row:hover > td {
@apply bg-lime-200;
.ant-table-tbody > tr:not(.ant-table-placeholder):hover > td {
@apply !bg-grey-light;
}
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
@apply !items-center;
.ant-table-tbody > tr.ant-table-row-selected.scroll-row > td {
@apply !bg-primary-light;
}
.ant-collapse-header-text {
@apply flex items-center;
.ant-table-tbody > tr.ant-table-row.scroll-row > td {
@apply !bg-primary-light;
}
.ant-table-tbody > tr.ant-table-row-selected.scroll-row:hover > td {
@apply !bg-primary-light;
}
.ant-table-tbody > tr.ant-table-row.scroll-row:hover > td {
@apply !bg-primary-light;
}

@ -6,6 +6,7 @@ import { useMap } from "react-map-gl";
import { useClickedPointConfig } from "../../stores/useClickedPointConfig";
import scrollIntoView from "scroll-into-view-if-needed";
import { twMerge } from "tailwind-merge";
import { HeaderWrapper } from "./HeaderWrapper";
export const Table = React.memo(
({
@ -18,6 +19,7 @@ export const Table = React.memo(
onPageChange,
columns,
header,
fullWidth,
}) => {
const { clickedPointConfig } = useClickedPointConfig();
const { map } = useMap();
@ -38,62 +40,49 @@ export const Table = React.memo(
}, [clickedPointConfig, data]);
return (
<div className="w-screen">
<Collapse>
<Collapse.Panel
key="1"
header={
header ? (
header
) : (
<span className="py-[5px]">Таблица атрибутов</span>
<Collapse>
<Collapse.Panel key="1" header={header ? header : <HeaderWrapper />}>
<AntdTable
ref={tableRef}
// className="max-w-full"
size="small"
locale={{ emptyText: <Empty description="Нет данных" /> }}
pagination={{
pageSize,
current: page,
onChange: onPageChange,
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) =>
twMerge(
"cursor-pointer",
record.location_id === clickedPointConfig?.id && "scroll-row"
)
}
>
<AntdTable
ref={tableRef}
className="table"
size="small"
locale={{ emptyText: <Empty description="Нет данных" /> }}
pagination={{
pageSize,
current: page,
onChange: onPageChange,
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) =>
twMerge(
"cursor-pointer",
record.location_id === clickedPointConfig?.id && "scroll-row"
)
}
/>
</Collapse.Panel>
</Collapse>
</div>
data-fullwidth={fullWidth}
/>
</Collapse.Panel>
</Collapse>
);
}
);

@ -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} />;
};

@ -6,8 +6,10 @@ import { api } from "../../../api";
import { useMergeTableData } from "../useMergeTableData";
import { Table } from "../Table";
import { columns } from "../InitialTable/columns";
import { HeaderWrapper } from "../HeaderWrapper";
import { useExportWorkingData } from "./useExportWorkingData";
export const WorkingTable = () => {
export const WorkingTable = ({ fullWidth }) => {
const [pageSize, setPageSize] = useState(PAGE_SIZE);
const [page, setPage] = useState(1);
@ -40,6 +42,8 @@ export const WorkingTable = () => {
pageSize={pageSize}
isClickedPointLoading={isClickedPointLoading}
columns={columns}
fullWidth={fullWidth}
header={<HeaderWrapper exportProvider={useExportWorkingData} />}
/>
);
};

@ -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;
};

@ -17,19 +17,9 @@ const store = (set) => ({
});
},
setCategories: (category) =>
setCategories: (categories) =>
set((state) => {
if (Array.isArray(category)) {
state.filters.categories = [];
return;
}
if (state.filters.categories.includes(category)) {
state.filters.categories = state.filters.categories.filter(
(id) => id !== category
);
} else {
state.filters.categories.push(category);
}
state.filters.categories = categories;
}),
setRegion: (value) =>

@ -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);
}

@ -5,11 +5,12 @@ module.exports = {
extend: {
colors: {
primary: "#CC2222FF",
'primary-light': "#ffc6c6",
'primary-light': "#ffe4e4",
blue: "rgba(167,201,236,0.57)",
"white-background": "rgba(255, 255, 255, 0.9)",
grey: "rgba(0,0, 0, 0.5)",
"grey-light": "rgba(239,239,239,0.9)",
border: '#d9d9d9'
},
},
},

@ -2244,10 +2244,10 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-icons@^4.6.0:
version "4.7.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.7.1.tgz#0f4b25a5694e6972677cb189d2a72eabea7a8345"
integrity sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==
react-icons@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.8.0.tgz#621e900caa23b912f737e41be57f27f6b2bff445"
integrity sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==
react-is@^16.12.0:
version "16.13.1"

Loading…
Cancel
Save