Wrap next/link in a shared Link component that ships prefetch=false by default, so no route is ever prefetched (viewport or hover) anymore, and drop the DNS prefetch hint. Removes hidden background requests that were the source of intermittent issues.
119 lines
2.7 KiB
TypeScript
119 lines
2.7 KiB
TypeScript
"use client";
|
||
|
||
import { Eye, Package, Pencil } from "lucide-react";
|
||
import { DataTable } from "@/components/admin/data-table";
|
||
import Link from "@/components/link";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { Button } from "@/components/ui/button";
|
||
import type { DataTableColumn, PaginatedResult } from "@/types/common";
|
||
|
||
export interface ItemsBaseRow {
|
||
id: number;
|
||
spriteId: number;
|
||
publicName: string;
|
||
itemName: string;
|
||
type: string;
|
||
interactionType: string;
|
||
width: number;
|
||
length: number;
|
||
}
|
||
|
||
function getColumns(canEdit: boolean): DataTableColumn<ItemsBaseRow>[] {
|
||
return [
|
||
{ key: "id", label: "ID", sortable: true },
|
||
{
|
||
key: "publicName",
|
||
label: "Name",
|
||
sortable: true,
|
||
render: (_, row) => (
|
||
<Link
|
||
href={`/admin/items/${row.id}`}
|
||
className="hover:opacity-80 flex items-center gap-2 min-w-0"
|
||
>
|
||
<Package className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||
<div className="min-w-0">
|
||
<span className="font-medium block truncate">
|
||
{row.publicName || "—"}
|
||
</span>
|
||
<span className="text-xs text-muted-foreground font-mono truncate block">
|
||
{row.itemName}
|
||
</span>
|
||
</div>
|
||
</Link>
|
||
),
|
||
},
|
||
{
|
||
key: "spriteId",
|
||
label: "Sprite",
|
||
sortable: true,
|
||
render: (v) => <span className="tabular-nums text-sm">{String(v)}</span>,
|
||
},
|
||
{
|
||
key: "type",
|
||
label: "Type",
|
||
sortable: true,
|
||
filterKey: "filter_type",
|
||
filterOptions: [
|
||
{ label: "Floor (s)", value: "s" },
|
||
{ label: "Wall (i)", value: "i" },
|
||
{ label: "Effect (e)", value: "e" },
|
||
{ label: "Badge (b)", value: "b" },
|
||
{ label: "Robot (r)", value: "r" },
|
||
],
|
||
render: (v) => <Badge variant="outline">{String(v)}</Badge>,
|
||
},
|
||
{
|
||
key: "interactionType",
|
||
label: "Interaction",
|
||
sortable: true,
|
||
render: (v) => (
|
||
<span className="text-xs font-mono text-muted-foreground">
|
||
{String(v || "default")}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
key: "width",
|
||
label: "Size",
|
||
render: (_, row) => (
|
||
<span className="tabular-nums text-sm">
|
||
{row.width}×{row.length}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
key: "actions",
|
||
label: "",
|
||
render: (_, row) => (
|
||
<div className="flex gap-1 justify-end">
|
||
<Button variant="ghost" size="icon" asChild>
|
||
<Link href={`/admin/items/${row.id}`} aria-label="View">
|
||
{canEdit ? (
|
||
<Pencil className="h-4 w-4" />
|
||
) : (
|
||
<Eye className="h-4 w-4" />
|
||
)}
|
||
</Link>
|
||
</Button>
|
||
</div>
|
||
),
|
||
},
|
||
];
|
||
}
|
||
|
||
export function ItemsTable({
|
||
data,
|
||
canEdit,
|
||
}: {
|
||
data: PaginatedResult<ItemsBaseRow>;
|
||
canEdit: boolean;
|
||
}) {
|
||
return (
|
||
<DataTable
|
||
columns={getColumns(canEdit)}
|
||
data={data}
|
||
searchPlaceholder="Search name, classname or ID…"
|
||
/>
|
||
);
|
||
}
|