Demo project
Barbershop dashboard: BROMID
A barber × time grid, drag-to-reschedule, loyalty tracking and studio analytics.
development · Next.js 15 · TypeScript · Tailwind · Zustand

The task
BROMID is a barbershop in Yekaterinburg: four barbers, four chairs, open from 10:00 to 22:00. A demo project - the studio is fictional, the brief is the ordinary one for the trade. In the premise clients book through a Telegram bot, but somebody still runs the front desk, and the shift is the same handful of questions all day long: who is in the chair now, who is coming tonight, who never showed up, can the six o’clock booking slide half an hour so one more fits. Answered from a paper diary or a group chat, every question costs a minute and a phone call. The panel has to answer at a glance, and a booking has to move with the mouse rather than through a conversation.
The solution
Five screens - schedule, bookings, clients, barbers, analytics - plus sign-in, a disclaimer page and a 404: eight statically exported routes. There is no backend, everything is computed in the browser.
- The schedule is a barber × time grid for a day and a day × time grid for a week filtered to one barber. Working window 10:00-22:00, a 15-minute step, a line marking the current moment.
- Rescheduling by drag: a ghost follows the pointer, snapping to the quarter hour, while the tile itself dims and stays where it was until you let go. On a free slot the ghost draws a mustard outline, on an impossible one a red dashed outline with the reason written on it - slot taken, slot already past, outside working hours, date outside the data window. Drop it on a taken slot and the booking stays where it was, with a notice explaining why. Esc cancels the drag mid-move.
- In the day grid a drag changes the barber and the time; in the week grid, the day and the time.
- A status change and a reschedule are each confirmed by a notice with an “Undo” button: one step back, for as long as the notice is on screen. A refused drop gets a notice too, but there is nothing in it to undo.
- Bookings: a list with period, barber and status filters, search and pagination 25 rows at a time. The booking card holds the status history, the price of the visit and a form to move the booking to another barber, another date or another time.
- Clients: a directory with segments - regulars, new, close to a free haircut, booked ahead - and a card with visit history and a six-step loyalty scale: every sixth haircut is free.
- Barbers and analytics: chair utilisation, revenue, average ticket, no-show and cancellation shares, service demand, the busiest hour, day-by-day charts.
Details that are easy to miss
- The data is deterministic: a hand-rolled seeded PRNG, and “now” is a constant rather than the clock. Every run renders exactly the same picture. A test pins the sha256 of the dataset, so the generator cannot quietly drift away from the screenshots.
- Demand is modelled, not scattered at random: evenings are denser than afternoons, weekends denser than weekdays, and the far end of the future is half empty because those bookings have not arrived yet. Utilisation comes out at 57%: a test holds it inside a 55-65% corridor, and the exact figure is pinned by that same dataset digest.
- A cancelled booking does not hold its slot and is drawn struck through, so a new one can go on top of it. A no-show does hold the slot, because the chair stood idle anyway. That distinction is half the reason statuses exist.
- Loyalty recalculates itself: mark a client as arrived and, if that was the sixth qualifying haircut, the price drops to zero with no manual edit. Beard, grey camouflage and waxing do not move the counter.
- Six qualifying haircuts do not fit into the data window: across 45 days a client averages three bookings. So most clients carry history from before the window - otherwise the loyalty scale would sit empty on every screen. For 82 clients out of 577 there is no such history, and the panel labels them new outright.
- Status is never carried by colour alone: every tile pairs the colour bar with an icon, a cancelled booking is struck through as well, and the word itself sits in the legend above the grid and in the list badges. People look at this screen for eight hours straight.
- Sign-in is demo-only and says so: the login and password are printed on the form and already filled in, the check happens in the browser, the session lives in sessionStorage. It is a gated sign-in, not real protection - the panel has a single role.
- Demo hygiene: a demo notice in the sidebar, a separate disclaimer page, noindex in the metadata and X-Robots-Tag in the deployment headers.
- There is no mobile layout by design: the schedule grid is unreadable at 375px. Breakpoints are 1440 / 1024 / 768.
The numbers
A 45-day data window holding 1,733 bookings and 577 clients. What matches the studio’s own storefront is the reference data: the same four barbers with the same specialities, the same eight-item price list at the same prices, the same opening hours and the same every-sixth-haircut rule. 135 automated tests, 17 of them on rescheduling, plus a 32-check scenario walking the panel as the front-desk admin: sign in, change a status, undo it, drag a booking across, get refused on a taken slot, sign out.
Screenshots




