L4 Einsatz mobil: Mobile Shell und Einsatz-Oberfläche

- /m in eigene Route-Group (field)/m verschoben (emergency-Platzhalter mit),
  Zugriffsprüfungen aus (app)/layout.tsx nach server/app-access.ts extrahiert
  und von Backoffice- und Mobile-Shell gemeinsam genutzt
- Mobile Shell mit Bottom-Nav (Heute · Aufträge · Notdienst · Sync · Profil)
  und Online/Offline-Badge; Startseite rollenabhängig (Feldrollen → /m),
  Login-Default-Redirect auf /
- Heute, Auftragsliste mit Tabs, Auftragsdetail mit einer Primäraktion je
  Zustand, Unterseiten Fotos (Kamera, Kompression, Upload-Fortschritt),
  Notizen + Sprachaufnahme, Material mit Stepper, Checkliste, Zeiten mit
  Korrektur, Profil; Sync-Platzhalter für L7
- Client-Wrapper submitOp (lib/field/client-ops.ts), Upload mit Fortschritt,
  Bildkompression, Formatierung; Texte in messages/{de,en}/field.json

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 12:30:03 +02:00
co-authored by Claude Opus 5
parent 952e74cddd
commit 95f41b29f5
42 changed files with 3186 additions and 66 deletions
+34
View File
@@ -0,0 +1,34 @@
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { requireAppAccess } from "@/server/app-access";
import { CraftviaLogo } from "@/components/brand/craftvia-logo";
import { AccountInactiveNotice } from "@/components/account-inactive-notice";
import { BottomNav } from "@/components/field/bottom-nav";
import { OnlineBadge } from "@/components/field/online-badge";
/**
* Mobile shell `/m` (ARCHITEKTUR §5): same session/account/MFA checks as the backoffice
* (src/server/app-access.ts), no sidebar, bottom navigation, online/offline badge.
* Module gates live one level below: (core) → "field", emergency → "emergency".
*/
export default async function FieldShell({ children }: Readonly<{ children: React.ReactNode }>) {
const access = await requireAppAccess();
if (access.kind === "inactive") return <AccountInactiveNotice />;
const t = await getTranslations("field.nav");
return (
<div className="flex min-h-screen flex-1 flex-col bg-background">
<header className="sticky top-0 z-20 flex h-14 items-center justify-between gap-3 border-b bg-card px-4 pt-[env(safe-area-inset-top)]">
<Link href="/m" aria-label={t("today")} className="flex min-h-12 items-center">
<CraftviaLogo variant="horizontal" height={26} />
</Link>
<div className="flex items-center gap-2">
{/* Slot für L6: <NotificationBell variant="mobile" /> aus src/components/notifications/bell.tsx */}
<OnlineBadge />
</div>
</header>
<div className="mx-auto w-full max-w-xl flex-1 pb-28">{children}</div>
<BottomNav />
</div>
);
}