155 lines
4.5 KiB
Svelte
155 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import { pb } from '$lib/pb';
|
|
import { onMount } from 'svelte';
|
|
|
|
const STATUS_LABEL: Record<string, string> = {
|
|
ok: 'OK', abweichung: 'Abweichung', kritisch: 'Kritisch'
|
|
};
|
|
const STATUS_COLOR: Record<string, string> = {
|
|
ok: '#16a34a', abweichung: '#ea580c', kritisch: '#dc2626'
|
|
};
|
|
|
|
let logs = $state<any[]>([]);
|
|
let stats = $state({ total: 0, ok: 0, abweichung: 0, kritisch: 0 });
|
|
let loading = $state(true);
|
|
|
|
onMount(async () => {
|
|
try {
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
// User-Record explizit laden damit tenant-Relation sicher gesetzt ist
|
|
const user = await pb.collection('users').getOne(pb.authStore.record!.id);
|
|
const tenantId = user.tenant;
|
|
const dateStr = today.toISOString().slice(0, 19).replace('T', ' ');
|
|
|
|
const result = await pb.collection('check_logs').getList(1, 50, {
|
|
sort: '-created'
|
|
});
|
|
|
|
logs = result.items;
|
|
stats.total = result.totalItems;
|
|
stats.ok = logs.filter(l => l.status === 'ok').length;
|
|
stats.abweichung = logs.filter(l => l.status === 'abweichung').length;
|
|
stats.kritisch = logs.filter(l => l.status === 'kritisch').length;
|
|
} catch {
|
|
// Keine Einträge oder Zugriffsrechte noch nicht gesetzt
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
});
|
|
|
|
function formatTime(iso: string) {
|
|
return new Date(iso).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
</script>
|
|
|
|
<svelte:head><title>Dashboard — checkflo</title></svelte:head>
|
|
|
|
<div class="page">
|
|
<h1>Dashboard</h1>
|
|
<p class="date">{new Date().toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}</p>
|
|
|
|
<div class="stats">
|
|
<div class="stat">
|
|
<span class="stat-num">{stats.total}</span>
|
|
<span class="stat-label">Checks heute</span>
|
|
</div>
|
|
<div class="stat stat-ok">
|
|
<span class="stat-num">{stats.ok}</span>
|
|
<span class="stat-label">In Ordnung</span>
|
|
</div>
|
|
<div class="stat stat-warn">
|
|
<span class="stat-num">{stats.abweichung}</span>
|
|
<span class="stat-label">Abweichungen</span>
|
|
</div>
|
|
<div class="stat stat-crit">
|
|
<span class="stat-num">{stats.kritisch}</span>
|
|
<span class="stat-label">Kritisch</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>Heutige Einträge</h2>
|
|
|
|
{#if loading}
|
|
<p class="hint">Lädt…</p>
|
|
{:else if logs.length === 0}
|
|
<p class="hint">Noch keine Einträge heute.</p>
|
|
{:else}
|
|
<div class="log-list">
|
|
{#each logs as log}
|
|
<div class="log-item">
|
|
<div class="log-status" style="background: {STATUS_COLOR[log.status]}20; color: {STATUS_COLOR[log.status]}">
|
|
{STATUS_LABEL[log.status]}
|
|
</div>
|
|
<div class="log-info">
|
|
<span class="log-station">{log.expand?.station?.name ?? '—'}</span>
|
|
{#if log.temperature}
|
|
<span class="log-temp">{log.temperature} °C</span>
|
|
{/if}
|
|
</div>
|
|
<div class="log-meta">
|
|
<span>{log.checked_by}</span>
|
|
<span class="log-time">{formatTime(log.created)}</span>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.page { max-width: 800px; }
|
|
h1 { font-size: 1.6rem; font-weight: 800; color: #0B1023; }
|
|
h2 { font-size: 1.1rem; font-weight: 700; color: #0B1023; margin: 2rem 0 1rem; }
|
|
.date { color: #888; margin-top: 0.25rem; margin-bottom: 2rem; }
|
|
|
|
.stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
.stat {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 1.25rem;
|
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
|
border-left: 3px solid #e2e8f0;
|
|
}
|
|
.stat-ok { border-color: #16a34a; }
|
|
.stat-warn { border-color: #ea580c; }
|
|
.stat-crit { border-color: #dc2626; }
|
|
|
|
.stat-num { display: block; font-size: 2rem; font-weight: 800; color: #0B1023; line-height: 1; }
|
|
.stat-label { font-size: 0.8rem; color: #888; margin-top: 0.25rem; display: block; }
|
|
|
|
.hint { color: #aaa; font-size: 0.95rem; }
|
|
|
|
.log-list { display: flex; flex-direction: column; gap: 0.6rem; }
|
|
.log-item {
|
|
background: #fff;
|
|
border-radius: 10px;
|
|
padding: 0.9rem 1.1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
|
|
}
|
|
.log-status {
|
|
padding: 0.25rem 0.7rem;
|
|
border-radius: 20px;
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
.log-info { flex: 1; display: flex; flex-direction: column; gap: 0.1rem; }
|
|
.log-station { font-weight: 600; font-size: 0.95rem; }
|
|
.log-temp { font-size: 0.85rem; color: #555; }
|
|
.log-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; font-size: 0.85rem; color: #888; }
|
|
.log-time { font-size: 0.8rem; }
|
|
|
|
@media (max-width: 600px) {
|
|
.stats { grid-template-columns: repeat(2, 1fr); }
|
|
}
|
|
</style>
|