desker/apps/web/src/routes/dashboard/building/[id]/+page.svelte

175 lines
6.6 KiB
Svelte

<script lang="ts">
import { page } from "$app/stores";
import { onMount } from "svelte";
import { token, user } from "$lib/stores/auth";
import { goto } from "$app/navigation";
import { apiFetch } from "$lib/api/client";
import FloorplanViewer from '$lib/components/FloorplanViewer.svelte';
import { Building2, Layers, Map as MapIcon, ChevronLeft, Settings, Calendar } from "lucide-svelte";
let building: any = null;
let currentFloor: any = null;
let loading = true;
let loadingFloor = false;
let selectedFloorId = "";
let selectedDateString = new Date().toISOString().split('T')[0];
let selectedDate = new Date();
$: {
selectedDate = new Date(selectedDateString);
// Add time component to the selected date if it's today, otherwise use start of day
if (selectedDateString === new Date().toISOString().split('T')[0]) {
selectedDate = new Date();
} else {
selectedDate.setHours(7, 0, 0, 0); // Default to 7 AM for other days
}
}
onMount(async () => {
const buildingId = $page.params.id;
if (!$token) {
goto(`/login?redirect=/dashboard/building/${buildingId}`);
return;
}
try {
building = await apiFetch(`/spaces/building/${buildingId}`);
if (building.floors && building.floors.length > 0) {
selectedFloorId = building.floors[0].id;
await loadFloor(selectedFloorId);
}
} catch (e: any) {
console.error(e);
if (e.message?.includes('401')) {
goto("/login");
}
} finally {
loading = false;
}
});
async function loadFloor(floorId: string) {
loadingFloor = true;
try {
currentFloor = await apiFetch(`/spaces/floor/${floorId}?date=${selectedDate.toISOString()}`);
} catch (e) {
console.error(e);
} finally {
loadingFloor = false;
}
}
$: if (selectedFloorId && building && selectedDate) {
// Re-fetch if floor or date changes
loadFloor(selectedFloorId);
}
</script>
<div class="p-8 max-w-7xl mx-auto">
<div class="mb-6">
<a href="/dashboard" class="inline-flex items-center text-indigo-600 hover:text-indigo-700 font-medium gap-1">
<ChevronLeft size={20} />
Back to Dashboard
</a>
</div>
{#if loading}
<div class="flex flex-col items-center justify-center h-64">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mb-4"></div>
<p class="text-gray-500">Loading building details...</p>
</div>
{:else if building}
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center mb-8 gap-4">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-indigo-600 rounded-xl flex items-center justify-center shadow-lg shadow-indigo-100">
<Building2 class="text-white" size={30} />
</div>
<div>
<h1 class="text-3xl font-bold text-gray-900">{building.name}</h1>
<div class="flex items-center gap-4 mt-1 text-gray-500">
<span class="flex items-center gap-1.5 text-sm">
<Layers size={16} />
{building.floors?.length || 0} Floors
</span>
</div>
</div>
</div>
<div class="flex items-center gap-3 w-full lg:w-auto">
<div class="relative flex-1 lg:flex-none min-w-[180px]">
<label for="date-select" class="absolute -top-2 left-3 px-1 bg-gray-50 text-[10px] font-bold text-indigo-600 uppercase tracking-wider">Select Date</label>
<input
id="date-select"
type="date"
bind:value={selectedDateString}
class="w-full pl-3 pr-4 py-3 bg-white border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent font-medium text-gray-700 shadow-sm"
/>
</div>
<div class="relative flex-1 lg:flex-none min-w-[200px]">
<label for="floor-select" class="absolute -top-2 left-3 px-1 bg-gray-50 text-[10px] font-bold text-indigo-600 uppercase tracking-wider">Select Floor</label>
<select
id="floor-select"
bind:value={selectedFloorId}
class="w-full pl-3 pr-10 py-3 bg-white border border-gray-300 rounded-xl appearance-none focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent font-medium text-gray-700 shadow-sm"
>
{#each building.floors as floor}
<option value={floor.id}>Floor {floor.number}</option>
{/each}
</select>
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none text-gray-400">
<Layers size={20} />
</div>
</div>
{#if $user?.role === 'ADMIN' && selectedFloorId}
<a
href="/admin/floorplan/{selectedFloorId}"
class="p-3 bg-white border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 transition-colors shadow-sm"
title="Edit Floorplan"
>
<Settings size={20} />
</a>
{/if}
</div>
</div>
<div class="bg-gray-100 rounded-3xl p-8 min-h-[700px] flex items-center justify-center border border-gray-200 shadow-inner relative overflow-hidden">
{#if loadingFloor}
<div class="flex flex-col items-center">
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-indigo-600 mb-4"></div>
<p class="text-gray-500 font-medium">Switching floor...</p>
</div>
{:else if currentFloor}
<div class="flex flex-col items-center">
<FloorplanViewer
floorId={currentFloor.id}
spaces={currentFloor.spaces}
planImageUrl={currentFloor.planImageUrl}
selectedDate={selectedDate}
/>
{#if currentFloor.planImageUrl}
<p class="mt-6 text-sm text-gray-500 flex items-center gap-2">
<MapIcon size={16} />
Interactive View - Click a space to see details or book.
</p>
{:else}
<p class="mt-6 text-sm text-gray-400 flex items-center gap-2 italic">
<MapIcon size={16} />
No floorplan image uploaded. Showing default grid.
</p>
{/if}
</div>
{:else}
<p class="text-gray-400">Select a floor to see the layout</p>
{/if}
</div>
{:else}
<div class="text-center py-20">
<h2 class="text-2xl font-bold text-gray-900">Building not found</h2>
<p class="text-gray-500 mt-2">The building you're looking for doesn't exist or you don't have access.</p>
<a href="/dashboard" class="mt-8 inline-block text-indigo-600 font-medium">Return to Dashboard</a>
</div>
{/if}
</div>