|
|
@@ -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
|