В последнее время к нам в студию часто поступают запросы на разработку систем бронирования с возможностью управлять записями и справочниками. Особенно это актуально для B2C-сервисов вроде студий красоты и клиник, где нужно быстро и недорого собрать админ-панель, которая сразу включится в работу, без остановки процессов и долгого онбординга.
Многие из существующих систем-агрегаторов работают по линейному принципу. Это значит, что один мастер может за 1 единицу времени оказывать одну конкретную услугу. Из-за чего возникает проблема с групповыми записями, где один и тот же специалист может гибко участвовать в разных частях процедуры.
В самих бронированиях также часто происходит изменения – отмены, переносы, замена одних услуг другими – которые часто влияют на график мастеров и требуют мгновенного обновления интерфейса.
Поэтому компании все чаще обращаются за кастомными решениями. Но у самих разработчиков могут возникнуть вопросы:
Какие фреймворки подходят для создания админ-панелей в системах бронирования?
Что делать, если типовые CRUD-генераторы не справляются с бизнес-логикой?
Можно ли сделать кастомный интерфейс без потери совместимости с остальной системой?
Как сократить время разработки и при этом сохранить гибкость?
Как Admiral обеспечивает кастомизацию без компромиссов
Большинство админок сегодня можно собирать на готовых фреймворках. И мы тоже раньше использовали популярные генераторы CRUD-интерфейсов, которые за 10 минут обещают готовые интерфейсы. Но сталкивались с тем, что у большинства из них – жестко определенная структура, в которой нельзя реализовать или настроить что-либо по своему усмотрению.
Нам нужно было больше свободы, поэтому мы пришли к созданию собственного фреймворка Admiral. И теперь используем его в 70% наших проектов. Это open-source решение, познакомиться с которым можно по ссылке: https://github.com/dev-family/admiral.
Главные преимущества Admiral заключается в возможности:
Реализовать уникальные рабочие процессы, которые не вписываются в стандартную модель CRUD;
Создавать интерактивные дашборды с графиками, статистикой и другими виджетами, которые отображают важные данные в удобном формате.
Прочитать всю историю разработки Admiral можно тут.
Как это работает?
В основе Admiral лежит расширяемая архитектура, которая позволяет вам интегрировать собственные компоненты и логику. Это достигается за счёт гибкой системы маршрутизации и компонентов, а не генерации статических файлов с жёстко заданной структурой.
Так у нас получилось создать мощный и универсальный инструмент, который полностью адаптируется под бизнес-процессы наших клиентов.
Добавление кастомных страниц в Admiral
А теперь хочу поделиться собственным опытом кастомизации CRUD-интерфейсов с помощью Admiral на примере одного из недавних проектов. Нашим клиентом была сеть спа-салонов, где администраторам нужно было решать две основные задачи:
Создавать и редактировать записи;
Отслеживать загруженность мастеров через календарь.
В итоге мы разработали два удобных и эстетичных интерфейса, которые можно адаптировать под разные сценарии. Разберем процесс по шагам, чтобы смогли легко и быстро сделать то же самое для вашего проекта.
Управление записью
Начнем с разработки кастомной страницы создания/редактирования записи с кастомными компонентами.
Создайте React-компонент, который будет отображать вашу кастомную страницу. В нём можно использовать как кастомные компоненты, так и стандартные компоненты Admiral, чтобы сохранить единый стиль и функциональность.
import React, { useCallback } from 'react' import { Page, Form, Notification } from '@devfamily/admiral' import { RecordSection, ClientSection, DiscountSection, PaymentSection } from './components' import api from './api' export enum RecordPageType { CREATE = 'create', EDIT = 'edit', } const RecordPage = ({ type }: { type: RecordPageType }) => { // Function to get default data if this is an edit page const fetchInitialData = useCallback(async () => { if (type === RecordPageType.EDIT) { let data = {} try { // Connect API request to get record data } catch (err) { // Displaying an error notification Notification({ message: Error getting record: ${err?.message}, type: 'error', }) } return Promise.resolve({ data, values: {}, }) } else { // Return empty data for the new record return Promise.resolve({ data: {}, // Сan use default data values: {}, }) } }, [type, idRecord]) // Function for submitting form data const onSubmit = useCallback(async (values) => { // Connect API request to create or edit record }, []) return ( <Page title={type === RecordPageType.CREATE ? 'Add record' : Edit record}> <Form submitData={onSubmit} fetchInitialData={fetchInitialData}> {/* Include custom form sections as separate components */} <RecordSection /> <ClientSection /> <DiscountSection /> <PaymentSection /> </Form> </Page> ) } export default RecordPage
В результате у вас получится вот такой интерфейс c использование кастомных компонентов.

Теперь, когда у вас есть основной компонент RecordPage, нужно создать файлы, которые будут использовать его в зависимости от типа операции (создание и редактирование). Эти файлы будут служить точками входа для ма��шрутизации Admiral.
Далее создайте следующие файлы в директории. Например, /pages/records/:
create.tsx – страница для создания новой записи
import React from 'react' import RecordPage, { RecordPageType } from '../RecordPage' const CreateRecordPage = () => { return <RecordPage type={RecordPageType.CREATE} /> } export default CreateRecordPage [id].tsx – страница редактирования существующей записи ([id] в имени файла позволяет Admiral динамически определять маршрут): import React from 'react' import RecordPage, { RecordPageType } from '../RecordPage' const EditRecordPage = () => { return <RecordPage type={RecordPageType.EDIT} /> } export default EditRecordPage
index.tsx – базовая страница CRUD для отображения таблицы записей:
import { CRUD } from '@/src/crud/records'
export default CRUD.IndexPage
В результате у нас получился полноценный CRUD со списком всех актуальных записей и кастомной страницей создания/редактирования записи.

Создание календаря с записями
Получившаяся CRUD-таблица решает задачу хранения и поиска записей. Но для администраторов этого часто недостаточно, так как им важно следить за загрузкой всех мастеров: где есть свободные окна, какие записи перенесли, и какие услуги пересекаются. Поэтому мы добавили в нашу админ-панель кастомную страницу с календарём, где все бронирования сразу отображаются в общей сетке.
В Admiral кастомные страницы подключаются так же, как и CRUD. Создайте отдельный React-компонент в директории /pages/main/, который отображает календарь с интерактивными элементами:
import React, { useEffect, useState } from 'react' import { CalendarHeader, CalendarTable, CalendarRecordNote } from './ui' import { TCalendarHeaderData, TGroupedServices, TCalendarHeaderData } from './ts' import styles from './CalendarPage.module.scss' const CelendarPage = () => { const [isLoading, setIsLoading] = useState<boolean>(false) const [timeSlots, setTimeSlots] = useState<string[]>([]) const [services, setServices] = useState<Array<TGroupedServices> | null>(null) const [calendarHeaderData, setCalendarHeaderData] = useState<Array<TCalendarHeaderData>>([]) const [comment, setComment] = useState<string>('') const fetchCalendarData = async ({date}: {date: string}) => { // Connect API request to get data } useEffect(() => { fetchCalendarData({ date: urlState.date || new Date() }) }, []) return <Page title="Главная"> <div className={styles.calendar_page}> <CalendarHeader /> <CalendarRecordNote comment={comment} /> <CalendarTable isLoading={isLoading} timeSlots={timeSlots} services={services} calendarHeaderData={calendarHeaderData} /> </div> </Page> } export default EditRecordPage
В результате администраторы могут работать с вот таким наглядным календарем.

Интеграция в навигацию
После создания страниц необходимо добавить их в навигацию проекта, чтобы пользователи могли легко переходить на них.
Для этого создайте файл с навигацией. Например, menu.tsx (если его ещё нет), и добавьте туда новые пункты навигации.
import React from 'react' import { Menu, MenuItemLink } from '@devfamily/admiral' import { FiUsers, FiUser, FiSettings } from 'react-icons/fi' const CustomMenu = () => { return ( <Menu> <MenuItemLink icon="FiUsers" name="Main" to="/main" /> // navigate to custom page <MenuItemLink icon="FiUser" name="Records" to="/records" /> // navigate to page with custom components <MenuItemLink icon="FiSettings" name="Services" to="/services" /> <MenuItemLink icon="FiSettings" name="Masters" to="/masters" /> </Menu> ) } export default CustomMenu
Заключение
Работая с Admiral над разными проектами, мы продолжаем убеждаться в его преимуществах:
Подходит для проектов с нестандартной логикой – там, где типовые CRUD-генераторы быстро упираются в структурные ограничения, мы можем продолжать расширять интерфейс;
Предоставляет гибкость без лишнего кода – фреймворк обладает открытой архитектурой, в которую можно встроить что угодно: кастомные компоненты, бизнес-логику, календарные интерфейсы.
Обеспечивает скорость и масштабируемость – после быстрой сборки базового CRUD, вы сможете и дальше наращивать любые интерфейсы по мере роста проекта.
Благодаря Admiral команда сэкономит десятки часов на разработку и может более глубоко сосредоточиться на ценности проекта для бизнеса.
