feat: role system fix
This commit is contained in:
@@ -21,6 +21,7 @@ export const ArticleListPage = observer(() => {
|
|||||||
page: 0,
|
page: 0,
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
|
const canWriteArticles = authStore.canWrite("sights");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchArticles = async () => {
|
const fetchArticles = async () => {
|
||||||
@@ -56,7 +57,7 @@ export const ArticleListPage = observer(() => {
|
|||||||
<button onClick={() => navigate(`/article/${params.row.id}`)}>
|
<button onClick={() => navigate(`/article/${params.row.id}`)}>
|
||||||
<Eye size={20} className="text-green-500" />
|
<Eye size={20} className="text-green-500" />
|
||||||
</button>
|
</button>
|
||||||
{authStore.canWrite("sights") && (
|
{canWriteArticles && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
@@ -86,7 +87,7 @@ export const ArticleListPage = observer(() => {
|
|||||||
<h1 className="text-2xl">Статьи</h1>
|
<h1 className="text-2xl">Статьи</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteArticles && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -102,25 +103,37 @@ export const ArticleListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection
|
checkboxSelection={canWriteArticles}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteArticles
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export const CityListPage = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteCities && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -145,25 +145,37 @@ export const CityListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection={authStore.canWrite("cities")}
|
checkboxSelection={canWriteCities}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteCities
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const CountryListPage = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteCountries && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -105,25 +105,37 @@ export const CountryListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows || []}
|
rows={rows || []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection={authStore.canWrite("countries")}
|
checkboxSelection={canWriteCountries}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteCountries
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const MediaListPage = observer(() => {
|
|||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const canWriteMedia = authStore.canWrite("sights");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMedia = async () => {
|
const fetchMedia = async () => {
|
||||||
@@ -79,7 +80,7 @@ export const MediaListPage = observer(() => {
|
|||||||
<button onClick={() => navigate(`/media/${params.row.id}`)}>
|
<button onClick={() => navigate(`/media/${params.row.id}`)}>
|
||||||
<Eye size={20} className="text-green-500" />
|
<Eye size={20} className="text-green-500" />
|
||||||
</button>
|
</button>
|
||||||
{authStore.canWrite("sights") && (
|
{canWriteMedia && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
@@ -103,7 +104,7 @@ export const MediaListPage = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{ids.length > 0 && (
|
{canWriteMedia && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -118,24 +119,36 @@ export const MediaListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection={authStore.canWrite("sights")}
|
checkboxSelection={canWriteMedia}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteMedia
|
||||||
const selectedIds = newSelection.map((id: string | number) => String(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => String(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => String(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ const LinkedItemsContentsInner = <
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching all items:", error);
|
console.error("Error fetching all items:", error);
|
||||||
setError("Failed to load available stations");
|
setError(null);
|
||||||
setAllItems([]);
|
setAllItems([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export const RouteListPage = observer(() => {
|
|||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const canWriteRoutes = authStore.canWrite("routes");
|
||||||
|
const canShowRoutePreview =
|
||||||
|
authStore.canWrite("stations") &&
|
||||||
|
authStore.canWrite("sights") &&
|
||||||
|
authStore.canWrite("routes");
|
||||||
|
const canShowActionsColumn = canWriteRoutes || canShowRoutePreview;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -104,7 +110,7 @@ export const RouteListPage = observer(() => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
...(canShowActionsColumn ? [{
|
||||||
field: "actions",
|
field: "actions",
|
||||||
headerName: "Действия",
|
headerName: "Действия",
|
||||||
width: 250,
|
width: 250,
|
||||||
@@ -112,14 +118,9 @@ export const RouteListPage = observer(() => {
|
|||||||
headerAlign: "center" as const,
|
headerAlign: "center" as const,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
renderCell: (params: GridRenderCellParams) => {
|
renderCell: (params: GridRenderCellParams) => {
|
||||||
const canWrite = authStore.canWrite("routes");
|
|
||||||
const canShowRoutePreview =
|
|
||||||
authStore.canRead("stations") &&
|
|
||||||
authStore.canRead("sights") &&
|
|
||||||
authStore.canRead("routes");
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full gap-7 justify-center items-center">
|
<div className="flex h-full gap-7 justify-center items-center">
|
||||||
{canWrite && (
|
{canWriteRoutes && (
|
||||||
<button onClick={() => navigate(`/route/${params.row.id}/edit`)}>
|
<button onClick={() => navigate(`/route/${params.row.id}/edit`)}>
|
||||||
<Pencil size={20} className="text-blue-500" />
|
<Pencil size={20} className="text-blue-500" />
|
||||||
</button>
|
</button>
|
||||||
@@ -129,7 +130,7 @@ export const RouteListPage = observer(() => {
|
|||||||
<Map size={20} className="text-purple-500" />
|
<Map size={20} className="text-purple-500" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{canWrite && (
|
{canWriteRoutes && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
@@ -142,7 +143,7 @@ export const RouteListPage = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
}] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const rows = routes.data.map((route) => ({
|
const rows = routes.data.map((route) => ({
|
||||||
@@ -160,10 +161,12 @@ export const RouteListPage = observer(() => {
|
|||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<div className="flex justify-between items-center mb-10">
|
<div className="flex justify-between items-center mb-10">
|
||||||
<h1 className="text-2xl">Маршруты</h1>
|
<h1 className="text-2xl">Маршруты</h1>
|
||||||
<CreateButton label="Создать маршрут" path="/route/create" />
|
{canWriteRoutes && (
|
||||||
|
<CreateButton label="Создать маршрут" path="/route/create" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteRoutes && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -178,25 +181,37 @@ export const RouteListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection={authStore.canWrite("routes")}
|
checkboxSelection={canWriteRoutes}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteRoutes
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map((id: string | number) =>
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
<Box sx={{ mt: 5, textAlign: "center", color: "text.secondary" }}>
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ const LinkedStationsContentsInner = <
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching all stations:", error);
|
console.error("Error fetching all stations:", error);
|
||||||
setError("Failed to load available stations");
|
setError(null);
|
||||||
setAllItems([]);
|
setAllItems([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ const LinkedSightsContentsInner = <
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching all sights:", error);
|
console.error("Error fetching all sights:", error);
|
||||||
setError("Failed to load available sights");
|
setError(null);
|
||||||
setAllItems([]);
|
setAllItems([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,75 +149,69 @@ export const StationListPage = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
{canWriteStations && ids.length > 0 && (
|
||||||
<button
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
className={`px-4 py-2 rounded flex gap-2 items-center transition-all ${
|
<button
|
||||||
ids.length > 0
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
? "bg-red-500 text-white cursor-pointer opacity-100"
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
: "bg-gray-300 text-gray-500 cursor-not-allowed opacity-0 pointer-events-none"
|
>
|
||||||
}`}
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
onClick={() => {
|
{ids.length})
|
||||||
if (ids.length > 0) {
|
</button>
|
||||||
setIsBulkDeleteModalOpen(true);
|
</div>
|
||||||
}
|
)}
|
||||||
}}
|
|
||||||
disabled={ids.length === 0}
|
|
||||||
>
|
|
||||||
<Trash2
|
|
||||||
size={20}
|
|
||||||
className={ids.length > 0 ? "text-white" : "text-gray-500"}
|
|
||||||
/>
|
|
||||||
Удалить выбранные ({ids.length})
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection
|
checkboxSelection={canWriteStations}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteStations
|
||||||
const selectedIds = newSelection
|
? (newSelection: any) => {
|
||||||
.map((id: string | number) => {
|
if (Array.isArray(newSelection)) {
|
||||||
const numId =
|
const selectedIds = newSelection
|
||||||
typeof id === "string"
|
.map((id: string | number) => {
|
||||||
? Number.parseInt(id, 10)
|
const numId =
|
||||||
: Number(id);
|
typeof id === "string"
|
||||||
return numId;
|
? Number.parseInt(id, 10)
|
||||||
})
|
: Number(id);
|
||||||
.filter(
|
return numId;
|
||||||
(id: number) =>
|
})
|
||||||
!Number.isNaN(id) && id !== null && id !== undefined
|
.filter(
|
||||||
);
|
(id: number) =>
|
||||||
setIds(selectedIds);
|
!Number.isNaN(id) && id !== null && id !== undefined
|
||||||
} else if (
|
);
|
||||||
newSelection &&
|
setIds(selectedIds);
|
||||||
typeof newSelection === "object" &&
|
} else if (
|
||||||
"ids" in newSelection
|
newSelection &&
|
||||||
) {
|
typeof newSelection === "object" &&
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
"ids" in newSelection
|
||||||
const selectedIds = Array.from(idsSet)
|
) {
|
||||||
.map((id: string | number) => {
|
const idsSet = newSelection.ids as Set<string | number>;
|
||||||
const numId =
|
const selectedIds = Array.from(idsSet)
|
||||||
typeof id === "string"
|
.map((id: string | number) => {
|
||||||
? Number.parseInt(id, 10)
|
const numId =
|
||||||
: Number(id);
|
typeof id === "string"
|
||||||
return numId;
|
? Number.parseInt(id, 10)
|
||||||
})
|
: Number(id);
|
||||||
.filter(
|
return numId;
|
||||||
(id: number) =>
|
})
|
||||||
!Number.isNaN(id) && id !== null && id !== undefined
|
.filter(
|
||||||
);
|
(id: number) =>
|
||||||
setIds(selectedIds);
|
!Number.isNaN(id) && id !== null && id !== undefined
|
||||||
} else {
|
);
|
||||||
setIds([]);
|
setIds(selectedIds);
|
||||||
}
|
} else {
|
||||||
}}
|
setIds([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const UserListPage = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteUsers && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -145,18 +145,30 @@ export const UserListPage = observer(() => {
|
|||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteUsers
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const VehicleListPage = observer(() => {
|
|||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
});
|
});
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const canWriteVehicles = authStore.canWrite("devices");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -156,7 +157,7 @@ export const VehicleListPage = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ids.length > 0 && (
|
{canWriteVehicles && ids.length > 0 && (
|
||||||
<div className="flex justify-end mb-5 duration-300">
|
<div className="flex justify-end mb-5 duration-300">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
@@ -171,24 +172,36 @@ export const VehicleListPage = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection={authStore.canWrite("devices")}
|
checkboxSelection={canWriteVehicles}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
onRowSelectionModelChange={(newSelection: any) => {
|
onRowSelectionModelChange={
|
||||||
if (Array.isArray(newSelection)) {
|
canWriteVehicles
|
||||||
const selectedIds = newSelection.map((id: string | number) => Number(id));
|
? (newSelection: any) => {
|
||||||
setIds(selectedIds);
|
if (Array.isArray(newSelection)) {
|
||||||
} else if (newSelection && typeof newSelection === 'object' && 'ids' in newSelection) {
|
const selectedIds = newSelection.map(
|
||||||
const idsSet = newSelection.ids as Set<string | number>;
|
(id: string | number) => Number(id)
|
||||||
const selectedIds = Array.from(idsSet).map((id: string | number) => Number(id));
|
);
|
||||||
setIds(selectedIds);
|
setIds(selectedIds);
|
||||||
} else {
|
} else if (
|
||||||
setIds([]);
|
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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
localeText={ruRU.components.MuiDataGrid.defaultProps.localeText}
|
||||||
slots={{
|
slots={{
|
||||||
noRowsOverlay: () => (
|
noRowsOverlay: () => (
|
||||||
|
|||||||
@@ -152,8 +152,6 @@ class AuthStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
canAccess = (permission: string): boolean => {
|
canAccess = (permission: string): boolean => {
|
||||||
// If permission looks like a concrete role (e.g. snapshot_create/snapshot_rw),
|
|
||||||
// check it as-is; otherwise treat it as a resource name.
|
|
||||||
if (permission.includes("_")) {
|
if (permission.includes("_")) {
|
||||||
return this.hasRole(permission);
|
return this.hasRole(permission);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ import { MapPin } from "lucide-react";
|
|||||||
|
|
||||||
export const CitySelector: React.FC = observer(() => {
|
export const CitySelector: React.FC = observer(() => {
|
||||||
const { selectedCity, setSelectedCity } = selectedCityStore;
|
const { selectedCity, setSelectedCity } = selectedCityStore;
|
||||||
const canReadCities = authStore.canRead("cities");
|
const canLoadAllCities = authStore.isAdmin && authStore.canRead("cities");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canReadCities) {
|
if (canLoadAllCities) {
|
||||||
cityStore.getCities("ru");
|
cityStore.getCities("ru");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
authStore.fetchMeCities().catch(() => undefined);
|
authStore.fetchMeCities().catch(() => undefined);
|
||||||
}, [canReadCities]);
|
}, [canLoadAllCities]);
|
||||||
|
|
||||||
const baseCities: City[] = canReadCities
|
const baseCities: City[] = canLoadAllCities
|
||||||
? cityStore.cities["ru"].data
|
? cityStore.cities["ru"].data
|
||||||
: authStore.meCities["ru"].map((uc) => ({
|
: authStore.meCities["ru"].map((uc) => ({
|
||||||
id: uc.city_id,
|
id: uc.city_id,
|
||||||
|
|||||||
@@ -781,7 +781,7 @@ export const DevicesTable = observer(() => {
|
|||||||
Добавить устройство
|
Добавить устройство
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{selectedIds.length > 0 && (
|
{canWriteDevices && selectedIds.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="error"
|
color="error"
|
||||||
@@ -862,16 +862,18 @@ export const DevicesTable = observer(() => {
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
rows={groupRows}
|
rows={groupRows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
checkboxSelection
|
checkboxSelection={canWriteDevices}
|
||||||
disableRowSelectionExcludeModel
|
disableRowSelectionExcludeModel
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
onPaginationModelChange={setPaginationModel}
|
||||||
pageSizeOptions={[50]}
|
pageSizeOptions={[50]}
|
||||||
onRowSelectionModelChange={
|
onRowSelectionModelChange={
|
||||||
createSelectionHandler(groupRowIds) as (
|
canWriteDevices
|
||||||
ids: unknown,
|
? (createSelectionHandler(groupRowIds) as (
|
||||||
) => void
|
ids: unknown,
|
||||||
|
) => void)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
rowSelectionModel={{
|
rowSelectionModel={{
|
||||||
type: "include",
|
type: "include",
|
||||||
|
|||||||
Reference in New Issue
Block a user