I’ve been getting more and more requests lately to build custom booking systems with admin panels that manage records and directories. This comes up a lot in B2C services — think beauty studios or clinics — where the admin panel has to be quick to build, affordable, and seamlessly integrated into existing workflows. Nobody wants to spend weeks training staff or risk disrupting daily operations.
Most aggregator platforms stick to a linear model: one specialist, one service, one time slot. Sounds simple — until you need to handle group bookings or overlapping workflows where one staff member is involved in multiple stages of a procedure.
Throw in the constant stream of changes — cancellations, reschedules, service swaps — and you quickly realize: the system has to react instantly and update the interface in real time.
That’s why so many businesses turn to custom-built solutions. But as a developer, I kept asking myself:
- Which frameworks are actually good for booking-system admin panels?
- What do I do if a standard CRUD generator can’t handle the business logic?
- How do I build a custom UI without breaking system compatibility?
- And most importantly: how do I reduce dev time without sacrificing flexibility?
Why I Built Admiral Instead of Using Standard CRUD Generators
Like most devs, I started out with off-the-shelf CRUD generators. The “ready-to-go in 10 minutes” promise is tempting. But every time, I hit the same wall: they were too rigid. Their structures were locked down, and whenever I tried to implement custom business logic, it felt like fighting the framework instead of working with it.
That frustration is exactly what pushed me to build Admiral — a framework we now use in over 70% of our projects. It’s open source, and you can check it out here: https://github.com/dev-family/admiral.
What makes Admiral different?
- I can build workflows that go way beyond standard CRUD logic.
- I can spin up interactive dashboards with charts, metrics, and widgets that are actually useful to the client.
Admiral doesn’t force the business to adapt to the framework. It adapts to the business.
How It Works Under the Hood
Instead of generating static files with rigid templates, Admiral gives me an extensible architecture with a flexible routing system and modular components. This means I can inject custom UI and logic wherever I need.
The result? A powerful toolset that doesn’t box me in.
Let me walk you through how I used Admiral to customize CRUD for one of my recent projects — a chain of spa salons.
Their admins needed two things:
- Create and manage bookings.
- Monitor staff workload via a calendar.
Here’s how I built it.
Managing Bookings Page
I started by building a custom page for creating and editing bookings. I used a mix of my own components and Admiral’s built-in ones to keep everything consistent.
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
Once that was done, I wired it into Admiral’s routing system with create.tsx
, [id].tsx
, and index.tsx
.
// create.tsx
import React from 'react'
import RecordPage, { RecordPageType } from '../RecordPage'
const CreateRecordPage = () => {
return <RecordPage type={RecordPageType.CREATE} />
}
export default CreateRecordPage
// [id].tsx
import React from 'react'
import RecordPage, { RecordPageType } from '../RecordPage'
const EditRecordPage = () => {
return <RecordPage type={RecordPageType.EDIT} />
}
export default EditRecordPage
// index.tsx
import { CRUD } from '@/src/crud/records'
export default CRUD.IndexPage
With just that, I had a CRUD interface with a table of records plus custom create/edit pages.
Creating a Booking Calendar
Of course, a CRUD table alone wasn’t enough for the client. They needed a calendar view where admins could track workloads, open slots, and rescheduled bookings.
In Admiral, I just created another custom page, same as with CRUD.
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="Main page">
<div className={styles.calendar_page}>
<CalendarHeader />
<CalendarRecordNote comment={comment} />
<CalendarTable
isLoading={isLoading}
timeSlots={timeSlots}
services={services}
calendarHeaderData={calendarHeaderData}
/>
</div>
</Page>
}
export default EditRecordPage
This gave the admins a clean, visual way to see workloads and manage bookings.
Adding Pages to Navigation
Finally, I added the new pages to the project’s menu so they were easy to access.
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
Wrapping Up
After using Admiral in multiple projects, here’s what I’ve learned firsthand:
- It’s built for complex logic — where typical CRUD generators give up, Admiral keeps going.
- It’s flexible without the overhead — I can plug in whatever I want: custom UI, business logic, calendars, widgets.
- It’s fast to launch and easy to scale — start with a simple CRUD table, then keep layering on complexity.
For me, Admiral has saved dozens of dev hours and let me focus on delivering actual business value instead of hacking around framework limitations.
Top comments (0)