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

187 lines
7.2 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";
import { t, locale } from "svelte-i18n";
import { format } from "date-fns";
import { es, enUS } from "date-fns/locale";
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
}
}
$: dateLocale = $locale?.startsWith('es') ? es : enUS;
$: dateFormat = $locale === 'es-ES' ? 'dd/MM/yyyy' : 'EEEE, MMMM d';
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} />
{$t('building.back')}
</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">{$t('building.loading')}</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} />
{$t('dashboard.floors', { values: { count: building.floors?.length || 0 } })}
</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-[200px]">
<span class="absolute -top-2 left-3 px-1 bg-gray-50 text-[10px] font-bold text-indigo-600 uppercase tracking-wider">{$t('building.selectDate')}</span>
<input
id="date-select"
type="date"
bind:value={selectedDateString}
min={new Date().toISOString().split('T')[0]}
class="absolute inset-0 opacity-0 cursor-pointer z-10 w-full h-full"
on:click={(e) => e.currentTarget.showPicker()}
/>
<div class="w-full pl-3 pr-4 py-3 bg-white border border-gray-300 rounded-xl font-medium text-gray-700 shadow-sm flex items-center justify-between">
<span class="capitalize">{format(selectedDate, dateFormat, { locale: dateLocale })}</span>
<Calendar size={18} class="text-indigo-600" />
</div>
</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">{$t('building.selectFloor')}</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}>{$t('building.floor', { values: { number: 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={$t('building.editFloorplan')}
>
<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">{$t('building.switching')}</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} />
{$t('building.interactiveView')}
</p>
{:else}
<p class="mt-6 text-sm text-gray-400 flex items-center gap-2 italic">
<MapIcon size={16} />
{$t('building.noFloorplan')}
</p>
{/if}
</div>
{:else}
<p class="text-gray-400">{$t('building.selectToSee')}</p>
{/if}
</div>
{:else}
<div class="text-center py-20">
<h2 class="text-2xl font-bold text-gray-900">{$t('building.notFound')}</h2>
<p class="text-gray-500 mt-2">{$t('building.notFoundDesc')}</p>
<a href="/dashboard" class="mt-8 inline-block text-indigo-600 font-medium">{$t('building.return')}</a>
</div>
{/if}
</div>