Explorar o código

RHL-022 feat(explorer): add empty, error, loading, and not found states for explorer component

Code_Uwe hai 1 mes
pai
achega
e314501c9a

+ 33 - 0
components/explorer/states/ExplorerEmpty.jsx

@@ -0,0 +1,33 @@
+import React from "react";
+import Link from "next/link";
+import { Inbox } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+/**
+ * ExplorerEmpty
+ *
+ * Friendly empty state with an optional "go up" action.
+ *
+ * @param {{
+ *   title: string,
+ *   description: string,
+ *   upHref?: string|null
+ * }} props
+ */
+export default function ExplorerEmpty({ title, description, upHref = null }) {
+	return (
+		<div className="flex flex-col items-center justify-center gap-2 py-10 text-center">
+			<Inbox className="h-6 w-6 text-muted-foreground" aria-hidden="true" />
+			<div className="space-y-1">
+				<p className="text-sm font-medium">{title}</p>
+				<p className="text-sm text-muted-foreground">{description}</p>
+			</div>
+
+			{upHref ? (
+				<Button variant="outline" size="sm" asChild>
+					<Link href={upHref}>Eine Ebene höher</Link>
+				</Button>
+			) : null}
+		</div>
+	);
+}

+ 33 - 0
components/explorer/states/ExplorerError.jsx

@@ -0,0 +1,33 @@
+import React from "react";
+import { RefreshCw, TriangleAlert } from "lucide-react";
+import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+
+/**
+ * ExplorerError
+ *
+ * Generic error state with retry action.
+ * All copy is German (user-facing).
+ *
+ * @param {{
+ *   title: string,
+ *   description: string,
+ *   onRetry: () => void
+ * }} props
+ */
+export default function ExplorerError({ title, description, onRetry }) {
+	return (
+		<div className="space-y-3">
+			<Alert variant="destructive">
+				<TriangleAlert className="h-4 w-4" />
+				<AlertTitle>{title}</AlertTitle>
+				<AlertDescription>{description}</AlertDescription>
+			</Alert>
+
+			<Button type="button" variant="outline" onClick={onRetry}>
+				<RefreshCw className="h-4 w-4" />
+				Erneut versuchen
+			</Button>
+		</div>
+	);
+}

+ 38 - 0
components/explorer/states/ExplorerLoading.jsx

@@ -0,0 +1,38 @@
+import React from "react";
+import { Skeleton } from "@/components/ui/skeleton";
+
+/**
+ * ExplorerLoading
+ *
+ * Loading placeholder for Explorer lists.
+ * Supports a grid and a table-like variant.
+ *
+ * @param {{ variant?: "grid"|"table", count?: number }} props
+ */
+export default function ExplorerLoading({ variant = "grid", count = 8 }) {
+	const n = Number.isInteger(count) && count > 0 ? count : 8;
+
+	if (variant === "table") {
+		return (
+			<div className="space-y-2">
+				{Array.from({ length: n }).map((_, i) => (
+					<div key={i} className="flex items-center justify-between gap-3">
+						<div className="flex min-w-0 flex-1 items-center gap-2">
+							<Skeleton className="h-4 w-4" />
+							<Skeleton className="h-4 w-[60%]" />
+						</div>
+						<Skeleton className="h-8 w-24" />
+					</div>
+				))}
+			</div>
+		);
+	}
+
+	return (
+		<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
+			{Array.from({ length: n }).map((_, i) => (
+				<Skeleton key={i} className="h-10 w-full" />
+			))}
+		</div>
+	);
+}

+ 66 - 0
components/explorer/states/ExplorerNotFound.jsx

@@ -0,0 +1,66 @@
+import React from "react";
+import Link from "next/link";
+import { FolderX } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+	Card,
+	CardHeader,
+	CardTitle,
+	CardDescription,
+	CardContent,
+	CardFooter,
+} from "@/components/ui/card";
+
+/**
+ * ExplorerNotFound
+ *
+ * Used when the backend returns FS_NOT_FOUND for a syntactically valid route.
+ * This can happen when the NAS structure changed after the user navigated.
+ *
+ * @param {{
+ *   title?: string,
+ *   description?: string,
+ *   upHref?: string|null,
+ *   branchRootHref?: string|null
+ * }} props
+ */
+export default function ExplorerNotFound({
+	title = "Nicht gefunden",
+	description = "Dieser Pfad existiert nicht (mehr). Bitte wählen Sie eine andere Ebene.",
+	upHref = null,
+	branchRootHref = null,
+}) {
+	return (
+		<Card>
+			<CardHeader>
+				<CardTitle className="flex items-center gap-2">
+					<FolderX
+						className="h-5 w-5 text-muted-foreground"
+						aria-hidden="true"
+					/>
+					{title}
+				</CardTitle>
+				<CardDescription>{description}</CardDescription>
+			</CardHeader>
+
+			<CardContent className="text-sm text-muted-foreground">
+				Dies kann passieren, wenn Ordner auf dem NAS verschoben oder gelöscht
+				wurden.
+			</CardContent>
+
+			<CardFooter className="flex flex-col gap-2 sm:flex-row sm:justify-end">
+				{upHref ? (
+					<Button variant="outline" asChild>
+						<Link href={upHref}>Eine Ebene höher</Link>
+					</Button>
+				) : null}
+
+				{branchRootHref ? (
+					<Button asChild>
+						<Link href={branchRootHref}>Zur Niederlassung</Link>
+					</Button>
+				) : null}
+			</CardFooter>
+		</Card>
+	);
+}