import { useMemo, useState } from 'react' import { m, mKey } from '@/i18n/messages' import { useLocale } from '@/i18n/config' import { asI18n } from '@pikku/react' import { Badge, Box, Button, Group, Paper, Select, SimpleGrid, Stack, Table, Text, } from '@pikku/mantine/core' type Room = { roomId: string number: number beds: number roomType: string building: string floor: string wheelchair: number groundFloor: number quiet: number sharedBath: number dogsAllowed: number doubleBed_140: number allergyFriendly: number occupiedByOtherBooking: boolean assignedParticipants: { participantId: string; name: string }[] } type Participant = { participantId: string; name: string; roomId: string | null } const PALETTE = { empty: '#FBF8FA', emptyBorder: '#E5DDE3', filled: '#E9D4E2', filledBorder: '#88557F', partial: '#F0E4EC', full: '#88557F', fullText: '#FFFFFF', occupiedOther: '#EDEDED', occupiedOtherText: '#9B9499', hover: '#FFCE00', } const initials = (name: string) => name .split(/\s+/) .filter(Boolean) .slice(0, 2) .map((p) => p[0]?.toUpperCase() ?? '') .join('') // Stable, soft per-participant tint so each guest reads as a distinct chip // in the floor plan without leaving the brand palette. const TINTS = [ '#88557F', '#A05C8E', '#BC7AA6', '#D3A7C4', '#FFBC7D', '#F37E15', '#FFCE00', '#806600', '#4E3149', '#583651', ] const tintFor = (id: string) => { let h = 0 for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0 return TINTS[Math.abs(h) % TINTS.length] } type Group = { building: string; floor: string; rooms: Room[] } function groupRooms(rooms: Room[]): Group[] { const map = new Map() for (const r of rooms) { const key = `${r.building}::${r.floor}` if (!map.has(key)) map.set(key, []) map.get(key)!.push(r) } const order = ['gaestehaus::eg', 'gaestehaus::og', 'haupthaus::eg', 'haupthaus::og'] return Array.from(map.entries()) .map(([k, rs]) => { const [building, floor] = k.split('::') return { building, floor, rooms: rs.sort((a, b) => a.number - b.number) } }) .sort( (a, b) => order.indexOf(`${a.building}::${a.floor}`) - order.indexOf(`${b.building}::${b.floor}`), ) } function buildingLabel(building: string, floor: string) { const b = mKey(`common.pages.adminBooking.rooms.buildings.${building}` as any) const f = mKey(`common.pages.adminBooking.rooms.floors.${floor}` as any) return `${b} · ${f}` } export function RoomsTab({ rooms, participants, bookingId, assign, unassign, }: { rooms: Room[] participants: Participant[] bookingId: string assign: (args: { bookingId: string; participantId: string; roomId: string }) => void unassign: (args: { participantId: string }) => void }) { useLocale() const [hovered, setHovered] = useState(null) const groups = useMemo(() => groupRooms(rooms), [rooms]) const unassigned = participants.filter((p) => !p.roomId) return ( {/* Left pane: room list ----------------------------------------- */} {m.common_pages_admin_booking_rooms_cols_room()} {m.common_pages_admin_booking_rooms_cols_guests()} {m.common_pages_admin_booking_rooms_cols_assign()} {groups.map((g) => ( ))}
{/* Right pane: SVG floorplan -------------------------------------- */} {groups.map((g) => ( {asI18n(`${buildingLabel(g.building, g.floor)}`)} ))}
) } function RoomGroupRows({ group, hovered, onHover, unassigned, assign, unassign, bookingId, }: { group: Group hovered: string | null onHover: (id: string | null) => void unassigned: Participant[] assign: (args: { bookingId: string; participantId: string; roomId: string }) => void unassign: (args: { participantId: string }) => void bookingId: string }) { useLocale() return ( <> {asI18n(`${buildingLabel(group.building, group.floor)}`)} {group.rooms.map((r) => ( onHover(r.roomId)} onMouseLeave={() => onHover(null)} style={{ background: hovered === r.roomId ? 'rgba(255, 206, 0, 0.12)' : undefined, cursor: 'default', }} > {r.number} {asI18n(`${r.roomType}`)} {asI18n(`${r.beds} ${m.common_pages_admin_booking_rooms_beds()}`)} {r.occupiedByOtherBooking ? ( {m.common_pages_admin_booking_rooms_occupied_other()} ) : r.assignedParticipants.length === 0 ? ( {asI18n('—')} ) : ( {r.assignedParticipants.map((p) => ( {initials(p.name)} {asI18n(`${p.name}`)} ))} )} {!r.occupiedByOtherBooking && unassigned.length > 0 && (