feat: Quickstart, Benutzerverwaltung und Orion-Ergebnisbox

This commit is contained in:
Schubert Ferenc 2026-07-12 12:19:54 +02:00
parent 4f669a3426
commit e9e46f2cb4
404 changed files with 1032 additions and 600 deletions

View file

@ -6,6 +6,7 @@ import Link from "next/link";
import { useAuth } from "@/components/auth";
import { apiGet } from "@/lib/api";
import { BrandLogo } from "@/components/brand/brand-logo";
import { ValidationResultBadge } from "@/lib/validation-result";
type DashboardData = {
customers: number;
@ -18,6 +19,10 @@ type DashboardData = {
validation_approved: number;
validation_completed: number;
validation_overdue: number;
validation_result_open: number;
validation_result_passed: number;
validation_result_conditional: number;
validation_result_failed: number;
equipment_green: number;
equipment_yellow: number;
equipment_red: number;
@ -105,6 +110,16 @@ export default function DashboardPage() {
<Link href="/validations?status=ABGESCHLOSSEN" className="rounded-lg border border-border bg-surface p-6 shadow-soft transition hover:border-primary/40 hover:bg-background">
<h2 className="text-xl font-semibold">Letzte Berichte</h2>
<p className="mt-4 text-3xl font-semibold text-text">{query.data?.validation_completed ?? 0}</p>
<div className="mt-4 flex flex-wrap gap-2">
<ValidationResultBadge value="BESTANDEN" className="text-[11px]" />
<span className="text-sm font-semibold text-text">{query.data?.validation_result_passed ?? 0}</span>
<ValidationResultBadge value="BESTANDEN_MIT_AUFLAGEN" className="text-[11px]" />
<span className="text-sm font-semibold text-text">{query.data?.validation_result_conditional ?? 0}</span>
<ValidationResultBadge value="NICHT_BESTANDEN" className="text-[11px]" />
<span className="text-sm font-semibold text-text">{query.data?.validation_result_failed ?? 0}</span>
<ValidationResultBadge value="OFFEN" className="text-[11px]" />
<span className="text-sm font-semibold text-text">{query.data?.validation_result_open ?? 0}</span>
</div>
</Link>
</section>
</div>

View file

@ -7,6 +7,7 @@ import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { useAuth } from "@/components/auth";
import { API_BASE, apiDelete, apiGet, apiSend, Customer, Device, Paginated, ValidationItem } from "@/lib/api";
import { ValidationResultBadge, validationResultValues, getValidationResultPresentation } from "@/lib/validation-result";
const statusLabels: Record<string, string> = {
ENTWURF: "Entwurf",
@ -94,7 +95,7 @@ export default function ValidationsPage() {
{ accessorKey: "performed_on", header: "Pruefdatum" },
{ accessorKey: "next_validation_on", header: "Naechste Validierung" },
{ accessorKey: "examiner_name", header: "Pruefer" },
{ accessorKey: "result", header: "Ergebnis" },
{ accessorKey: "result", header: "Ergebnis", cell: ({ row }) => <ValidationResultBadge value={row.original.result} /> },
{ accessorKey: "updated_at", header: "Zuletzt geaendert" },
{ accessorKey: "status", header: "Status", cell: ({ row }) => <span className={`rounded-full px-3 py-1 text-xs font-semibold ${statusClass[row.original.status] ?? statusClass.ENTWURF}`}>{statusLabels[row.original.status] ?? row.original.status}</span> },
{ id: "actions", header: "Aktionen", enableSorting: false, cell: ({ row }) => {
@ -128,7 +129,7 @@ export default function ValidationsPage() {
<select value={validationType} onChange={(event) => setValidationType(event.target.value)} className="h-12 rounded-lg border border-border px-3"><option value="">Alle Arten</option><option>Erstvalidierung</option><option>Revalidierung</option><option>Leistungsbeurteilung</option><option>Sonderpruefung</option></select>
<select value={customerId} onChange={(event) => setCustomerId(event.target.value)} className="h-12 rounded-lg border border-border px-3"><option value="">Alle Kunden</option>{(customers.data?.items ?? []).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}</select>
<select value={deviceId} onChange={(event) => setDeviceId(event.target.value)} className="h-12 rounded-lg border border-border px-3"><option value="">Alle Geraete</option>{(devices.data?.items ?? []).map((item) => <option key={item.id} value={item.id}>{item.manufacturer} {item.model}</option>)}</select>
<select value={result} onChange={(event) => setResult(event.target.value)} className="h-12 rounded-lg border border-border px-3"><option value="">Alle Ergebnisse</option><option value="offen">Offen</option><option value="bestanden">Bestanden</option><option value="nicht_bestanden">Nicht bestanden</option><option value="mit_auflagen">Mit Auflagen</option></select>
<select value={result} onChange={(event) => setResult(event.target.value)} className="h-12 rounded-lg border border-border px-3"><option value="">Alle Ergebnisse</option>{validationResultValues.map((value) => <option key={value} value={value}>{getValidationResultPresentation(value).label}</option>)}</select>
<input type="date" value={dateFrom} onChange={(event) => setDateFrom(event.target.value)} className="h-12 rounded-lg border border-border px-3" />
<input type="date" value={dateTo} onChange={(event) => setDateTo(event.target.value)} className="h-12 rounded-lg border border-border px-3" />
<label className="flex h-12 items-center gap-3 rounded-lg border border-border px-3"><input type="checkbox" checked={overdueOnly} onChange={(event) => setOverdueOnly(event.target.checked)} /> ueberfaellige Revalidierungen</label>