// 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 ( {tilePattern} {/* Комната педикюра (верх) */} {/* Комната маникюра (низ) */} {ped.slice(0, 2).map((r, i) => pedPos[i] && )} {nails.slice(0, 5).map((r, i) => manicurePos[i] && )} Педикюр 2 МЕСТА Маникюр 5 МЕСТ {hoverHint(424, 632)} ); } // ============================================================ // ПЛАН 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 ( {tilePattern} {/* ===== Общий зал ===== */} {/* ===== Подложки зон ===== */} {/* ===== Ресепшн — L-стойка в левом верхнем углу (коричневая) ===== */} Ресепшн {/* ===== 2 кресла-мойки — у правой стены ===== */} {[435, 490].map((cy, i) => ( ))} 2 кресла-мойки {/* ===== Места ===== */} {hair.slice(0, 4).map((r, i) => hairTopPos[i] && )} {hair.slice(4, 7).map((r, i) => hairBotPos[i] && )} {makeup.map((r, i) => makeupPos[i] && )} {/* ===== Разделительный столб между П6 и П7 ===== */} {/* ===== Раковина в углу возле П7 ===== */} раковина {/* ===== ФОТОЗОНА — нижний левый угол ===== */} Фотозона КОНТЕНТ · СЪЁМКА {/* ===== Вход — нижний левый угол ===== */} Вход {/* ===== Подписи зон ===== */} парикмахерская · 7 мест визаж · 2 {hoverHint(700, 688)} ); }; window.FloorPlan = FloorPlan;