feat: pagination for tables with deleted station directions
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
|||||||
CityCreatePage,
|
CityCreatePage,
|
||||||
CarrierCreatePage,
|
CarrierCreatePage,
|
||||||
VehicleCreatePage,
|
VehicleCreatePage,
|
||||||
|
VehicleEditPage,
|
||||||
CountryEditPage,
|
CountryEditPage,
|
||||||
CityEditPage,
|
CityEditPage,
|
||||||
UserCreatePage,
|
UserCreatePage,
|
||||||
@@ -153,6 +154,7 @@ const router = createBrowserRouter([
|
|||||||
{ path: "station/:id", element: <StationPreviewPage /> },
|
{ path: "station/:id", element: <StationPreviewPage /> },
|
||||||
{ path: "station/:id/edit", element: <StationEditPage /> },
|
{ path: "station/:id/edit", element: <StationEditPage /> },
|
||||||
{ path: "vehicle/create", element: <VehicleCreatePage /> },
|
{ path: "vehicle/create", element: <VehicleCreatePage /> },
|
||||||
|
{ path: "vehicle/:id/edit", element: <VehicleEditPage /> },
|
||||||
{ path: "article", element: <ArticleListPage /> },
|
{ path: "article", element: <ArticleListPage /> },
|
||||||
{ path: "article/:id", element: <ArticlePreviewPage /> },
|
{ path: "article/:id", element: <ArticlePreviewPage /> },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const ArticleListPage = observer(() => {
|
|||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchArticles = async () => {
|
const fetchArticles = async () => {
|
||||||
@@ -83,31 +87,41 @@ export const ArticleListPage = observer(() => {
|
|||||||
<h1 className="text-2xl">Статьи</h1>
|
<h1 className="text-2xl">Статьи</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids) as number[]);
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
hideFooter
|
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const CarrierListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<number | null>(null);
|
const [rowId, setRowId] = useState<number | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -129,28 +133,39 @@ export const CarrierListPage = observer(() => {
|
|||||||
<CreateButton label="Создать перевозчика" path="/carrier/create" />
|
<CreateButton label="Создать перевозчика" path="/carrier/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids as unknown as number[]));
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export const CityListPage = observer(() => {
|
|||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [rows, setRows] = useState<any[]>([]);
|
const [rows, setRows] = useState<any[]>([]);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -128,28 +132,39 @@ export const CityListPage = observer(() => {
|
|||||||
<CreateButton label="Создать город" path="/city/create" />
|
<CreateButton label="Создать город" path="/city/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids as unknown as number[]));
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export const CountryListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<string | null>(null);
|
const [rowId, setRowId] = useState<string | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -93,28 +97,39 @@ export const CountryListPage = observer(() => {
|
|||||||
<CreateButton label="Добавить страну" path="/country/add" />
|
<CreateButton label="Добавить страну" path="/country/add" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows || []}
|
rows={rows || []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids as unknown as number[]));
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export const MediaListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<string | null>(null);
|
const [rowId, setRowId] = useState<string | null>(null);
|
||||||
const [ids, setIds] = useState<string[]>([]);
|
const [ids, setIds] = useState<string[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -100,29 +104,39 @@ export const MediaListPage = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
paginationModel={paginationModel}
|
||||||
setIds(Array.from(newSelection.ids) as string[]);
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => String(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => String(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
hideFooter
|
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ type LinkedItemsProps<T> = {
|
|||||||
disableCreation?: boolean;
|
disableCreation?: boolean;
|
||||||
updatedLinkedItems?: T[];
|
updatedLinkedItems?: T[];
|
||||||
refresh?: number;
|
refresh?: number;
|
||||||
routeDirection?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LinkedItems = <
|
export const LinkedItems = <
|
||||||
@@ -128,7 +127,6 @@ const LinkedItemsContentsInner = <
|
|||||||
disableCreation = false,
|
disableCreation = false,
|
||||||
updatedLinkedItems,
|
updatedLinkedItems,
|
||||||
refresh,
|
refresh,
|
||||||
routeDirection,
|
|
||||||
}: LinkedItemsProps<T>) => {
|
}: LinkedItemsProps<T>) => {
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
@@ -153,11 +151,6 @@ const LinkedItemsContentsInner = <
|
|||||||
|
|
||||||
const availableItems = allItems
|
const availableItems = allItems
|
||||||
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
|
||||||
.filter((item) => {
|
|
||||||
if (routeDirection === undefined) return true;
|
|
||||||
|
|
||||||
return item.direction === routeDirection;
|
|
||||||
})
|
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
const selectedCityId = selectedCityStore.selectedCityId;
|
const selectedCityId = selectedCityStore.selectedCityId;
|
||||||
if (selectedCityId && "city_id" in item) {
|
if (selectedCityId && "city_id" in item) {
|
||||||
@@ -513,12 +506,6 @@ const LinkedItemsContentsInner = <
|
|||||||
{type === "edit" && !disableCreation && (
|
{type === "edit" && !disableCreation && (
|
||||||
<Stack gap={2} mt={2}>
|
<Stack gap={2} mt={2}>
|
||||||
<Typography variant="subtitle1">Добавить остановки</Typography>
|
<Typography variant="subtitle1">Добавить остановки</Typography>
|
||||||
{routeDirection !== undefined && (
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Показываются только остановки для{" "}
|
|
||||||
{routeDirection ? "прямого" : "обратного"} направления
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
@@ -547,6 +534,7 @@ const LinkedItemsContentsInner = <
|
|||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="Выберите остановку"
|
label="Выберите остановку"
|
||||||
|
placeholder="Введите название или описание остановки..."
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -554,16 +542,15 @@ const LinkedItemsContentsInner = <
|
|||||||
option.id === value?.id
|
option.id === value?.id
|
||||||
}
|
}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
const searchWords = inputValue
|
if (!inputValue.trim()) return options;
|
||||||
.toLowerCase()
|
const query = inputValue.toLowerCase();
|
||||||
.split(" ")
|
|
||||||
.filter(Boolean);
|
|
||||||
return options.filter((option) => {
|
return options.filter((option) => {
|
||||||
const optionWords = String(option.name)
|
const name = String(option.name || "").toLowerCase();
|
||||||
.toLowerCase()
|
const description = String(
|
||||||
.split(" ");
|
option.description || ""
|
||||||
return searchWords.every((searchWord) =>
|
).toLowerCase();
|
||||||
optionWords.some((word) => word.startsWith(searchWord))
|
return (
|
||||||
|
name.includes(query) || description.includes(query)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -575,7 +575,6 @@ export const RouteEditPage = observer(() => {
|
|||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
routeStore.getRoute(Number(id));
|
routeStore.getRoute(Number(id));
|
||||||
}}
|
}}
|
||||||
routeDirection={editRouteData.route_direction}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const RouteListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<number | null>(null);
|
const [rowId, setRowId] = useState<number | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -149,28 +153,39 @@ export const RouteListPage = observer(() => {
|
|||||||
<CreateButton label="Создать маршрут" path="/route/create" />
|
<CreateButton label="Создать маршрут" path="/route/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids as unknown as number[]));
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ export const SightListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<string | null>(null);
|
const [rowId, setRowId] = useState<string | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -98,7 +102,6 @@ export const SightListPage = observer(() => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Фильтрация достопримечательностей по выбранному городу
|
|
||||||
const filteredSights = useMemo(() => {
|
const filteredSights = useMemo(() => {
|
||||||
const { selectedCityId } = selectedCityStore;
|
const { selectedCityId } = selectedCityStore;
|
||||||
if (!selectedCityId) {
|
if (!selectedCityId) {
|
||||||
@@ -126,28 +129,39 @@ export const SightListPage = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
setIds(Array.from(newSelection.ids as unknown as number[]));
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ export const SnapshotListPage = observer(() => {
|
|||||||
snapshotStore;
|
snapshotStore;
|
||||||
|
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [rowId, setRowId] = useState<string | null>(null); // Lifted state
|
const [rowId, setRowId] = useState<string | null>(null);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
|
const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSnapshots = async () => {
|
const fetchSnapshots = async () => {
|
||||||
@@ -87,9 +91,10 @@ export const SnapshotListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
hideFooter
|
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
paginationModel={paginationModel}
|
||||||
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -136,23 +136,6 @@ export const StationCreatePage = observer(() => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel id="direction-label">Прямой/обратный маршрут</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="direction-label"
|
|
||||||
value={createStationData.common.direction ? "Прямой" : "Обратный"}
|
|
||||||
label="Прямой/обратный маршрут"
|
|
||||||
onChange={(e) =>
|
|
||||||
setCreateCommonData({
|
|
||||||
direction: e.target.value === "Прямой",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MenuItem value="Прямой">Прямой</MenuItem>
|
|
||||||
<MenuItem value="Обратный">Обратный</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Описание"
|
label="Описание"
|
||||||
|
|||||||
@@ -162,23 +162,6 @@ export const StationEditPage = observer(() => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel id="direction-label">Прямой/обратный маршрут</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="direction-label"
|
|
||||||
value={editStationData.common.direction ? "Прямой" : "Обратный"}
|
|
||||||
label="Прямой/обратный маршрут"
|
|
||||||
onChange={(e) =>
|
|
||||||
setEditCommonData({
|
|
||||||
direction: e.target.value === "Прямой",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MenuItem value="Прямой">Прямой</MenuItem>
|
|
||||||
<MenuItem value="Обратный">Обратный</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Описание"
|
label="Описание"
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export const StationListPage = observer(() => {
|
|||||||
);
|
);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -75,25 +79,6 @@ export const StationListPage = observer(() => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: "direction",
|
|
||||||
headerName: "Направление",
|
|
||||||
width: 200,
|
|
||||||
align: "center",
|
|
||||||
headerAlign: "center",
|
|
||||||
|
|
||||||
renderCell: (params: GridRenderCellParams) => {
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
params.row.direction === true ? "text-green-500" : "text-red-500"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{params.row.direction ? "Прямой" : "Обратный"}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: "actions",
|
field: "actions",
|
||||||
headerName: "Действия",
|
headerName: "Действия",
|
||||||
@@ -134,7 +119,6 @@ export const StationListPage = observer(() => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Фильтрация станций по выбранному городу
|
|
||||||
const filteredStations = () => {
|
const filteredStations = () => {
|
||||||
const { selectedCityId } = selectedCityStore;
|
const { selectedCityId } = selectedCityStore;
|
||||||
if (!selectedCityId) {
|
if (!selectedCityId) {
|
||||||
@@ -149,7 +133,6 @@ export const StationListPage = observer(() => {
|
|||||||
id: station.id,
|
id: station.id,
|
||||||
name: station.name,
|
name: station.name,
|
||||||
description: station.description,
|
description: station.description,
|
||||||
direction: station.direction,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -162,29 +145,75 @@ export const StationListPage = observer(() => {
|
|||||||
<CreateButton label="Создать остановки" path="/station/create" />
|
<CreateButton label="Создать остановки" path="/station/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
className="flex justify-end mb-5 duration-300"
|
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className={`px-4 py-2 rounded flex gap-2 items-center transition-all ${
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
ids.length > 0
|
||||||
|
? "bg-red-500 text-white cursor-pointer opacity-100"
|
||||||
|
: "bg-gray-300 text-gray-500 cursor-not-allowed opacity-0 pointer-events-none"
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (ids.length > 0) {
|
||||||
|
setIsBulkDeleteModalOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={ids.length === 0}
|
||||||
>
|
>
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
<Trash2
|
||||||
{ids.length})
|
size={20}
|
||||||
|
className={ids.length > 0 ? "text-white" : "text-gray-500"}
|
||||||
|
/>
|
||||||
|
Удалить выбранные ({ids.length})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
paginationModel={paginationModel}
|
||||||
setIds(Array.from(newSelection.ids) as number[]);
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection
|
||||||
|
.map((id: string | number) => {
|
||||||
|
const numId =
|
||||||
|
typeof id === "string"
|
||||||
|
? Number.parseInt(id, 10)
|
||||||
|
: Number(id);
|
||||||
|
return numId;
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(id: number) =>
|
||||||
|
!Number.isNaN(id) && id !== null && id !== undefined
|
||||||
|
);
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (
|
||||||
|
newSelection &&
|
||||||
|
typeof newSelection === "object" &&
|
||||||
|
"ids" in newSelection
|
||||||
|
) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet)
|
||||||
|
.map((id: string | number) => {
|
||||||
|
const numId =
|
||||||
|
typeof id === "string"
|
||||||
|
? Number.parseInt(id, 10)
|
||||||
|
: Number(id);
|
||||||
|
return numId;
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(id: number) =>
|
||||||
|
!Number.isNaN(id) && id !== null && id !== undefined
|
||||||
|
);
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
hideFooter
|
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -67,21 +67,6 @@ export const StationPreviewPage = observer(() => {
|
|||||||
<p>{stationPreview[id!]?.[language]?.data.system_name}</p>
|
<p>{stationPreview[id!]?.[language]?.data.system_name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h1 className="text-lg font-bold">Направление</h1>
|
|
||||||
<p
|
|
||||||
className={`${
|
|
||||||
stationPreview[id!]?.[language]?.data.direction
|
|
||||||
? "text-green-500"
|
|
||||||
: "text-red-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{stationPreview[id!]?.[language]?.data.direction
|
|
||||||
? "Прямой"
|
|
||||||
: "Обратный"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{stationPreview[id!]?.[language]?.data.address && (
|
{stationPreview[id!]?.[language]?.data.address && (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h1 className="text-lg font-bold">Адрес</h1>
|
<h1 className="text-lg font-bold">Адрес</h1>
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export const UserListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<number | null>(null);
|
const [rowId, setRowId] = useState<number | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
@@ -126,29 +130,39 @@ export const UserListPage = observer(() => {
|
|||||||
<CreateButton label="Создать пользователя" path="/user/create" />
|
<CreateButton label="Создать пользователя" path="/user/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
paginationModel={paginationModel}
|
||||||
setIds(Array.from(newSelection.ids) as number[]);
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
hideFooter
|
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Button,
|
Button,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
languageStore,
|
languageStore,
|
||||||
VEHICLE_TYPES,
|
VEHICLE_TYPES,
|
||||||
vehicleStore,
|
vehicleStore,
|
||||||
|
LoadingSpinner,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@ export const VehicleEditPage = observer(() => {
|
|||||||
} = vehicleStore;
|
} = vehicleStore;
|
||||||
const { getCarriers } = carrierStore;
|
const { getCarriers } = carrierStore;
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Устанавливаем русский язык при загрузке страницы
|
// Устанавливаем русский язык при загрузке страницы
|
||||||
@@ -38,31 +42,58 @@ export const VehicleEditPage = observer(() => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
const fetchAndSetVehicleData = async () => {
|
||||||
await getVehicle(Number(id));
|
if (!id) {
|
||||||
await getCarriers(language);
|
setIsLoadingData(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setEditVehicleData({
|
setIsLoadingData(true);
|
||||||
tail_number: vehicle[Number(id)]?.vehicle.tail_number,
|
try {
|
||||||
type: vehicle[Number(id)]?.vehicle.type,
|
await getVehicle(Number(id));
|
||||||
carrier: vehicle[Number(id)]?.vehicle.carrier,
|
await getCarriers(language);
|
||||||
carrier_id: vehicle[Number(id)]?.vehicle.carrier_id,
|
|
||||||
});
|
setEditVehicleData({
|
||||||
})();
|
tail_number: vehicle[Number(id)]?.vehicle.tail_number,
|
||||||
|
type: vehicle[Number(id)]?.vehicle.type,
|
||||||
|
carrier: vehicle[Number(id)]?.vehicle.carrier,
|
||||||
|
carrier_id: vehicle[Number(id)]?.vehicle.carrier_id,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAndSetVehicleData();
|
||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const handleEdit = async () => {
|
const handleEdit = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await editVehicle(Number(id), editVehicleData);
|
await editVehicle(Number(id), editVehicleData);
|
||||||
toast.success("Транспортное средство успешно обновлено");
|
toast.success("Транспортное средство успешно обновлено");
|
||||||
navigate("/vehicle");
|
navigate("/devices");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Ошибка при обновлении транспортного средства");
|
toast.error("Ошибка при обновлении транспортного средства");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных транспортного средства..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export const VehicleListPage = observer(() => {
|
|||||||
const [rowId, setRowId] = useState<number | null>(null);
|
const [rowId, setRowId] = useState<number | null>(null);
|
||||||
const [ids, setIds] = useState<number[]>([]);
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
|
page: 0,
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -148,29 +152,39 @@ export const VehicleListPage = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{ids.length > 0 && (
|
||||||
className="flex justify-end mb-5 duration-300"
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
<button
|
||||||
>
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
<button
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
>
|
||||||
onClick={() => setIsBulkDeleteModalOpen(true)}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
>
|
{ids.length})
|
||||||
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
</button>
|
||||||
{ids.length})
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onRowSelectionModelChange={(newSelection) => {
|
paginationModel={paginationModel}
|
||||||
setIds(Array.from(newSelection.ids) as number[]);
|
onPaginationModelChange={setPaginationModel}
|
||||||
|
pageSizeOptions={[50]}
|
||||||
|
onRowSelectionModelChange={(newSelection: any) => {
|
||||||
|
if (Array.isArray(newSelection)) {
|
||||||
|
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
||||||
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
|
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
||||||
|
setIds(selectedIds);
|
||||||
|
} else {
|
||||||
|
setIds([]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
hideFooter
|
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ type StationLanguageData = {
|
|||||||
|
|
||||||
type StationCommonData = {
|
type StationCommonData = {
|
||||||
city_id: number;
|
city_id: number;
|
||||||
direction: boolean;
|
|
||||||
description: string;
|
description: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
@@ -44,7 +43,6 @@ type Station = {
|
|||||||
city: string;
|
city: string;
|
||||||
city_id: number;
|
city_id: number;
|
||||||
description: string;
|
description: string;
|
||||||
direction: boolean;
|
|
||||||
icon: string;
|
icon: string;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
@@ -123,7 +121,6 @@ class StationsStore {
|
|||||||
common: {
|
common: {
|
||||||
city: "",
|
city: "",
|
||||||
city_id: 0,
|
city_id: 0,
|
||||||
direction: false,
|
|
||||||
description: "",
|
description: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@@ -169,7 +166,6 @@ class StationsStore {
|
|||||||
common: {
|
common: {
|
||||||
city: "",
|
city: "",
|
||||||
city_id: 0,
|
city_id: 0,
|
||||||
direction: false,
|
|
||||||
description: "",
|
description: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@@ -252,7 +248,6 @@ class StationsStore {
|
|||||||
common: {
|
common: {
|
||||||
city: ruResponse.data.city,
|
city: ruResponse.data.city,
|
||||||
city_id: ruResponse.data.city_id,
|
city_id: ruResponse.data.city_id,
|
||||||
direction: ruResponse.data.direction,
|
|
||||||
description: ruResponse.data.description,
|
description: ruResponse.data.description,
|
||||||
icon: ruResponse.data.icon,
|
icon: ruResponse.data.icon,
|
||||||
latitude: ruResponse.data.latitude,
|
latitude: ruResponse.data.latitude,
|
||||||
@@ -277,7 +272,6 @@ class StationsStore {
|
|||||||
editStation = async (id: number) => {
|
editStation = async (id: number) => {
|
||||||
const commonDataPayload = {
|
const commonDataPayload = {
|
||||||
city_id: this.editStationData.common.city_id,
|
city_id: this.editStationData.common.city_id,
|
||||||
direction: this.editStationData.common.direction,
|
|
||||||
icon: this.editStationData.common.icon,
|
icon: this.editStationData.common.icon,
|
||||||
latitude: this.editStationData.common.latitude,
|
latitude: this.editStationData.common.latitude,
|
||||||
longitude: this.editStationData.common.longitude,
|
longitude: this.editStationData.common.longitude,
|
||||||
@@ -405,7 +399,6 @@ class StationsStore {
|
|||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
let commonDataPayload: Partial<StationCommonData> = {
|
let commonDataPayload: Partial<StationCommonData> = {
|
||||||
city_id: this.createStationData.common.city_id,
|
city_id: this.createStationData.common.city_id,
|
||||||
direction: this.createStationData.common.direction,
|
|
||||||
icon: this.createStationData.common.icon,
|
icon: this.createStationData.common.icon,
|
||||||
latitude: this.createStationData.common.latitude,
|
latitude: this.createStationData.common.latitude,
|
||||||
longitude: this.createStationData.common.longitude,
|
longitude: this.createStationData.common.longitude,
|
||||||
@@ -479,7 +472,6 @@ class StationsStore {
|
|||||||
common: {
|
common: {
|
||||||
city: "",
|
city: "",
|
||||||
city_id: 0,
|
city_id: 0,
|
||||||
direction: false,
|
|
||||||
icon: "",
|
icon: "",
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
description: "",
|
description: "",
|
||||||
@@ -526,7 +518,6 @@ class StationsStore {
|
|||||||
common: {
|
common: {
|
||||||
city: "",
|
city: "",
|
||||||
city_id: 0,
|
city_id: 0,
|
||||||
direction: false,
|
|
||||||
description: "",
|
description: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@@ -575,7 +566,6 @@ class StationsStore {
|
|||||||
// Формируем commonDataPayload как в editStation, с обновленными transfers
|
// Формируем commonDataPayload как в editStation, с обновленными transfers
|
||||||
const commonDataPayload = {
|
const commonDataPayload = {
|
||||||
city_id: stationData.city_id,
|
city_id: stationData.city_id,
|
||||||
direction: stationData.direction,
|
|
||||||
latitude: stationData.latitude,
|
latitude: stationData.latitude,
|
||||||
longitude: stationData.longitude,
|
longitude: stationData.longitude,
|
||||||
offset_x: stationData.offset_x,
|
offset_x: stationData.offset_x,
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ import TableContainer from "@mui/material/TableContainer";
|
|||||||
import TableHead from "@mui/material/TableHead";
|
import TableHead from "@mui/material/TableHead";
|
||||||
import TableRow from "@mui/material/TableRow";
|
import TableRow from "@mui/material/TableRow";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import { Check, Copy, RotateCcw, Trash2, X } from "lucide-react";
|
import { Check, Copy, Pencil, RotateCcw, Trash2, X } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
authInstance,
|
authInstance,
|
||||||
devicesStore,
|
devicesStore,
|
||||||
Modal,
|
Modal,
|
||||||
snapshotStore,
|
snapshotStore,
|
||||||
vehicleStore,
|
vehicleStore,
|
||||||
|
Vehicle,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Button, Checkbox, Typography } from "@mui/material";
|
import { Button, Checkbox, Typography } from "@mui/material";
|
||||||
import { Vehicle } from "@shared";
|
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { DeleteModal } from "@widgets";
|
import { DeleteModal } from "@widgets";
|
||||||
@@ -47,6 +47,7 @@ const formatDate = (dateString: string | null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TableRowData = {
|
type TableRowData = {
|
||||||
|
vehicle_id: number;
|
||||||
tail_number: number;
|
tail_number: number;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
lastUpdate: string | null;
|
lastUpdate: string | null;
|
||||||
@@ -56,6 +57,7 @@ type TableRowData = {
|
|||||||
device_uuid: string | null;
|
device_uuid: string | null;
|
||||||
};
|
};
|
||||||
function createData(
|
function createData(
|
||||||
|
vehicle_id: number,
|
||||||
tail_number: number,
|
tail_number: number,
|
||||||
online: boolean,
|
online: boolean,
|
||||||
lastUpdate: string | null,
|
lastUpdate: string | null,
|
||||||
@@ -65,6 +67,7 @@ function createData(
|
|||||||
device_uuid: string | null
|
device_uuid: string | null
|
||||||
): TableRowData {
|
): TableRowData {
|
||||||
return {
|
return {
|
||||||
|
vehicle_id,
|
||||||
tail_number,
|
tail_number,
|
||||||
online,
|
online,
|
||||||
lastUpdate,
|
lastUpdate,
|
||||||
@@ -80,6 +83,7 @@ const transformDevicesToRows = (vehicles: Vehicle[]): TableRowData[] => {
|
|||||||
const uuid = vehicle.vehicle.uuid;
|
const uuid = vehicle.vehicle.uuid;
|
||||||
if (!uuid)
|
if (!uuid)
|
||||||
return {
|
return {
|
||||||
|
vehicle_id: vehicle.vehicle.id,
|
||||||
tail_number: vehicle.vehicle.tail_number,
|
tail_number: vehicle.vehicle.tail_number,
|
||||||
online: false,
|
online: false,
|
||||||
lastUpdate: null,
|
lastUpdate: null,
|
||||||
@@ -89,6 +93,7 @@ const transformDevicesToRows = (vehicles: Vehicle[]): TableRowData[] => {
|
|||||||
device_uuid: null,
|
device_uuid: null,
|
||||||
};
|
};
|
||||||
return createData(
|
return createData(
|
||||||
|
vehicle.vehicle.id,
|
||||||
vehicle.vehicle.tail_number,
|
vehicle.vehicle.tail_number,
|
||||||
vehicle.device_status?.online ?? false,
|
vehicle.device_status?.online ?? false,
|
||||||
vehicle.device_status?.last_update ?? null,
|
vehicle.device_status?.last_update ?? null,
|
||||||
@@ -404,37 +409,54 @@ export const DevicesTable = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<Button
|
<div className="flex items-center justify-center gap-1">
|
||||||
onClick={async (e) => {
|
<Button
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
try {
|
e.stopPropagation();
|
||||||
if (
|
navigate(`/vehicle/${row.vehicle_id}/edit`);
|
||||||
row.device_uuid &&
|
}}
|
||||||
devices.find((device) => device === row.device_uuid)
|
title="Редактировать транспорт"
|
||||||
) {
|
size="small"
|
||||||
await handleReloadStatus(row.device_uuid);
|
variant="text"
|
||||||
toast.success("Статус устройства обновлен");
|
>
|
||||||
} else {
|
<Pencil size={16} />
|
||||||
toast.error("Нет связи с устройством");
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
row.device_uuid &&
|
||||||
|
devices.find((device) => device === row.device_uuid)
|
||||||
|
) {
|
||||||
|
await handleReloadStatus(row.device_uuid);
|
||||||
|
toast.success("Статус устройства обновлен");
|
||||||
|
} else {
|
||||||
|
toast.error("Нет связи с устройством");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Ошибка сервера");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}}
|
||||||
toast.error("Ошибка сервера");
|
title="Перезапросить статус"
|
||||||
}
|
size="small"
|
||||||
}}
|
variant="text"
|
||||||
title="Перезапросить статус"
|
>
|
||||||
size="small"
|
<RotateCcw size={16} />
|
||||||
variant="text"
|
</Button>
|
||||||
>
|
<Button
|
||||||
<RotateCcw size={16} />
|
onClick={(e) => {
|
||||||
</Button>
|
e.stopPropagation();
|
||||||
<Button
|
navigator.clipboard.writeText(row.device_uuid ?? "");
|
||||||
onClick={() => {
|
toast.success("UUID скопирован");
|
||||||
navigator.clipboard.writeText(row.device_uuid ?? "");
|
}}
|
||||||
toast.success("UUID скопирован");
|
title="Копировать UUID"
|
||||||
}}
|
size="small"
|
||||||
>
|
variant="text"
|
||||||
<Copy size={16} />
|
>
|
||||||
</Button>
|
<Copy size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user