// Estetic Hall — планы зала (top-down).
// mode='hall' — общий зал: 7 парикмахеров (синие столы) + 2 визажиста.
// mode='nails' — отдельная комната: маникюр (5) + педикюр (2).
// readonly + bookedRoomIds — режим «карта забронированных мест» для админ-дашборда.
const FloorPlan = ({ rooms, onSelect, hovered, setHovered, mode = 'hall', readonly = false, bookedRoomIds = null }) => {
const occupied = bookedRoomIds ? new Set(bookedRoomIds.map(Number)) : null;
const colorFor = (st) => st === 'free' ? '#5d8a72' : st === 'partial' ? '#c9913d' : '#b56666';
const statusColorOf = (room) => occupied
? (occupied.has(Number(room.id)) ? '#b56666' : '#5d8a72')
: colorFor(room.status);
const byZone = (z) => rooms.filter(r => r.zone === z);
const hair = byZone('hair'), nails = byZone('nails'), makeup = byZone('makeup'), ped = byZone('pedicure');
const noop = () => {};
const select = readonly ? noop : (onSelect || noop);
const setHov = readonly ? noop : (setHovered || noop);
// ====== Ячейка-место ======
// blueTable=true → столы парикмахеров синие. В readonly-режиме заливка показывает занятость.
const Cell = ({ room, x, y, w, h, wall, blueTable }) => {
if (!room) return null;
const isH = !readonly && hovered === room.id;
const stColor = statusColorOf(room);
let mirror, chairCx, chairCy, numX, numY;
if (wall === 'top') {
mirror = { x: x + 4, y: y - 3, w: w - 8, h: 4 };
chairCx = x + w / 2; chairCy = y + h + 14;
numX = x + w / 2; numY = y + h / 2 + 5;
} else if (wall === 'bot') {
mirror = { x: x + 4, y: y + h - 1, w: w - 8, h: 4 };
chairCx = x + w / 2; chairCy = y - 14;
numX = x + w / 2; numY = y + h / 2 + 5;
} else if (wall === 'right') {
mirror = { x: x + w - 1, y: y + 4, w: 4, h: h - 8 };
chairCx = x - 14; chairCy = y + h / 2;
numX = x + w / 2; numY = y + h / 2 + 5;
} else { // left
mirror = { x: x - 3, y: y + 4, w: 4, h: h - 8 };
chairCx = x + w + 14; chairCy = y + h / 2;
numX = x + w / 2; numY = y + h / 2 + 5;
}
// Заливка стола
let tableFill = '#cba374', tableStroke = '#7a5a30', numColor = '#0f1419', tableOpacity = 0.92;
if (readonly) {
tableFill = stColor; tableOpacity = 0.5; tableStroke = stColor; numColor = '#0f1419';
} else if (isH) {
tableFill = stColor; tableOpacity = 0.55; tableStroke = stColor; numColor = '#ffffff';
} else if (blueTable) {
tableFill = '#066aab'; tableOpacity = 0.9; tableStroke = '#044270'; numColor = '#ffffff';
}
return (
setHov(room.id)} onMouseLeave={() => setHov(null)}
onClick={() => select(room)} style={{ cursor: readonly ? 'default' : 'pointer' }}>
{(wall === 'top' || wall === 'bot') && (
)}
{(wall === 'left' || wall === 'right') && (
)}
{room.num}
);
};
const tilePattern = (
);
const hoverHint = (x, y) => {
if (readonly) return null;
const room = hovered ? rooms.find(r => r.id === hovered) : null;
return (
{room
? `${room.num} · ${room.name} · от ${room.prices.hour}₽/ч`
: 'Наведите на место · кликните, чтобы забронировать'}
);
};
// ============================================================
// ПЛАН 2 — Маникюр + Педикюр (отдельная комната)
// ============================================================
if (mode === 'nails') {
const pedPos = [
{ x: 70, y: 78, w: 130, h: 58, wall: 'top' },
{ x: 250, y: 78, w: 130, h: 58, wall: 'top' },
];
const manicurePos = [
{ x: 50, y: 300, w: 120, h: 52, wall: 'left' },
{ x: 50, y: 420, w: 120, h: 52, wall: 'left' },
{ x: 280, y: 280, w: 120, h: 50, wall: 'right' },
{ x: 280, y: 392, w: 120, h: 50, wall: 'right' },
{ x: 280, y: 504, w: 120, h: 50, wall: 'right' },
];
return (
);
}
// ============================================================
// ПЛАН 1 — Общий зал: 7 парикмахеров (синие) + 2 визажиста
// ============================================================
const hairTopPos = Array.from({ length: 4 }, (_, i) => ({ x: 210 + i * 116, y: 86, w: 88, h: 48, wall: 'top' }));
const hairBotPos = Array.from({ length: 3 }, (_, i) => ({ x: 340 + i * 108, y: 566, w: 84, h: 46, wall: 'bot' }));
const makeupPos = [
{ x: 46, y: 300, w: 104, h: 62, wall: 'left' },
{ x: 46, y: 380, w: 104, h: 62, wall: 'left' },
];
return (
);
};
window.FloorPlan = FloorPlan;