desker/apps/web/src/lib/components/FloorplanViewer.svelte

84 lines
2.8 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { io, Socket } from 'socket.io-client';
import { Laptop, Bath, Coffee, Users } from 'lucide-svelte';
export let floorId: string;
export let spaces: any[] = [];
export let planImageUrl: string | null = null;
let container: HTMLDivElement;
let socket: Socket;
let spaceStatus: Record<string, boolean> = {};
$: {
// Initialize space status from the current state
spaces.forEach(s => {
spaceStatus[s.id] = s.reservations && s.reservations.length > 0;
});
}
onMount(() => {
const socketUrl = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000';
socket = io(socketUrl);
socket.on('connect', () => {
socket.emit('join_floor', floorId);
});
socket.on('space_status_changed', (data: { spaceId: string, isOccupied: boolean }) => {
spaceStatus[data.spaceId] = data.isOccupied;
spaceStatus = { ...spaceStatus }; // Trigger reactivity
});
});
onDestroy(() => {
if (socket) {
socket.emit('leave_floor', floorId);
socket.disconnect();
}
});
function getSpaceTypeIcon(type: string, name: string) {
if (type === 'DESK') return Laptop;
if (type === 'MEETING_ROOM') return Users;
const lowerName = name.toLowerCase();
if (lowerName.includes('toilet') || lowerName.includes('bath')) return Bath;
if (lowerName.includes('coffee')) return Coffee;
return null;
}
</script>
<div
bind:this={container}
class="relative bg-white shadow-xl rounded-lg overflow-hidden border mx-auto floorplan-container"
style="width: 800px; height: 600px; background-image: url({planImageUrl}); background-size: contain; background-repeat: no-repeat; background-position: center;"
>
{#each spaces as space (space.id)}
<div
class="absolute p-2 border-2 rounded-lg shadow-md transition-colors {spaceStatus[space.id] ? 'bg-red-50 border-red-500' : 'bg-green-50 border-green-500'}"
style="left: 0; top: 0; transform: translate({(space.x / 100) * (container?.clientWidth || 800)}px, {(space.y / 100) * (container?.clientHeight || 600)}px)"
title="{space.name} ({spaceStatus[space.id] ? 'Occupied' : 'Available'})"
>
<div class="flex items-center gap-1">
<div class={spaceStatus[space.id] ? 'text-red-600' : 'text-green-600'}>
<svelte:component this={getSpaceTypeIcon(space.type, space.name)} size={16} />
</div>
<span class="text-[9px] font-bold text-gray-800 whitespace-nowrap">
{space.name}
</span>
</div>
</div>
{/each}
</div>
<style>
.floorplan-container {
background-color: #f8fafc;
background-image:
linear-gradient(to right, #e2e8f0 1px, transparent 1px),
linear-gradient(to bottom, #e2e8f0 1px, transparent 1px);
background-size: 40px 40px;
}
</style>