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

RHL-038 ix(search): stabilize layout of SearchResultsTable with fixed-width columns and improved truncation

Code_Uwe пре 3 недеља
родитељ
комит
4595abce0a
1 измењених фајлова са 50 додато и 31 уклоњено
  1. 50 31
      components/search/SearchResultsTable.jsx

+ 50 - 31
components/search/SearchResultsTable.jsx

@@ -7,7 +7,6 @@ import { Eye, FolderOpen } from "lucide-react";
 import { dayPath } from "@/lib/frontend/routes";
 import { buildPdfUrl } from "@/lib/frontend/explorer/pdfUrl";
 import { formatSearchItemDateDe } from "@/lib/frontend/search/resultsSorting";
-import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
 
 import { Button } from "@/components/ui/button";
 import {
@@ -21,24 +20,32 @@ import {
 } from "@/components/ui/table";
 
 export default function SearchResultsTable({ routeBranch, scope, items }) {
-	const showBranchColumn =
-		scope === SEARCH_SCOPE.ALL || scope === SEARCH_SCOPE.MULTI;
+	// NOTE:
+	// - We intentionally keep the column set stable:
+	//   Branch | Date | File | Path | Actions
+	// - For single-scope searches, all rows will show the same branch (routeBranch),
+	//   but the consistent layout improves scanability and avoids “column jumping”.
 
 	const list = Array.isArray(items) ? items : [];
 
 	return (
-		<Table>
+		<Table className="table-fixed">
 			<TableCaption>
 				Hinweis: PDFs werden in einem neuen Tab geöffnet.
 			</TableCaption>
 
 			<TableHeader>
 				<TableRow>
-					{showBranchColumn ? <TableHead>Niederlassung</TableHead> : null}
-					<TableHead>Datum</TableHead>
+					{/* Fixed-width columns keep the layout stable and prevent “action column drift”. */}
+					<TableHead className="w-24">Niederlassung</TableHead>
+					<TableHead className="w-28">Datum</TableHead>
+
+					{/* Flexible columns: these must truncate to prevent horizontal scrolling. */}
 					<TableHead>Datei</TableHead>
 					<TableHead className="hidden md:table-cell">Pfad</TableHead>
-					<TableHead className="text-right">Aktion</TableHead>
+
+					{/* Fixed actions column so buttons are always reachable without scrolling. */}
+					<TableHead className="w-40 text-right">Aktionen</TableHead>
 				</TableRow>
 			</TableHeader>
 
@@ -50,11 +57,8 @@ export default function SearchResultsTable({ routeBranch, scope, items }) {
 					const day = String(it?.day || "");
 					const filename = String(it?.filename || "");
 					const relativePath = String(it?.relativePath || "");
-					const snippet =
-						typeof it?.snippet === "string" && it.snippet.trim()
-							? it.snippet.trim()
-							: null;
 
+					// Binary PDF endpoint: open in a new tab (do NOT fetch via apiClient).
 					const pdfUrl = buildPdfUrl({
 						branch: itemBranch,
 						year,
@@ -65,46 +69,56 @@ export default function SearchResultsTable({ routeBranch, scope, items }) {
 
 					const dayHref = dayPath(itemBranch, year, month, day);
 
+					// Stable key: relativePath is best; fallback is deterministic.
 					const key =
 						relativePath || `${itemBranch}/${year}/${month}/${day}/${filename}`;
 
 					return (
 						<TableRow key={key}>
-							{showBranchColumn ? (
-								<TableCell>
-									<span className="text-sm">{itemBranch}</span>
-								</TableCell>
-							) : null}
+							<TableCell className="w-24">
+								<span className="text-sm">{itemBranch}</span>
+							</TableCell>
 
-							<TableCell>
+							<TableCell className="w-28">
 								<span className="text-sm">{formatSearchItemDateDe(it)}</span>
 							</TableCell>
 
+							{/* File column: must be shrinkable so truncate works in table-fixed layout. */}
 							<TableCell className="min-w-0">
 								<div className="min-w-0">
-									<p className="truncate font-medium">{filename}</p>
-
-									{snippet ? (
-										<p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">
-											{snippet}
-										</p>
-									) : null}
+									<p className="truncate font-medium" title={filename}>
+										{filename}
+									</p>
 
-									<p className="truncate text-xs text-muted-foreground md:hidden">
+									{/* Mobile: show path as secondary line instead of a dedicated column */}
+									<p
+										className="truncate text-xs text-muted-foreground md:hidden"
+										title={relativePath}
+									>
 										{relativePath}
 									</p>
 								</div>
 							</TableCell>
 
-							<TableCell className="hidden md:table-cell">
-								<span className="text-xs text-muted-foreground">
+							{/* Path column (desktop): always truncate + title for full hover read. */}
+							<TableCell className="hidden md:table-cell min-w-0">
+								<span
+									className="block max-w-full truncate text-xs text-muted-foreground"
+									title={relativePath}
+								>
 									{relativePath}
 								</span>
 							</TableCell>
 
-							<TableCell className="text-right">
-								<div className="flex justify-end gap-2">
-									<Button variant="outline" size="sm" asChild>
+							{/* Actions: fixed-width column, buttons stacked to keep width small and stable. */}
+							<TableCell className="w-40 text-right">
+								<div className="flex flex-col items-end gap-2">
+									<Button
+										variant="outline"
+										size="sm"
+										asChild
+										className="w-36 justify-start"
+									>
 										<a
 											href={pdfUrl}
 											target="_blank"
@@ -117,7 +131,12 @@ export default function SearchResultsTable({ routeBranch, scope, items }) {
 										</a>
 									</Button>
 
-									<Button variant="outline" size="sm" asChild>
+									<Button
+										variant="outline"
+										size="sm"
+										asChild
+										className="w-36 justify-start"
+									>
 										<Link href={dayHref} title="Zum Tag">
 											<FolderOpen className="h-4 w-4" />
 											Zum Tag