From b4afcdfa3b244b29c650810c4e0b013863758c20 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 28 Mar 2025 03:54:15 +0300 Subject: [PATCH] init `/user` route only for `admin` user --- src/App.tsx | 54 +++++++++++++++-- src/authProvider.ts | 6 ++ src/components/ui/Icons.tsx | 15 ++++- src/locales/ru/translation.json | 7 +++ src/pages/route/create.tsx | 2 +- src/pages/user/create.tsx | 68 +++++++++++++++++++++ src/pages/user/edit.tsx | 67 +++++++++++++++++++++ src/pages/user/index.tsx | 4 ++ src/pages/user/list.tsx | 77 ++++++++++++++++++++++++ src/pages/user/show.tsx | 34 +++++++++++ src/stylesheets/hidden-functionality.css | 10 +++ 11 files changed, 338 insertions(+), 6 deletions(-) create mode 100644 src/pages/user/create.tsx create mode 100644 src/pages/user/edit.tsx create mode 100644 src/pages/user/index.tsx create mode 100644 src/pages/user/list.tsx create mode 100644 src/pages/user/show.tsx diff --git a/src/App.tsx b/src/App.tsx index d267c41..e7d8b58 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,9 +24,11 @@ import {SightList, SightCreate, SightEdit, SightShow} from './pages/sight' import {StationList, StationCreate, StationEdit, StationShow} from './pages/station' import {VehicleList, VehicleCreate, VehicleEdit, VehicleShow} from './pages/vehicle' import {RouteList, RouteCreate, RouteEdit, RouteShow} from './pages/route' +import {UserList, UserCreate, UserEdit, UserShow} from './pages/user' -import {CountryIcon, CityIcon, CarrierIcon, MediaIcon, ArticleIcon, SightIcon, StationIcon, VehicleIcon, RouteIcon} from './components/ui/Icons' +import {CountryIcon, CityIcon, CarrierIcon, MediaIcon, ArticleIcon, SightIcon, StationIcon, VehicleIcon, RouteIcon, UsersIcon} from './components/ui/Icons' import SidebarTitle from './components/ui/SidebarTitle' +import {AdminOnly} from './components/AdminOnly' function App() { return ( @@ -134,7 +136,6 @@ function App() { create: '/vehicle/create', edit: '/vehicle/edit/:id', show: '/vehicle/show/:id', - // добавить SHOW для vehicle->routes (https://wn.krbl.ru/vehicle/routes?id=1) meta: { canDelete: true, label: 'Транспорт', @@ -147,14 +148,24 @@ function App() { create: '/route/create', edit: '/route/edit/:id', show: '/route/show/:id', - // добавить SHOW для route->station (https://wn.krbl.ru/route/station) - // добавить SHOW для route->vehicle (https://wn.krbl.ru/route/vehicle) meta: { canDelete: true, label: 'Маршруты', icon: , }, }, + { + name: 'user', + list: '/user', + create: '/user/create', + edit: '/user/edit/:id', + show: '/user/show/:id', + meta: { + canDelete: true, + label: 'Пользователи', + icon: , + }, + }, ]} options={{ syncWithLocation: true, @@ -238,6 +249,41 @@ function App() { } /> + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + } /> (token) + if (decoded.is_admin) { + document.body.classList.add('is-admin') + } else { + document.body.classList.remove('is-admin') + } return decoded.is_admin ? ['admin'] : ['user'] } catch { + document.body.classList.remove('is-admin') return ['user'] } }, diff --git a/src/components/ui/Icons.tsx b/src/components/ui/Icons.tsx index 8c61c98..f2bf143 100644 --- a/src/components/ui/Icons.tsx +++ b/src/components/ui/Icons.tsx @@ -8,5 +8,18 @@ import CastleIcon from '@mui/icons-material/Castle' import HailIcon from '@mui/icons-material/Hail' import DirectionsBusIcon from '@mui/icons-material/DirectionsBus' import ForkLeftIcon from '@mui/icons-material/ForkLeft' +import PeopleAltIcon from '@mui/icons-material/PeopleAlt' -export {BedtimeIcon as ProjectIcon, PublicIcon as CountryIcon, LocationCityIcon as CityIcon, TramIcon as CarrierIcon, PermMediaIcon as MediaIcon, FeedIcon as ArticleIcon, CastleIcon as SightIcon, HailIcon as StationIcon, DirectionsBusIcon as VehicleIcon, ForkLeftIcon as RouteIcon} +export { + BedtimeIcon as ProjectIcon, + PublicIcon as CountryIcon, + LocationCityIcon as CityIcon, + TramIcon as CarrierIcon, + PermMediaIcon as MediaIcon, + FeedIcon as ArticleIcon, + CastleIcon as SightIcon, + HailIcon as StationIcon, + DirectionsBusIcon as VehicleIcon, + ForkLeftIcon as RouteIcon, + PeopleAltIcon as UsersIcon, // users icon +} diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 1da23c4..f987165 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -112,5 +112,12 @@ "edit": "Редактировать маршрут", "show": "Показать маршрут" } + }, + "user": { + "titles": { + "create": "Создать пользователя", + "edit": "Редактировать пользователя", + "show": "Показать пользователя" + } } } diff --git a/src/pages/route/create.tsx b/src/pages/route/create.tsx index dd85235..086a673 100644 --- a/src/pages/route/create.tsx +++ b/src/pages/route/create.tsx @@ -70,13 +70,13 @@ export const RouteCreate = () => { label={'Номер маршрута *'} name="route_number" /> + field.onChange(e.target.checked)} />} />} /> - (Прямой / Обратный) diff --git a/src/pages/user/create.tsx b/src/pages/user/create.tsx new file mode 100644 index 0000000..6a64d78 --- /dev/null +++ b/src/pages/user/create.tsx @@ -0,0 +1,68 @@ +import {Box, TextField, FormControlLabel, Checkbox} from '@mui/material' +import {Create} from '@refinedev/mui' +import {useForm} from '@refinedev/react-hook-form' +import {Controller} from 'react-hook-form' + +export const UserCreate = () => { + const { + saveButtonProps, + refineCore: {formLoading}, + register, + control, + formState: {errors}, + } = useForm({}) + + return ( + + + + + + + + field.onChange(e.target.checked)} />} />} + /> + + + ) +} diff --git a/src/pages/user/edit.tsx b/src/pages/user/edit.tsx new file mode 100644 index 0000000..275d5ec --- /dev/null +++ b/src/pages/user/edit.tsx @@ -0,0 +1,67 @@ +import {Box, TextField, FormControlLabel, Checkbox} from '@mui/material' +import {Edit} from '@refinedev/mui' +import {useForm} from '@refinedev/react-hook-form' +import {Controller} from 'react-hook-form' + +export const UserEdit = () => { + const { + saveButtonProps, + register, + control, + formState: {errors}, + } = useForm({}) + + return ( + + + + + + + + field.onChange(e.target.checked)} />} />} + /> + + + ) +} diff --git a/src/pages/user/index.tsx b/src/pages/user/index.tsx new file mode 100644 index 0000000..0f886cc --- /dev/null +++ b/src/pages/user/index.tsx @@ -0,0 +1,4 @@ +export * from './create' +export * from './edit' +export * from './list' +export * from './show' diff --git a/src/pages/user/list.tsx b/src/pages/user/list.tsx new file mode 100644 index 0000000..845879c --- /dev/null +++ b/src/pages/user/list.tsx @@ -0,0 +1,77 @@ +import React from 'react' +import {type GridColDef} from '@mui/x-data-grid' +import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui' +import {CustomDataGrid} from '../../components/CustomDataGrid' +import {localeText} from '../../locales/ru/localeText' +import {Typography} from '@mui/material' + +export const UserList = () => { + const {dataGridProps} = useDataGrid({}) + + const columns = React.useMemo( + () => [ + { + field: 'id', + headerName: 'ID', + type: 'number', + minWidth: 50, + align: 'left', + headerAlign: 'left', + }, + { + field: 'name', + headerName: 'Имя', + type: 'string', + minWidth: 250, + align: 'left', + headerAlign: 'left', + }, + { + field: 'email', + headerName: 'Email', + type: 'string', + minWidth: 250, + align: 'left', + headerAlign: 'left', + }, + { + field: 'is_admin', + headerName: 'Роль', + type: 'boolean', + align: 'left', + headerAlign: 'left', + minWidth: 150, + flex: 1, + // renderCell: ({value}) => , + renderCell: ({value}) => {value ? 'администратор' : 'пользователь'}, + }, + { + field: 'actions', + headerName: 'Действия', + minWidth: 120, + display: 'flex', + align: 'right', + headerAlign: 'center', + sortable: false, + filterable: false, + disableColumnMenu: true, + renderCell: function render({row}) { + return ( + <> + + + + + ) + }, + }, + ], + [], + ) + + return ( + + row.id} /> + + ) +} diff --git a/src/pages/user/show.tsx b/src/pages/user/show.tsx new file mode 100644 index 0000000..f757c5f --- /dev/null +++ b/src/pages/user/show.tsx @@ -0,0 +1,34 @@ +import {Stack, Typography} from '@mui/material' +import {useShow} from '@refinedev/core' +import {Show, TextFieldComponent as TextField} from '@refinedev/mui' + +export const UserShow = () => { + const {query} = useShow({}) + const {data, isLoading} = query + const record = data?.data + + const fields = [ + {label: 'Имя', data: 'name'}, + {label: 'Электронная почта', data: 'email'}, + { + label: 'Администратор', + data: 'is_admin', + render: (value: number[][]) => {value ? 'администратор' : 'пользователь'}, + }, + ] + + return ( + + + {fields.map(({label, data, render}) => ( + + + {label} + + {render ? render(record?.[data]) : } + + ))} + + + ) +} diff --git a/src/stylesheets/hidden-functionality.css b/src/stylesheets/hidden-functionality.css index 5ddb1e5..55a5dfc 100644 --- a/src/stylesheets/hidden-functionality.css +++ b/src/stylesheets/hidden-functionality.css @@ -37,3 +37,13 @@ .MuiBox-root div .css-1enbsbt-MuiButtonBase-root-MuiButton-root { display: none !important; } + +/* Hide users menu item by default */ +a[aria-label='Пользователи'] { + display: none !important; +} + +/* Show for admin users - this class will be added to body */ +body.is-admin a[aria-label='Пользователи'] { + display: flex !important; +}