Commit 6: Adminbereich (TanStack Router, 8 Menüpunkte, Scan-Logs)

This commit is contained in:
2026-07-19 02:53:04 +02:00
parent b0ff551ca0
commit 2f31996bbb
31 changed files with 1693 additions and 112 deletions

View File

@@ -0,0 +1,34 @@
import { useEffect, useState } from "react";
import type { HealthStatus } from "@launchpad/shared";
export function useBackendHealth() {
const [health, setHealth] = useState<HealthStatus | null>(null);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
async function check() {
try {
const res = await fetch("/api/health");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data: HealthStatus = await res.json();
if (!cancelled) {
setHealth(data);
setError(false);
}
} catch {
if (!cancelled) setError(true);
}
}
check();
const interval = setInterval(check, 10_000);
return () => {
cancelled = true;
clearInterval(interval);
};
}, []);
return { health, error };
}

View File

@@ -0,0 +1,17 @@
import { useQuery } from "@tanstack/react-query";
import type { Category } from "@launchpad/shared";
async function fetchCategories(): Promise<Category[]> {
const res = await fetch("/api/categories");
if (!res.ok) {
throw new Error(`Kategorien konnten nicht geladen werden (HTTP ${res.status})`);
}
return res.json();
}
export function useCategories() {
return useQuery({
queryKey: ["categories"],
queryFn: fetchCategories,
});
}

View File

@@ -0,0 +1,21 @@
import { useQuery } from "@tanstack/react-query";
import type { Device, Service } from "@launchpad/shared";
export interface DeviceWithServices extends Device {
services: Service[];
}
async function fetchDevices(): Promise<DeviceWithServices[]> {
const res = await fetch("/api/devices");
if (!res.ok) {
throw new Error(`Geräte konnten nicht geladen werden (HTTP ${res.status})`);
}
return res.json();
}
export function useDevices() {
return useQuery({
queryKey: ["devices"],
queryFn: fetchDevices,
});
}

View File

@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";
import type { ScanLogEntry } from "@launchpad/shared";
async function fetchLogs(): Promise<ScanLogEntry[]> {
const res = await fetch("/api/logs");
if (!res.ok) {
throw new Error(`Logs konnten nicht geladen werden (HTTP ${res.status})`);
}
return res.json();
}
export function useLogs() {
return useQuery({
queryKey: ["logs"],
queryFn: fetchLogs,
refetchInterval: 5000,
});
}

View File

@@ -0,0 +1,22 @@
import { useEffect, useState } from "react";
export type Theme = "light" | "dark";
export function useTheme(): [Theme, () => void] {
const [theme, setTheme] = useState<Theme>(() => {
if (typeof window === "undefined") return "dark";
const stored = window.localStorage.getItem("launchpad-theme");
if (stored === "light" || stored === "dark") return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
});
useEffect(() => {
document.documentElement.classList.toggle("dark", theme === "dark");
window.localStorage.setItem("launchpad-theme", theme);
}, [theme]);
const toggle = () => setTheme((t) => (t === "dark" ? "light" : "dark"));
return [theme, toggle];
}