Admin-Bereich, PWA-Manifest und Deploy-Setup
- Admin: Login, Dashboard, Protokoll, Stationen mit QR-Links - PWA: vite-plugin-pwa mit Workbox Offline-Caching - SvelteKit adapter-node + Dockerfile für DS-Deployment - docker-compose.yml mit app + pocketbase Services - Makefile: make deploy Befehl
This commit is contained in:
parent
f2615c9e07
commit
18570a42f0
15 changed files with 6042 additions and 12 deletions
129
app/src/routes/admin/logs/+page.svelte
Normal file
129
app/src/routes/admin/logs/+page.svelte
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<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 loading = $state(true);
|
||||
let page = $state(1);
|
||||
let totalPages = $state(1);
|
||||
const PER_PAGE = 30;
|
||||
|
||||
onMount(() => loadPage(1));
|
||||
|
||||
async function loadPage(p: number) {
|
||||
loading = true;
|
||||
const tenantId = (pb.authStore.record as any)?.tenant;
|
||||
const result = await pb.collection('check_logs').getList(p, PER_PAGE, {
|
||||
filter: `tenant = "${tenantId}"`,
|
||||
expand: 'station',
|
||||
sort: '-created'
|
||||
});
|
||||
logs = result.items;
|
||||
page = p;
|
||||
totalPages = Math.ceil(result.totalItems / PER_PAGE);
|
||||
loading = false;
|
||||
}
|
||||
|
||||
function formatDate(iso: string) {
|
||||
return new Date(iso).toLocaleString('de-DE', {
|
||||
day: '2-digit', month: '2-digit', year: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Protokoll — checkflo</title></svelte:head>
|
||||
|
||||
<div class="page">
|
||||
<h1>Protokoll</h1>
|
||||
|
||||
{#if loading}
|
||||
<p class="hint">Lädt…</p>
|
||||
{:else if logs.length === 0}
|
||||
<p class="hint">Noch keine Einträge vorhanden.</p>
|
||||
{:else}
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Station</th>
|
||||
<th>Status</th>
|
||||
<th>Temp.</th>
|
||||
<th>Person</th>
|
||||
<th>Notiz</th>
|
||||
<th>Datum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each logs as log}
|
||||
<tr>
|
||||
<td class="td-station">{log.expand?.station?.name ?? '—'}</td>
|
||||
<td>
|
||||
<span class="badge" style="background:{STATUS_COLOR[log.status]}20;color:{STATUS_COLOR[log.status]}">
|
||||
{STATUS_LABEL[log.status]}
|
||||
</span>
|
||||
</td>
|
||||
<td>{log.temperature ? log.temperature + ' °C' : '—'}</td>
|
||||
<td>{log.checked_by}</td>
|
||||
<td class="td-notes">{log.notes || '—'}</td>
|
||||
<td class="td-date">{formatDate(log.created)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if totalPages > 1}
|
||||
<div class="pagination">
|
||||
<button disabled={page === 1} onclick={() => loadPage(page - 1)}>← zurück</button>
|
||||
<span>Seite {page} / {totalPages}</span>
|
||||
<button disabled={page === totalPages} onclick={() => loadPage(page + 1)}>weiter →</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.page { max-width: 1000px; }
|
||||
h1 { font-size: 1.6rem; font-weight: 800; color: #0B1023; margin-bottom: 1.5rem; }
|
||||
.hint { color: #aaa; }
|
||||
|
||||
.table-wrap { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||
th { background: #F5F7FA; padding: 0.75rem 1rem; text-align: left; font-size: 0.8rem; font-weight: 700; color: #888; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
td { padding: 0.75rem 1rem; border-top: 1px solid #f0f0f0; font-size: 0.9rem; vertical-align: middle; }
|
||||
tr:hover td { background: #fafafa; }
|
||||
|
||||
.badge { padding: 0.2rem 0.6rem; border-radius: 20px; font-size: 0.8rem; font-weight: 700; white-space: nowrap; }
|
||||
.td-station { font-weight: 600; }
|
||||
.td-notes { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #666; }
|
||||
.td-date { white-space: nowrap; color: #888; font-size: 0.85rem; }
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
.pagination button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1.5px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.pagination button:hover:not(:disabled) { border-color: #F97316; color: #F97316; }
|
||||
.pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue