Feature: Live-Dashboard in Landing Page (PocketBase, 30s Auto-Refresh) + stündlicher Demo-Reset
This commit is contained in:
parent
a7176a589a
commit
b06a3fac80
2 changed files with 163 additions and 27 deletions
|
|
@ -1,6 +1,52 @@
|
|||
<script lang="ts">
|
||||
import logo from '$lib/assets/checkflo-logo.png';
|
||||
import hafnerLogo from '$lib/assets/hafner-logo.png';
|
||||
import { pb } from '$lib/pb';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
const DEMO_TENANT = 'mengbzc3ajxpccz';
|
||||
|
||||
const STATUS_LABEL: Record<string, string> = { ok: 'OK', abweichung: 'Abweichung', kritisch: 'Kritisch' };
|
||||
const STATUS_CLASS: Record<string, string> = { ok: 'ok', abweichung: 'warn', kritisch: 'crit' };
|
||||
|
||||
let liveLogs = $state<any[]>([]);
|
||||
let liveStats = $state({ total: 0, ok: 0, warn: 0, crit: 0 });
|
||||
let lastUpdate = $state('');
|
||||
|
||||
async function fetchLiveData() {
|
||||
try {
|
||||
const today = new Date();
|
||||
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'
|
||||
});
|
||||
|
||||
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;
|
||||
lastUpdate = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
} catch { /* public endpoint, sollte immer klappen */ }
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!browser) return;
|
||||
fetchLiveData();
|
||||
const iv = setInterval(fetchLiveData, 30_000);
|
||||
return () => clearInterval(iv);
|
||||
});
|
||||
|
||||
function formatTime(iso: string) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
return isNaN(d.getTime()) ? '' : d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
|
|
@ -71,44 +117,39 @@
|
|||
</div>
|
||||
<div class="dash-main">
|
||||
<div class="dash-title">Dashboard</div>
|
||||
<div class="dash-date">Sonntag, 17. Mai</div>
|
||||
<div class="dash-date">
|
||||
{new Date().toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||
{#if lastUpdate}<span class="dash-refresh">· {lastUpdate}</span>{/if}
|
||||
</div>
|
||||
<div class="dash-stats">
|
||||
<div class="dash-stat">
|
||||
<div class="dash-num">184</div>
|
||||
<div class="dash-label">Checks gesamt</div>
|
||||
<div class="dash-num">{liveStats.total}</div>
|
||||
<div class="dash-label">Heute</div>
|
||||
</div>
|
||||
<div class="dash-stat ok">
|
||||
<div class="dash-num">171</div>
|
||||
<div class="dash-label">In Ordnung</div>
|
||||
<div class="dash-num">{liveStats.ok}</div>
|
||||
<div class="dash-label">OK</div>
|
||||
</div>
|
||||
<div class="dash-stat warn">
|
||||
<div class="dash-num">11</div>
|
||||
<div class="dash-label">Abweichungen</div>
|
||||
<div class="dash-num">{liveStats.warn}</div>
|
||||
<div class="dash-label">Abw.</div>
|
||||
</div>
|
||||
<div class="dash-stat crit">
|
||||
<div class="dash-num">2</div>
|
||||
<div class="dash-num">{liveStats.crit}</div>
|
||||
<div class="dash-label">Kritisch</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dash-entries-title">Heutige Einträge</div>
|
||||
<div class="dash-entry">
|
||||
<span class="entry-badge ok">OK</span>
|
||||
<span class="entry-station">Fleischkühltheke</span>
|
||||
<span class="entry-temp">2 °C</span>
|
||||
<span class="entry-who">Thomas K. · 06:55</span>
|
||||
</div>
|
||||
<div class="dash-entry">
|
||||
<span class="entry-badge ok">OK</span>
|
||||
<span class="entry-station">Wurstkühlschrank</span>
|
||||
<span class="entry-temp">3 °C</span>
|
||||
<span class="entry-who">Maria S. · 07:12</span>
|
||||
</div>
|
||||
<div class="dash-entry">
|
||||
<span class="entry-badge warn">Abweichung</span>
|
||||
<span class="entry-station">Tiefkühltruhe</span>
|
||||
<span class="entry-temp">−16 °C</span>
|
||||
<span class="entry-who">Thomas K. · 08:03</span>
|
||||
</div>
|
||||
<div class="dash-entries-title">Letzte Einträge</div>
|
||||
{#each liveLogs as log}
|
||||
<div class="dash-entry">
|
||||
<span class="entry-badge {STATUS_CLASS[log.status] ?? 'ok'}">{STATUS_LABEL[log.status] ?? log.status}</span>
|
||||
<span class="entry-station">{log.expand?.station?.name ?? '—'}</span>
|
||||
<span class="entry-temp">{log.temperature ? log.temperature + ' °C' : ''}</span>
|
||||
<span class="entry-who">{log.checked_by} · {formatTime(log.created)}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="dash-empty">Noch keine Einträge heute</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -394,7 +435,9 @@
|
|||
.dash-num { font-size: 1rem; font-weight: 800; color: #0B1023; line-height: 1; }
|
||||
.dash-label { font-size: 0.5rem; color: #888; margin-top: 0.15rem; }
|
||||
|
||||
.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; }
|
||||
.dash-entry {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
|
|
|
|||
93
scripts/demo-refresh.sh
Executable file
93
scripts/demo-refresh.sh
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/bin/bash
|
||||
# Löscht heutige Demo-Einträge und erstellt frische für die letzte Stunde.
|
||||
# Cron: jede Stunde via DSM Task Scheduler oder crontab
|
||||
|
||||
set -euo pipefail
|
||||
PB_URL="${PB_URL:-https://api.checkflo.de}"
|
||||
|
||||
if [ -z "${PB_EMAIL:-}" ] || [ -z "${PB_PASSWORD:-}" ]; then
|
||||
echo "Aufruf: PB_EMAIL=... PB_PASSWORD=... ./demo-refresh.sh"; exit 1
|
||||
fi
|
||||
|
||||
TOKEN=$(curl -sf -X POST "$PB_URL/api/collections/_superusers/auth-with-password" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"identity\":\"$PB_EMAIL\",\"password\":\"$PB_PASSWORD\"}" | jq -r '.token')
|
||||
|
||||
TENANT_ID="mengbzc3ajxpccz"
|
||||
NOW=$(date +"%Y-%m-%d %H:00:00")
|
||||
TODAY=$(date +"%Y-%m-%d")
|
||||
|
||||
# Alle heutigen Demo-Einträge löschen
|
||||
EXISTING=$(curl -sf \
|
||||
"$PB_URL/api/collections/check_logs/records?filter=tenant%3D'$TENANT_ID'%26%26created%3E%3D'$TODAY%2000%3A00%3A00'&perPage=200" \
|
||||
-H "Authorization: $TOKEN" | jq -r '.items[].id')
|
||||
|
||||
COUNT_DEL=0
|
||||
for id in $EXISTING; do
|
||||
curl -sf -X DELETE "$PB_URL/api/collections/check_logs/records/$id" \
|
||||
-H "Authorization: $TOKEN" > /dev/null
|
||||
COUNT_DEL=$((COUNT_DEL + 1))
|
||||
done
|
||||
echo "→ $COUNT_DEL alte Einträge gelöscht"
|
||||
|
||||
# Stationen laden
|
||||
STATIONS=$(curl -sf "$PB_URL/api/collections/stations/records?filter=tenant%3D'$TENANT_ID'%26%26active%3Dtrue&perPage=50" \
|
||||
-H "Authorization: $TOKEN")
|
||||
STATION_COUNT=$(echo "$STATIONS" | jq '.items | length')
|
||||
|
||||
NAMES=("Maria S." "Klaus B." "Sandra M." "Tobias R." "Anna K." "Michael F.")
|
||||
NOTES_ABW=("Tür stand leicht offen" "Dichtung prüfen" "Temperatur leicht erhöht")
|
||||
NOTES_KRIT=("Tür war 2h offen" "Kompressor ausgefallen" "Sofortmaßnahme eingeleitet")
|
||||
|
||||
rand_between() { echo $(( $1 + RANDOM % ($2 - $1 + 1) )); }
|
||||
|
||||
create_log() {
|
||||
curl -sf -X POST "$PB_URL/api/collections/check_logs/records" \
|
||||
-H "Authorization: $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"station\": \"$1\",
|
||||
\"tenant\": \"$TENANT_ID\",
|
||||
\"temperature\": $2,
|
||||
\"status\": \"$3\",
|
||||
\"notes\": \"$4\",
|
||||
\"checked_by\": \"$5\",
|
||||
\"created\": \"$6\",
|
||||
\"updated\": \"$6\"
|
||||
}" > /dev/null
|
||||
}
|
||||
|
||||
# Für jede vergangene Stunde seit 7:00 Einträge erstellen
|
||||
CURRENT_HOUR=$(date +"%H")
|
||||
COUNT_NEW=0
|
||||
|
||||
for HOUR in $(seq 7 $CURRENT_HOUR); do
|
||||
for i in $(seq 0 $((STATION_COUNT - 1))); do
|
||||
SID=$(echo "$STATIONS" | jq -r ".items[$i].id")
|
||||
TYPE=$(echo "$STATIONS" | jq -r ".items[$i].type")
|
||||
TMIN=$(echo "$STATIONS" | jq -r ".items[$i].target_temp_min // 0")
|
||||
TMAX=$(echo "$STATIONS" | jq -r ".items[$i].target_temp_max // 0")
|
||||
PERSON=${NAMES[$((RANDOM % ${#NAMES[@]}))]}
|
||||
MIN=$(rand_between 0 59)
|
||||
TIME="$TODAY $(printf "%02d" $HOUR):$(printf "%02d" $MIN):00"
|
||||
|
||||
if [ "$TYPE" = "hygiene" ] && [ "$HOUR" != "9" ]; then continue; fi
|
||||
|
||||
RAND=$((RANDOM % 100))
|
||||
if [ $RAND -lt 88 ]; then
|
||||
TEMP=$(rand_between $TMIN $TMAX)
|
||||
create_log "$SID" "$TEMP" "ok" "" "$PERSON" "$TIME"
|
||||
elif [ $RAND -lt 96 ]; then
|
||||
TEMP=$((TMAX + 1 + RANDOM % 3))
|
||||
NOTE=${NOTES_ABW[$((RANDOM % ${#NOTES_ABW[@]}))]}
|
||||
create_log "$SID" "$TEMP" "abweichung" "$NOTE" "$PERSON" "$TIME"
|
||||
else
|
||||
TEMP=$((TMAX + 4 + RANDOM % 4))
|
||||
NOTE=${NOTES_KRIT[$((RANDOM % ${#NOTES_KRIT[@]}))]}
|
||||
create_log "$SID" "$TEMP" "kritisch" "$NOTE" "$PERSON" "$TIME"
|
||||
fi
|
||||
COUNT_NEW=$((COUNT_NEW + 1))
|
||||
done
|
||||
done
|
||||
|
||||
echo "✓ $COUNT_NEW neue Demo-Einträge erstellt (bis $(printf "%02d" $CURRENT_HOUR):00 Uhr)"
|
||||
Loading…
Add table
Add a link
Reference in a new issue