generated from Dicken/dickendock
Fix: Adminbereich responsiv (Mobile-Drawer-Navigation, scrollbare Tabellen)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
@@ -13,17 +14,65 @@ const NAV_ITEMS = [
|
||||
|
||||
export function AdminLayout() {
|
||||
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
// Drawer schließen, wenn per Navigation die Seite wechselt
|
||||
useEffect(() => {
|
||||
setMobileOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-neutral-50 dark:bg-neutral-950">
|
||||
<aside className="flex w-56 shrink-0 flex-col border-r border-black/10 bg-white/70 dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-center gap-2 border-b border-black/10 px-5 py-4 dark:border-white/10">
|
||||
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-950 sm:flex">
|
||||
{/* Mobile-Kopfleiste mit Hamburger-Button, nur unterhalb des sm-Breakpoints sichtbar */}
|
||||
<div
|
||||
className="flex items-center gap-3 border-b border-black/10 bg-white/80 px-4 py-3
|
||||
backdrop-blur-md dark:border-white/10 dark:bg-neutral-950/80 sm:hidden"
|
||||
>
|
||||
<button
|
||||
onClick={() => setMobileOpen(true)}
|
||||
aria-label="Menü öffnen"
|
||||
className="rounded-lg p-1.5 text-black/70 hover:bg-black/5 dark:text-white/70 dark:hover:bg-white/10"
|
||||
>
|
||||
<span className="block text-xl leading-none" aria-hidden>
|
||||
☰
|
||||
</span>
|
||||
</button>
|
||||
<Link to="/" className="text-base font-semibold text-black dark:text-white">
|
||||
LaunchPad
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Abdunkelungs-Overlay hinter der aufgeklappten Drawer-Navigation (nur mobil) */}
|
||||
{mobileOpen ? (
|
||||
<div
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="fixed inset-0 z-30 bg-black/40 sm:hidden"
|
||||
aria-hidden
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<aside
|
||||
className={`fixed inset-y-0 left-0 z-40 flex w-64 flex-col border-r border-black/10
|
||||
bg-white transition-transform duration-200 ease-out dark:border-white/10
|
||||
dark:bg-neutral-950 sm:static sm:z-auto sm:w-56 sm:shrink-0 sm:translate-x-0
|
||||
sm:bg-white/70 sm:transition-none dark:sm:bg-white/5 ${
|
||||
mobileOpen ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 border-b border-black/10 px-5 py-4 dark:border-white/10">
|
||||
<Link to="/" className="text-lg font-semibold text-black dark:text-white">
|
||||
LaunchPad
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setMobileOpen(false)}
|
||||
aria-label="Menü schließen"
|
||||
className="rounded-lg p-1 text-black/50 hover:bg-black/5 dark:text-white/50 dark:hover:bg-white/10 sm:hidden"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-1 flex-col gap-0.5 p-3">
|
||||
<nav className="flex flex-1 flex-col gap-0.5 overflow-y-auto p-3">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const active = pathname.startsWith(item.to);
|
||||
return (
|
||||
@@ -56,7 +105,7 @@ export function AdminLayout() {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-8">
|
||||
<main className="flex-1 overflow-x-hidden overflow-y-auto p-4 sm:p-8">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -196,7 +196,7 @@ export function CategoriesPage() {
|
||||
description="Per Drag & Drop sortieren. Favoriten erscheinen in der Suche trotzdem immer zuerst."
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mb-6 flex items-end gap-2">
|
||||
<form onSubmit={handleSubmit} className="mb-6 flex flex-wrap items-end gap-2">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-black/50 dark:text-white/50">
|
||||
Neue Kategorie
|
||||
|
||||
@@ -49,24 +49,26 @@ export function DashboardPage() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||||
<table className="w-full text-sm">
|
||||
<tbody>
|
||||
{recentlyScanned.map((device) => (
|
||||
<tr
|
||||
key={device.id}
|
||||
className="border-b border-black/5 last:border-0 dark:border-white/5"
|
||||
>
|
||||
<td className="px-4 py-3 font-medium text-black dark:text-white">
|
||||
{device.hostname}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-black/40 dark:text-white/40">{device.ip}</td>
|
||||
<td className="px-4 py-3 text-black/40 dark:text-white/40">
|
||||
{device.lastScan ? new Date(device.lastScan).toLocaleString("de-DE") : "–"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<tbody>
|
||||
{recentlyScanned.map((device) => (
|
||||
<tr
|
||||
key={device.id}
|
||||
className="border-b border-black/5 last:border-0 dark:border-white/5"
|
||||
>
|
||||
<td className="whitespace-nowrap px-4 py-3 font-medium text-black dark:text-white">
|
||||
{device.hostname}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-4 py-3 text-black/40 dark:text-white/40">{device.ip}</td>
|
||||
<td className="whitespace-nowrap px-4 py-3 text-black/40 dark:text-white/40">
|
||||
{device.lastScan ? new Date(device.lastScan).toLocaleString("de-DE") : "–"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -134,7 +134,7 @@ function AddDeviceForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex items-end gap-2">
|
||||
<form onSubmit={handleSubmit} className="flex flex-wrap items-end gap-2">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-black/50 dark:text-white/50">
|
||||
Hostname
|
||||
@@ -191,23 +191,25 @@ export function DevicesPage() {
|
||||
<p className="text-sm text-red-500">Geräte konnten nicht geladen werden.</p>
|
||||
) : devices && devices.length > 0 ? (
|
||||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium">Gerät</th>
|
||||
<th className="px-4 py-2 font-medium">Status</th>
|
||||
<th className="px-4 py-2 font-medium">Dienste</th>
|
||||
<th className="px-4 py-2 font-medium">Letzter Scan</th>
|
||||
<th className="px-4 py-2" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((device) => (
|
||||
<DeviceRow key={device.id} device={device} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[640px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium">Gerät</th>
|
||||
<th className="px-4 py-2 font-medium">Status</th>
|
||||
<th className="px-4 py-2 font-medium">Dienste</th>
|
||||
<th className="px-4 py-2 font-medium">Letzter Scan</th>
|
||||
<th className="px-4 py-2" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((device) => (
|
||||
<DeviceRow key={device.id} device={device} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-black/40 dark:text-white/40">
|
||||
|
||||
@@ -17,37 +17,39 @@ export function LogsPage() {
|
||||
<p className="text-sm text-red-500">Logs konnten nicht geladen werden.</p>
|
||||
) : logs && logs.length > 0 ? (
|
||||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium">Zeit</th>
|
||||
<th className="px-4 py-2 font-medium">Typ</th>
|
||||
<th className="px-4 py-2 font-medium">Nachricht</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{logs.map((log) => (
|
||||
<tr key={log.id} className="border-b border-black/5 last:border-0 dark:border-white/5">
|
||||
<td className="whitespace-nowrap px-4 py-3 text-xs text-black/40 dark:text-white/40">
|
||||
{new Date(log.createdAt).toLocaleString("de-DE")}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||
log.level === "error"
|
||||
? "bg-red-500/10 text-red-600 dark:text-red-400"
|
||||
: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||
}`}
|
||||
>
|
||||
{log.type === "fritzbox" ? "FritzBox" : "Gerät"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-black/70 dark:text-white/70">{log.message}</td>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[560px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium">Zeit</th>
|
||||
<th className="px-4 py-2 font-medium">Typ</th>
|
||||
<th className="px-4 py-2 font-medium">Nachricht</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{logs.map((log) => (
|
||||
<tr key={log.id} className="border-b border-black/5 last:border-0 dark:border-white/5">
|
||||
<td className="whitespace-nowrap px-4 py-3 text-xs text-black/40 dark:text-white/40">
|
||||
{new Date(log.createdAt).toLocaleString("de-DE")}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||
log.level === "error"
|
||||
? "bg-red-500/10 text-red-600 dark:text-red-400"
|
||||
: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||
}`}
|
||||
>
|
||||
{log.type === "fritzbox" ? "FritzBox" : "Gerät"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-black/70 dark:text-white/70">{log.message}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-black/40 dark:text-white/40">
|
||||
|
||||
@@ -188,24 +188,26 @@ export function ServicesPage() {
|
||||
<p className="text-sm text-red-500">Dienste konnten nicht geladen werden.</p>
|
||||
) : services && services.length > 0 ? (
|
||||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium" />
|
||||
<th className="px-4 py-2 font-medium">Dienst</th>
|
||||
<th className="px-4 py-2 font-medium">Kategorie</th>
|
||||
<th className="px-4 py-2 font-medium">Alias</th>
|
||||
<th className="px-4 py-2 font-medium">URL</th>
|
||||
<th className="px-4 py-2" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{services.map((service) => (
|
||||
<ServiceRow key={service.id} service={service} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[720px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<th className="px-4 py-2 font-medium" />
|
||||
<th className="px-4 py-2 font-medium">Dienst</th>
|
||||
<th className="px-4 py-2 font-medium">Kategorie</th>
|
||||
<th className="px-4 py-2 font-medium">Alias</th>
|
||||
<th className="px-4 py-2 font-medium">URL</th>
|
||||
<th className="px-4 py-2" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{services.map((service) => (
|
||||
<ServiceRow key={service.id} service={service} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-black/40 dark:text-white/40">
|
||||
|
||||
Reference in New Issue
Block a user