diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 0e80911..57c679d 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -10,9 +10,11 @@ const STATUS_LABEL: Record = { ok: 'OK', abweichung: 'Abweichung', kritisch: 'Kritisch' }; const STATUS_CLASS: Record = { ok: 'ok', abweichung: 'warn', kritisch: 'crit' }; - let liveLogs = $state([]); - let liveStats = $state({ total: 0, ok: 0, warn: 0, crit: 0 }); - let lastUpdate = $state(''); + let activeView = $state<'dashboard' | 'protokoll' | 'stationen'>('dashboard'); + let liveLogs = $state([]); + let liveStats = $state({ total: 0, ok: 0, warn: 0, crit: 0 }); + let lastUpdate = $state(''); + let liveStations = $state([]); async function fetchLiveData() { try { @@ -20,19 +22,26 @@ today.setHours(0, 0, 0, 0); const dateStr = today.toISOString().slice(0, 19).replace('T', ' '); - const result = await pb.collection('check_logs').getList(1, 3, { - filter: `tenant = '${DEMO_TENANT}' && created >= '${dateStr}'`, - expand: 'station', - sort: '-created' - }); + const [logsResult, stationsResult] = await Promise.all([ + pb.collection('check_logs').getList(1, 6, { + filter: `tenant = '${DEMO_TENANT}' && created >= '${dateStr}'`, + expand: 'station', + sort: '-created' + }), + pb.collection('stations').getFullList({ + filter: `tenant = '${DEMO_TENANT}' && active = true`, + sort: 'name' + }) + ]); - liveLogs = result.items; - liveStats.total = result.totalItems; - liveStats.ok = result.items.filter((l: any) => l.status === 'ok').length; - liveStats.warn = result.items.filter((l: any) => l.status === 'abweichung').length; - liveStats.crit = result.items.filter((l: any) => l.status === 'kritisch').length; + liveLogs = logsResult.items; + liveStats.total = logsResult.totalItems; + liveStats.ok = logsResult.items.filter((l: any) => l.status === 'ok').length; + liveStats.warn = logsResult.items.filter((l: any) => l.status === 'abweichung').length; + liveStats.crit = logsResult.items.filter((l: any) => l.status === 'kritisch').length; lastUpdate = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); - } catch { /* public endpoint, sollte immer klappen */ } + liveStations = stationsResult; + } catch { } } onMount(() => { @@ -124,10 +133,13 @@
-
Dashboard
-
Protokoll
-
Stationen
+ + +
+ + + {#if activeView === 'dashboard'}
Dashboard
@@ -135,25 +147,13 @@ {#if lastUpdate}· {lastUpdate}{/if}
-
-
{liveStats.total}
-
Heute
-
-
-
{liveStats.ok}
-
OK
-
-
-
{liveStats.warn}
-
Abw.
-
-
-
{liveStats.crit}
-
Kritisch
-
+
{liveStats.total}
Heute
+
{liveStats.ok}
OK
+
{liveStats.warn}
Abw.
+
{liveStats.crit}
Kritisch
Letzte Einträge
- {#each liveLogs as log} + {#each liveLogs.slice(0,4) as log}
{STATUS_LABEL[log.status] ?? log.status} {log.expand?.station?.name ?? '—'} @@ -164,6 +164,51 @@
Noch keine Einträge heute
{/each}
+ {/if} + + + {#if activeView === 'protokoll'} +
+
Protokoll
+
{new Date().toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })}
+ + + + {#each liveLogs as log} + + + + + + + {:else} + + {/each} + +
StationStatusTemp.Zeit
{log.expand?.station?.name ?? '—'}{STATUS_LABEL[log.status]}{log.temperature ? log.temperature + '°' : '—'}{formatTime(log.created)}
Keine Einträge
+
+ {/if} + + + {#if activeView === 'stationen'} +
+
Stationen
+
{liveStations.length} aktive Stationen
+
+ {#each liveStations as s} +
+
+
{s.name}
+ {#if s.target_temp_min || s.target_temp_max} +
{s.target_temp_min}° – {s.target_temp_max}°C
+ {/if} +
+ {:else} +
Keine Stationen
+ {/each} +
+
+ {/if}
@@ -414,11 +459,15 @@ border-radius: 5px; font-size: 0.65rem; color: #6b7a9a; + background: none; + border: none; + cursor: pointer; + text-align: left; + width: 100%; + transition: all 0.15s; } - .dash-nav-item.active { - background: rgba(249,115,22,0.15); - color: #F97316; - } + .dash-nav-item:hover { color: #fff; background: rgba(255,255,255,0.06); } + .dash-nav-item.active { background: rgba(249,115,22,0.15); color: #F97316; } .dash-main { flex: 1; @@ -450,6 +499,20 @@ .dash-refresh { font-size: 0.55rem; color: #aaa; } .dash-entries-title { font-size: 0.7rem; font-weight: 700; color: #0B1023; margin-bottom: 0.4rem; } .dash-empty { font-size: 0.65rem; color: #aaa; padding: 0.5rem 0; } + + /* Protokoll-Tabelle */ + .proto-table { width: 100%; border-collapse: collapse; font-size: 0.6rem; margin-top: 0.5rem; } + .proto-table th { background: #F5F7FA; padding: 0.3rem 0.4rem; text-align: left; color: #888; font-size: 0.55rem; text-transform: uppercase; } + .proto-table td { padding: 0.35rem 0.4rem; border-bottom: 1px solid #f0f0f0; } + .proto-station { font-weight: 600; color: #0B1023; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .proto-time { color: #aaa; } + + /* Stationen-Grid */ + .stations-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; margin-top: 0.5rem; } + .station-card { background: #F5F7FA; border-radius: 6px; padding: 0.5rem; } + .station-card-icon { font-size: 1rem; margin-bottom: 0.2rem; } + .station-card-name { font-size: 0.6rem; font-weight: 700; color: #0B1023; } + .station-card-temp { font-size: 0.55rem; color: #888; margin-top: 0.1rem; } .dash-entry { background: #fff; border-radius: 6px;