feat(frontend): Responsive Smartphone-Oberfläche (Modul 15)
Mobile Bottom-Navigation und permanent erreichbare "+ Neu"- Schnellaktion (Durchsicht/Varroa/Behandlung/Fütterung/Aufgabe/Ernte). Header-Navigation überarbeitet, damit sie bei schmaler Breite intern scrollt statt die Seite horizontal zu sprengen. Verifiziert bei Smartphone- (390px) und Tablet-Breite (820px).
This commit is contained in:
1 parent
166979ff3c
commit
45646833b9
4 files changed
+99
-7
No files matched your search
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { LayoutDashboard, Layers, ListTodo, Search } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const ITEMS = [
|
||||||
|
{ label: "Start", href: "/dashboard", icon: LayoutDashboard },
|
||||||
|
{ label: "Völker", href: "/colonies", icon: Layers },
|
||||||
|
{ label: "Aufgaben", href: "/tasks", icon: ListTodo },
|
||||||
|
{ label: "Suche", href: "/search", icon: Search },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function BottomNav() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="fixed inset-x-0 bottom-0 z-30 flex border-t bg-background sm:hidden">
|
||||||
|
{ITEMS.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive = pathname.startsWith(item.href);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 flex-col items-center gap-1 py-2 text-xs",
|
||||||
|
isActive ? "text-primary" : "text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="size-5" />
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "@/lib/auth/auth-context";
|
import { useAuth } from "@/lib/auth/auth-context";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { BottomNav } from "./bottom-nav";
|
||||||
|
import { QuickAddMenu } from "./quick-add-menu";
|
||||||
|
|
||||||
export default function AppLayout({ children }: { children: ReactNode }) {
|
export default function AppLayout({ children }: { children: ReactNode }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -22,9 +24,10 @@ export default function AppLayout({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<div className="flex min-h-screen flex-col">
|
||||||
<header className="flex items-center justify-between border-b px-4 py-3">
|
<header className="flex items-center justify-between gap-3 border-b px-4 py-3">
|
||||||
<nav className="flex items-center gap-4">
|
<nav className="flex min-w-0 items-center gap-4 max-sm:gap-2">
|
||||||
<span className="font-semibold">ApiCore</span>
|
<span className="shrink-0 font-semibold">ApiCore</span>
|
||||||
|
<div className="hidden min-w-0 items-center gap-4 overflow-x-auto whitespace-nowrap sm:flex">
|
||||||
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/dashboard">
|
<Link className="text-sm text-muted-foreground hover:text-foreground" href="/dashboard">
|
||||||
Dashboard
|
Dashboard
|
||||||
</Link>
|
</Link>
|
||||||
@@ -60,9 +63,12 @@ export default function AppLayout({ children }: { children: ReactNode }) {
|
|||||||
Benutzer
|
Benutzer
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="flex items-center gap-3 text-sm">
|
<div className="flex shrink-0 items-center gap-3 text-sm">
|
||||||
<span className="text-muted-foreground">{user.email}</span>
|
<span className="hidden max-w-40 truncate text-muted-foreground md:inline">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -74,7 +80,9 @@ export default function AppLayout({ children }: { children: ReactNode }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="flex-1">{children}</div>
|
<div className="flex-1 pb-16 sm:pb-0">{children}</div>
|
||||||
|
<BottomNav />
|
||||||
|
<QuickAddMenu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
const QUICK_ACTIONS = [
|
||||||
|
{ label: "Durchsicht", href: "/inspections/new" },
|
||||||
|
{ label: "Varroa-Messung", href: "/varroa/new" },
|
||||||
|
{ label: "Behandlung", href: "/treatments/new" },
|
||||||
|
{ label: "Fütterung", href: "/feedings/new" },
|
||||||
|
{ label: "Aufgabe", href: "/tasks/new" },
|
||||||
|
{ label: "Ernte", href: "/harvests/new" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function QuickAddMenu() {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
size="icon-lg"
|
||||||
|
className="fixed right-4 bottom-20 z-40 rounded-full shadow-lg sm:hidden"
|
||||||
|
aria-label="Neu"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Plus className="size-6" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" side="top">
|
||||||
|
{QUICK_ACTIONS.map((action) => (
|
||||||
|
<DropdownMenuItem key={action.href} render={<Link href={action.href} />}>
|
||||||
|
{action.label}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@ Abgeleitet aus [`docs/lastenheft.md`](docs/lastenheft.md) Abschnitt 26. Reihenfo
|
|||||||
### 15. Responsive Smartphone-Oberfläche
|
### 15. Responsive Smartphone-Oberfläche
|
||||||
|
|
||||||
- Frontend: Mobile-first-Layout für alle o.g. Views verifiziert (Touch-Targets, Bottom-Nav, "+ Neu"-Schnellaktion permanent erreichbar)
|
- Frontend: Mobile-first-Layout für alle o.g. Views verifiziert (Touch-Targets, Bottom-Nav, "+ Neu"-Schnellaktion permanent erreichbar)
|
||||||
- [ ] Manuell auf simulierter Smartphone-Breite (< 480px) und Tablet-Breite geprüft
|
- [x] Manuell auf simulierter Smartphone-Breite (< 480px) und Tablet-Breite geprüft
|
||||||
|
|
||||||
### 16–18. API, API-Dokumentation, Rollen/Berechtigungen
|
### 16–18. API, API-Dokumentation, Rollen/Berechtigungen
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user