В последнее время к нам в студию часто поступают запросы на разработку систем бронирования с возможностью управлять записями и справочниками. Особенно это актуально для 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 команда сэкономит десятки часов на разработку и может более глубоко сосредоточиться на ценности проекта для бизнеса.