Разработка административной панели на Refine

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка административной панели на Refine
Средняя
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1228
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1165
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    861
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1074
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    839

Разработка административной панели на Refine

Refine — headless React-фреймворк для admin-интерфейсов. В отличие от React Admin, который поставляет компоненты на базе Material UI, Refine разделяет логику и UI: ядро управляет состоянием, маршрутизацией, data fetching, правами — а UI-компоненты выбирает разработчик (Ant Design, Material UI, Chakra, Mantine, или полностью кастомные).

Установка

npm create refine-app@latest -- --preset refine-nextjs
# или для Vite + Ant Design:
npm create refine-app@latest -- --preset refine-vite

Ручная установка для существующего проекта:

npm install @refinedev/core @refinedev/react-router-v6
# UI пакет на выбор:
npm install @refinedev/antd antd
# или:
npm install @refinedev/mui @mui/material @emotion/react

Структура проекта

src/
  App.tsx
  providers/
    dataProvider.ts
    authProvider.ts
  pages/
    users/
      list.tsx
      edit.tsx
      create.tsx
      show.tsx
    products/
      list.tsx
      edit.tsx

App.tsx

import { Refine } from '@refinedev/core';
import { RefineThemes, ThemedLayoutV2 } from '@refinedev/antd';
import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';
import { ConfigProvider } from 'antd';
import ruRU from 'antd/locale/ru_RU';
import { dataProvider } from './providers/dataProvider';
import { authProvider } from './providers/authProvider';
import { UserList, UserEdit, UserCreate } from './pages/users';
import { ProductList, ProductEdit } from './pages/products';

export function App() {
  return (
    <BrowserRouter>
      <ConfigProvider theme={RefineThemes.Blue} locale={ruRU}>
        <Refine
          dataProvider={dataProvider}
          authProvider={authProvider}
          routerProvider={routerBindings}
          resources={[
            {
              name: 'users',
              list: '/users',
              create: '/users/create',
              edit: '/users/edit/:id',
              show: '/users/show/:id',
              meta: { label: 'Пользователи', icon: <UserOutlined /> },
            },
            {
              name: 'products',
              list: '/products',
              edit: '/products/edit/:id',
              meta: { label: 'Товары' },
            },
          ]}
          options={{ syncWithLocation: true }}
        >
          <Routes>
            <Route element={<ThemedLayoutV2><Outlet /></ThemedLayoutV2>}>
              <Route path="/users" element={<UserList />} />
              <Route path="/users/create" element={<UserCreate />} />
              <Route path="/users/edit/:id" element={<UserEdit />} />
              <Route path="/products" element={<ProductList />} />
              <Route path="/products/edit/:id" element={<ProductEdit />} />
            </Route>
          </Routes>
        </Refine>
      </ConfigProvider>
    </BrowserRouter>
  );
}

dataProvider

Refine использует стандартизированный интерфейс dataProvider:

import { DataProvider } from '@refinedev/core';
import axios from 'axios';

const api = axios.create({ baseURL: import.meta.env.VITE_API_URL });

export const dataProvider: DataProvider = {
  getList: async ({ resource, pagination, sorters, filters }) => {
    const { current = 1, pageSize = 10 } = pagination ?? {};

    const sortParams = sorters?.reduce((acc, s) => ({
      ...acc,
      [`sort[${s.field}]`]: s.order,
    }), {});

    const filterParams = filters?.reduce((acc, f) => {
      if (f.operator === 'eq') return { ...acc, [f.field]: f.value };
      if (f.operator === 'contains') return { ...acc, [`${f.field}_like`]: f.value };
      return acc;
    }, {});

    const { data } = await api.get(`/${resource}`, {
      params: {
        _page: current,
        _limit: pageSize,
        ...sortParams,
        ...filterParams,
      },
    });

    return {
      data: data.items,
      total: data.total,
    };
  },

  getOne: async ({ resource, id }) => {
    const { data } = await api.get(`/${resource}/${id}`);
    return { data };
  },

  create: async ({ resource, variables }) => {
    const { data } = await api.post(`/${resource}`, variables);
    return { data };
  },

  update: async ({ resource, id, variables }) => {
    const { data } = await api.patch(`/${resource}/${id}`, variables);
    return { data };
  },

  deleteOne: async ({ resource, id }) => {
    const { data } = await api.delete(`/${resource}/${id}`);
    return { data };
  },

  getApiUrl: () => import.meta.env.VITE_API_URL,
};

Список с useTable

Refine предоставляет хуки для работы с данными. useTable управляет пагинацией, сортировкой и фильтрацией:

// pages/users/list.tsx
import { useTable } from '@refinedev/antd';
import { Table, Space, Button, Input } from 'antd';
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useDeleteMany } from '@refinedev/core';

export function UserList() {
  const { tableProps, searchFormProps } = useTable({
    resource: 'users',
    sorters: { initial: [{ field: 'createdAt', order: 'desc' }] },
    filters: {
      initial: [{ field: 'isActive', operator: 'eq', value: true }],
    },
    syncWithLocation: true,
  });

  const { mutate: deleteMany } = useDeleteMany();

  return (
    <Table
      {...tableProps}
      rowKey="id"
      rowSelection={{
        onChange: (selectedKeys) => {
          // selectedKeys для массовых действий
        },
      }}
    >
      <Table.Column dataIndex="id" title="ID" sorter />
      <Table.Column dataIndex="name" title="Имя" sorter />
      <Table.Column dataIndex="email" title="Email" />
      <Table.Column
        dataIndex="role"
        title="Роль"
        filters={[
          { text: 'Администратор', value: 'admin' },
          { text: 'Редактор', value: 'editor' },
        ]}
      />
      <Table.Column
        title="Действия"
        render={(_, record) => (
          <Space>
            <Button icon={<EditOutlined />} href={`/users/edit/${record.id}`} />
            <Button
              danger
              icon={<DeleteOutlined />}
              onClick={() => deleteMany({ resource: 'users', ids: [record.id] })}
            />
          </Space>
        )}
      />
    </Table>
  );
}

Форма редактирования с useForm

// pages/users/edit.tsx
import { useForm, Edit } from '@refinedev/antd';
import { Form, Input, Select } from 'antd';

export function UserEdit() {
  const { formProps, saveButtonProps, queryResult } = useForm({
    resource: 'users',
    action: 'edit',
    redirect: 'list',
  });

  return (
    <Edit saveButtonProps={saveButtonProps}>
      <Form {...formProps} layout="vertical">
        <Form.Item
          name="name"
          label="Имя"
          rules={[{ required: true, message: 'Введите имя' }]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          name="email"
          label="Email"
          rules={[{ required: true, type: 'email' }]}
        >
          <Input />
        </Form.Item>
        <Form.Item name="role" label="Роль">
          <Select options={[
            { value: 'admin', label: 'Администратор' },
            { value: 'editor', label: 'Редактор' },
            { value: 'user', label: 'Пользователь' },
          ]} />
        </Form.Item>
      </Form>
    </Edit>
  );
}

authProvider

import { AuthProvider } from '@refinedev/core';

export const authProvider: AuthProvider = {
  login: async ({ email, password }) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });

    if (!res.ok) {
      return { success: false, error: { message: 'Неверные учётные данные' } };
    }

    const { token, user } = await res.json();
    localStorage.setItem('token', token);
    localStorage.setItem('user', JSON.stringify(user));

    return { success: true, redirectTo: '/' };
  },

  logout: async () => {
    localStorage.removeItem('token');
    localStorage.removeItem('user');
    return { success: true, redirectTo: '/login' };
  },

  check: async () => {
    const token = localStorage.getItem('token');
    if (token) return { authenticated: true };
    return { authenticated: false, redirectTo: '/login' };
  },

  getPermissions: async () => {
    const user = JSON.parse(localStorage.getItem('user') ?? '{}');
    return user.role;
  },

  getIdentity: async () => {
    const user = JSON.parse(localStorage.getItem('user') ?? '{}');
    return { id: user.id, name: user.name, avatar: user.avatar };
  },

  onError: async (error) => {
    if (error.status === 401) return { logout: true };
    return {};
  },
};

Access Control

Refine поддерживает несколько провайдеров прав: Casbin, Cerbos, кастомный:

import { AccessControlProvider } from '@refinedev/core';

export const accessControlProvider: AccessControlProvider = {
  can: async ({ resource, action, params }) => {
    const user = JSON.parse(localStorage.getItem('user') ?? '{}');

    // admin — всё разрешено
    if (user.role === 'admin') return { can: true };

    // editor — только чтение и редактирование, без удаления
    if (user.role === 'editor') {
      if (action === 'delete') return { can: false, reason: 'Нет прав' };
      return { can: true };
    }

    return { can: false };
  },
};
<Refine accessControlProvider={accessControlProvider}>

Отличие от React Admin

Аспект React Admin Refine
UI зависимость Material UI любой (headless)
Роутинг встроенный react-router / next.js
Кривая обучения ниже чуть выше
Гибкость UI ограничена полная
TypeScript частично полностью
SSR/Next.js сложно нативно

Сроки

  • MVP (3–5 ресурсов, Ant Design, стандартные CRUD): 3–5 дней
  • Полноценная панель (кастомный UI, сложные формы, access control, загрузка файлов): 2–3 недели
  • Интеграция с Next.js App Router и SSR: ещё 3–5 дней