148 lines
4.9 KiB
TypeScript
148 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { DESKS, getDeskLabel } from "../types";
|
|
import type { DeskId, DeskReservation } from "../types";
|
|
import { getReservationForDesk } from "../services/reservation-service";
|
|
import { cn } from "@/shared/lib/utils";
|
|
|
|
interface DeskRoomLayoutProps {
|
|
selectedDate: string;
|
|
reservations: DeskReservation[];
|
|
onDeskClick: (deskId: DeskId) => void;
|
|
}
|
|
|
|
export function DeskRoomLayout({
|
|
selectedDate,
|
|
reservations,
|
|
onDeskClick,
|
|
}: DeskRoomLayoutProps) {
|
|
return (
|
|
<div className="flex flex-col items-center gap-3">
|
|
{/* Room container — styled like a top-down floor plan */}
|
|
<div className="relative w-full max-w-[340px] rounded-xl border border-border/60 bg-muted/20 p-5">
|
|
{/* Window indicator — LEFT wall (landmark for orientation) */}
|
|
<div className="absolute top-4 bottom-4 left-0 w-1.5 rounded-r-sm bg-sky-300/30 dark:bg-sky-500/20" />
|
|
<div className="absolute top-6 bottom-6 left-0 flex flex-col justify-between">
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="ml-0.5 h-3 w-0.5 rounded-full bg-sky-400/25 dark:bg-sky-400/15"
|
|
/>
|
|
))}
|
|
</div>
|
|
{/* Window label */}
|
|
<div className="absolute left-1.5 top-1/2 -translate-y-1/2 -rotate-90 text-[9px] font-medium text-muted-foreground/40 tracking-widest uppercase select-none">
|
|
Fereastră
|
|
</div>
|
|
|
|
{/* Door indicator — RIGHT wall */}
|
|
<div className="absolute top-[60%] right-0 h-8 w-1.5 rounded-l-sm bg-amber-400/25 dark:bg-amber-500/15" />
|
|
<div className="absolute top-[60%] right-1.5 translate-y-1 text-[8px] text-muted-foreground/30 select-none">
|
|
Ușă
|
|
</div>
|
|
|
|
{/* Central table */}
|
|
<div className="mx-auto mt-4 mb-4 flex flex-col items-center">
|
|
{/* Top row desks */}
|
|
<div className="flex gap-3 mb-2">
|
|
{DESKS.filter((d) => d.position.startsWith("top")).map((desk) => {
|
|
const reservation = getReservationForDesk(
|
|
desk.id,
|
|
selectedDate,
|
|
reservations,
|
|
);
|
|
return (
|
|
<DeskSlot
|
|
key={desk.id}
|
|
deskId={desk.id}
|
|
label={getDeskLabel(desk.id)}
|
|
reservation={reservation}
|
|
side="top"
|
|
onClick={() => onDeskClick(desk.id)}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* The table surface */}
|
|
<div className="h-12 w-full max-w-[280px] rounded-md border border-border/50 bg-muted/40" />
|
|
|
|
{/* Bottom row desks */}
|
|
<div className="flex gap-3 mt-2">
|
|
{DESKS.filter((d) => d.position.startsWith("bottom")).map(
|
|
(desk) => {
|
|
const reservation = getReservationForDesk(
|
|
desk.id,
|
|
selectedDate,
|
|
reservations,
|
|
);
|
|
return (
|
|
<DeskSlot
|
|
key={desk.id}
|
|
deskId={desk.id}
|
|
label={getDeskLabel(desk.id)}
|
|
reservation={reservation}
|
|
side="bottom"
|
|
onClick={() => onDeskClick(desk.id)}
|
|
/>
|
|
);
|
|
},
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface DeskSlotProps {
|
|
deskId: DeskId;
|
|
label: string;
|
|
reservation: DeskReservation | undefined;
|
|
side: "top" | "bottom";
|
|
onClick: () => void;
|
|
}
|
|
|
|
function DeskSlot({ label, reservation, side, onClick }: DeskSlotProps) {
|
|
const isBooked = !!reservation;
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onClick}
|
|
className={cn(
|
|
"group relative flex w-[125px] cursor-pointer flex-col items-center rounded-lg border p-3 transition-all",
|
|
side === "top" ? "rounded-b-sm" : "rounded-t-sm",
|
|
isBooked
|
|
? "border-primary/30 bg-primary/8 hover:border-primary/50 hover:bg-primary/12"
|
|
: "border-dashed border-border/60 bg-background/60 hover:border-primary/40 hover:bg-muted/50",
|
|
)}
|
|
>
|
|
{/* Chair indicator */}
|
|
<div
|
|
className={cn(
|
|
"absolute left-1/2 -translate-x-1/2 h-1.5 w-8 rounded-full transition-colors",
|
|
side === "top" ? "-top-2.5" : "-bottom-2.5",
|
|
isBooked
|
|
? "bg-primary/40"
|
|
: "bg-muted-foreground/15 group-hover:bg-muted-foreground/25",
|
|
)}
|
|
/>
|
|
|
|
{/* Desk label */}
|
|
<span className="text-[11px] font-medium text-muted-foreground">
|
|
{label}
|
|
</span>
|
|
|
|
{/* Status */}
|
|
{isBooked ? (
|
|
<span className="mt-1 text-xs font-medium text-primary truncate max-w-full">
|
|
{reservation.userName}
|
|
</span>
|
|
) : (
|
|
<span className="mt-1 text-[11px] text-muted-foreground/50">Liber</span>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|