Преглед изворни кода

RHL-046 feat(overview): implement OverviewHomePage and OverviewCard components

Code_Uwe пре 1 месец
родитељ
комит
984ee961dc
3 измењених фајлова са 141 додато и 37 уклоњено
  1. 2 37
      app/(protected)/page.jsx
  2. 79 0
      components/overview/OverviewCard.jsx
  3. 60 0
      components/overview/OverviewHomePage.jsx

+ 2 - 37
app/(protected)/page.jsx

@@ -1,40 +1,5 @@
-import PlaceholderPage from "@/components/placeholders/PlaceholderPage";
+import OverviewHomePage from "@/components/overview/OverviewHomePage";
 
-/**
- * /
- *
- * RHL-019:
- * - Placeholder entry page
- *
- * Later:
- * - If unauthenticated: redirect to /login
- * - If authenticated: redirect to a branch route (e.g. /NL01)
- */
 export default function ProtectedEntryPage() {
-	return (
-		<PlaceholderPage
-			title="Übersicht"
-			description='Dies ist der geschützte Einstieg ("/"). Später wird hier abhängig von der Sitzung weitergeleitet.'
-		>
-			<div className="space-y-2 text-sm text-muted-foreground">
-				<p>Testen Sie diese URLs manuell:</p>
-				<ul className="list-disc pl-5">
-					<li>
-						<code className="rounded bg-muted px-1 py-0.5">/login</code>
-					</li>
-					<li>
-						<code className="rounded bg-muted px-1 py-0.5">/NL01</code>
-					</li>
-					<li>
-						<code className="rounded bg-muted px-1 py-0.5">
-							/NL01/2025/12/31
-						</code>
-					</li>
-					<li>
-						<code className="rounded bg-muted px-1 py-0.5">/NL01/search</code>
-					</li>
-				</ul>
-			</div>
-		</PlaceholderPage>
-	);
+	return <OverviewHomePage />;
 }

+ 79 - 0
components/overview/OverviewCard.jsx

@@ -0,0 +1,79 @@
+"use client";
+
+import Link from "next/link";
+
+import { cn } from "@/lib/utils";
+import {
+	Card,
+	CardContent,
+	CardDescription,
+	CardTitle,
+} from "@/components/ui/card";
+
+export default function OverviewCard({
+	title,
+	description,
+	imageSrc,
+	href = null,
+	disabledHint = null,
+	containerClassName = "",
+}) {
+	const isDisabled = !href;
+
+	const card = (
+		<Card
+			className={[
+				"h-[420px] w-full overflow-hidden gap-0 py-0 border",
+				"bg-card/90",
+				"transition-all duration-200",
+				isDisabled
+					? "opacity-85"
+					: "group-hover:-translate-y-0.5 group-hover:shadow-lg group-hover:border-primary/30 group-active:translate-y-0",
+			].join(" ")}
+		>
+			<div className="h-56 w-full overflow-hidden border-b bg-muted/30">
+				<img
+					src={imageSrc}
+					alt={`${title} Karte`}
+					className="h-full w-full object-cover"
+					loading="lazy"
+				/>
+			</div>
+
+			<CardContent className="flex flex-1 flex-col space-y-2 px-4 py-3">
+				<CardTitle className="text-base">{title}</CardTitle>
+				<CardDescription className="text-sm leading-relaxed">
+					{description}
+				</CardDescription>
+				{isDisabled && disabledHint ? (
+					<p className="mt-auto text-xs text-muted-foreground">
+						{disabledHint}
+					</p>
+				) : null}
+			</CardContent>
+		</Card>
+	);
+
+	if (isDisabled) {
+		return (
+			<div
+				className={cn("group block w-full", containerClassName)}
+				aria-disabled="true"
+			>
+				{card}
+			</div>
+		);
+	}
+
+	return (
+		<Link
+			href={href}
+			className={cn(
+				"group block w-full rounded-xl focus:outline-none",
+				containerClassName,
+			)}
+		>
+			{card}
+		</Link>
+	);
+}

+ 60 - 0
components/overview/OverviewHomePage.jsx

@@ -0,0 +1,60 @@
+"use client";
+
+import React from "react";
+import { useOverviewBranchTarget } from "@/lib/frontend/overview/useOverviewBranchTarget";
+import { buildOverviewCards } from "@/lib/frontend/overview/cardsConfig";
+import OverviewCard from "@/components/overview/OverviewCard";
+import { getOverviewCardsLayoutClasses } from "@/components/overview/layoutClasses";
+
+export default function OverviewHomePage() {
+	const {
+		isAuthenticated,
+		canManageUsers,
+		explorerHref,
+		searchHref,
+		disabledHint,
+	} = useOverviewBranchTarget();
+
+	if (!isAuthenticated) return null;
+
+	const cards = buildOverviewCards({
+		explorerHref,
+		searchHref,
+		canManageUsers,
+		disabledHint,
+	});
+
+	const { cardsRowClassName, cardItemClassName } =
+		getOverviewCardsLayoutClasses({
+			cardCount: cards.length,
+		});
+
+	return (
+		<div className="min-h-[calc(100dvh-9rem)] py-4">
+			<div className="mx-auto flex h-full w-full flex-col">
+				<div className="space-y-1 text-left">
+					<h1 className="text-2xl font-semibold tracking-tight">Übersicht</h1>
+					<p className="text-sm text-muted-foreground">
+						Schnellzugriff auf die wichtigsten Bereiche.
+					</p>
+				</div>
+
+				<div className="flex flex-1 items-center justify-center pt-20 pb-10">
+					<div className={cardsRowClassName}>
+						{cards.map((card) => (
+							<OverviewCard
+								key={card.key}
+								title={card.title}
+								description={card.description}
+								imageSrc={card.imageSrc}
+								href={card.href}
+								disabledHint={card.disabledHint}
+								containerClassName={cardItemClassName}
+							/>
+						))}
+					</div>
+				</div>
+			</div>
+		</div>
+	);
+}