Розробка адміністративної панелі на React Admin
React Admin — це фреймворк для побудови адміністративних інтерфейсів поверх REST або GraphQL API. Він надає готові компоненти для CRUD-операцій, фільтрації, пагінації, аутентифікації та управління правами. Основна цінність — швидкість розробки: типовий розділ з таблицею та формами створення й редагування займає години, а не дні.
Встановлення та структура
npm install react-admin ra-data-simple-rest
# або для GraphQL:
npm install react-admin ra-data-graphql-simple
src/
admin/
App.tsx — кореневий компонент
dataProvider.ts — адаптер API
authProvider.ts — аутентифікація
resources/
users/
UserList.tsx
UserEdit.tsx
UserCreate.tsx
products/
ProductList.tsx
ProductEdit.tsx
dataProvider
Адаптер між React Admin та вашим API. ra-data-simple-rest працює з REST API де:
-
GET /users— список з заголовкомContent-Range -
GET /users/1— запис -
POST /users— створення -
PUT /users/1— оновлення -
DELETE /users/1— видалення
// dataProvider.ts
import simpleRestProvider from 'ra-data-simple-rest';
import { fetchUtils } from 'react-admin';
const httpClient = (url: string, options: fetchUtils.Options = {}) => {
const token = localStorage.getItem('auth_token');
const headers = new Headers(options.headers);
if (token) headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, { ...options, headers });
};
export const dataProvider = simpleRestProvider(
import.meta.env.VITE_API_URL,
httpClient
);
Для нестандартного API — користувацький dataProvider:
import { DataProvider } from 'react-admin';
export const dataProvider: DataProvider = {
getList: async (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const res = await api.get(`/${resource}`, {
params: {
_page: page,
_limit: perPage,
_sort: field,
_order: order.toLowerCase(),
...params.filter,
},
});
return {
data: res.data.items,
total: res.data.total,
};
},
getOne: async (resource, { id }) => {
const { data } = await api.get(`/${resource}/${id}`);
return { data };
},
create: async (resource, { data }) => {
const res = await api.post(`/${resource}`, data);
return { data: res.data };
},
update: async (resource, { id, data }) => {
const res = await api.put(`/${resource}/${id}`, data);
return { data: res.data };
},
delete: async (resource, { id }) => {
await api.delete(`/${resource}/${id}`);
return { data: { id } as any };
},
deleteMany: async (resource, { ids }) => {
await Promise.all(ids.map((id) => api.delete(`/${resource}/${id}`)));
return { data: ids };
},
updateMany: async (resource, { ids, data }) => {
await Promise.all(ids.map((id) => api.put(`/${resource}/${id}`, data)));
return { data: ids };
},
getManyReference: async (resource, params) => {
// Для пов'язаних записів (наприклад, коментарі поста)
const res = await api.get(`/${resource}`, {
params: { [params.target]: params.id },
});
return { data: res.data.items, total: res.data.total };
},
getMany: async (resource, { ids }) => {
const res = await api.get(`/${resource}`, {
params: { ids: ids.join(',') },
});
return { data: res.data };
},
};
authProvider
import { AuthProvider } from 'react-admin';
export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error('Неверний логін або пароль');
const { token, user } = await res.json();
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_user', JSON.stringify(user));
},
logout: () => {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
return Promise.resolve();
},
checkAuth: () =>
localStorage.getItem('auth_token') ? Promise.resolve() : Promise.reject(),
checkError: ({ status }) => {
if (status === 401 || status === 403) {
localStorage.removeItem('auth_token');
return Promise.reject();
}
return Promise.resolve();
},
getPermissions: () => {
const user = JSON.parse(localStorage.getItem('auth_user') ?? '{}');
return Promise.resolve(user.role ?? 'guest');
},
getIdentity: () => {
const user = JSON.parse(localStorage.getItem('auth_user') ?? '{}');
return Promise.resolve({
id: user.id,
fullName: user.name,
avatar: user.avatar,
});
},
};
Ресурс: список та форми
// resources/users/UserList.tsx
import { List, Datagrid, TextField, EmailField, DateField, EditButton, DeleteButton, Filter, TextInput, SelectInput } from 'react-admin';
const UserFilter = () => (
<Filter>
<TextInput label="Пошук" source="q" alwaysOn />
<SelectInput source="role" choices={[
{ id: 'admin', name: 'Адміністратор' },
{ id: 'editor', name: 'Редактор' },
{ id: 'user', name: 'Користувач' },
]} />
</Filter>
);
export const UserList = () => (
<List filters={<UserFilter />} sort={{ field: 'createdAt', order: 'DESC' }}>
<Datagrid rowClick="edit" bulkActionButtons={false}>
<TextField source="id" />
<TextField source="name" label="Ім'я" />
<EmailField source="email" />
<TextField source="role" label="Роль" />
<DateField source="createdAt" label="Створено" showTime />
<EditButton />
<DeleteButton />
</Datagrid>
</List>
);
// resources/users/UserEdit.tsx
import { Edit, SimpleForm, TextInput, SelectInput, required, email } from 'react-admin';
export const UserEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="name" label="Ім'я" validate={required()} />
<TextInput source="email" label="Email" validate={[required(), email()]} />
<SelectInput source="role" label="Роль" choices={[
{ id: 'admin', name: 'Адміністратор' },
{ id: 'editor', name: 'Редактор' },
{ id: 'user', name: 'Користувач' },
]} />
</SimpleForm>
</Edit>
);
App.tsx
import { Admin, Resource } from 'react-admin';
import { dataProvider } from './dataProvider';
import { authProvider } from './authProvider';
import { UserList, UserEdit, UserCreate } from './resources/users';
import { ProductList, ProductEdit, ProductCreate } from './resources/products';
import PeopleIcon from '@mui/icons-material/People';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
export function AdminApp() {
return (
<Admin
dataProvider={dataProvider}
authProvider={authProvider}
basename="/admin"
>
<Resource
name="users"
list={UserList}
edit={UserEdit}
create={UserCreate}
icon={PeopleIcon}
options={{ label: 'Користувачі' }}
/>
<Resource
name="products"
list={ProductList}
edit={ProductEdit}
create={ProductCreate}
icon={ShoppingCartIcon}
options={{ label: 'Товари' }}
/>
</Admin>
);
}
Кастомізація теми та макета
import { defaultTheme, Layout, AppBar, Menu } from 'react-admin';
const myTheme = {
...defaultTheme,
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' },
background: { default: '#f5f5f5' },
},
components: {
...defaultTheme.components,
RaDatagrid: {
styleOverrides: {
root: {
'& .RaDatagrid-headerCell': {
backgroundColor: '#f0f0f0',
},
},
},
},
},
};
const CustomLayout = (props: any) => (
<Layout {...props} appBar={AppBar} menu={Menu} />
);
<Admin theme={myTheme} layout={CustomLayout} />
Управління правами доступу
// Показуємо кнопки тільки потрібним ролям
import { usePermissions, EditButton, DeleteButton } from 'react-admin';
const UserActions = () => {
const { permissions } = usePermissions();
return (
<>
{['admin', 'editor'].includes(permissions) && <EditButton />}
{permissions === 'admin' && <DeleteButton />}
</>
);
};
Строки виконання
- MVP (3–5 ресурсів, стандартний CRUD, базова автентифікація): 3–5 днів
- Повноцінна панель (користувацький dataProvider, складні форми, завантаження файлів, права, користувацька тема): 2–3 тижні
- З дашбордом, графіками, користувацькими компонентами: ще 1–2 тижні







