- Remove .prettierrc (dead config, Biome replaces Prettier) - Rename lighthouserc.json to lighthouserc.cjs with module.exports for ESM compat - Add logger.warn to empty catch blocks in auth, register, site-settings, prisma-cache, redis, security, rate-limit - Unify ActionResult type: action-helper.ts uses 'ok' consistent with safe-action-shared.ts - Add noUnusedLocals + noUnusedParameters to tsconfig + fix 25 pre-existing unused vars - Replace barrel export src/types/index.ts with direct @/types/common imports - Make trustHost conditional (development only) in auth.ts - Add pre-flight URL validation to update-Nitrov3.sh to catch image.library.url misconfigurations - Improve NITRO_IMAGE_LIBRARY_URL content validation in pre-flight & post-compute checks
197 lines
4.5 KiB
TypeScript
197 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { Pencil } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { toggleCatalogPage } from "@/actions/catalog";
|
|
import { toggleBcPage } from "@/actions/catalog-bc";
|
|
import { DataTable } from "@/components/admin/data-table";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useServerAction } from "@/hooks/use-server-action";
|
|
import type { DataTableColumn, PaginatedResult } from "@/types/common";
|
|
|
|
interface CatalogPageRow {
|
|
id: number;
|
|
caption: string;
|
|
parentId: number;
|
|
parentCaption: string;
|
|
pageLayout: string;
|
|
enabled: string;
|
|
visible: string;
|
|
minRank: number;
|
|
orderNum: number;
|
|
itemCount: number;
|
|
childCount: number;
|
|
}
|
|
|
|
interface CatalogTableProps {
|
|
data: PaginatedResult<CatalogPageRow>;
|
|
canEdit: boolean;
|
|
catalogType?: "normal" | "bc";
|
|
}
|
|
|
|
export function CatalogTable({
|
|
data,
|
|
canEdit,
|
|
catalogType = "normal",
|
|
}: CatalogTableProps) {
|
|
const { run, isPending } = useServerAction();
|
|
const isBc = catalogType === "bc";
|
|
|
|
// Parent URLs stay in the same catalog; root takes you to the catalog index.
|
|
const detailHref = (id: number) =>
|
|
isBc ? `/admin/catalog/builder-club/${id}` : `/admin/catalog/${id}`;
|
|
|
|
function handleToggle(id: number, action: "toggleEnabled" | "toggleVisible") {
|
|
if (isBc) {
|
|
const field = action === "toggleEnabled" ? "enabled" : "visible";
|
|
run(() => toggleBcPage({ id, field }), { successMessage: "Updated." });
|
|
} else {
|
|
run(() => toggleCatalogPage({ id, action }), {
|
|
successMessage: "Updated.",
|
|
});
|
|
}
|
|
}
|
|
|
|
const columns: DataTableColumn<CatalogPageRow>[] = [
|
|
{ key: "id", label: "ID", sortable: true },
|
|
{
|
|
key: "caption",
|
|
label: "Caption",
|
|
sortable: true,
|
|
render: (value, row) => (
|
|
<Link
|
|
href={detailHref(row.id)}
|
|
className="font-medium text-primary hover:underline"
|
|
>
|
|
{String(value)}
|
|
</Link>
|
|
),
|
|
},
|
|
{
|
|
key: "parentId",
|
|
label: "Parent",
|
|
sortable: true,
|
|
render: (_, row) =>
|
|
row.parentId === -1 ? (
|
|
<span className="text-xs text-muted-foreground">Root</span>
|
|
) : (
|
|
<Link
|
|
href={detailHref(row.parentId)}
|
|
className="text-xs hover:underline"
|
|
>
|
|
{row.parentCaption}
|
|
</Link>
|
|
),
|
|
},
|
|
{
|
|
key: "pageLayout",
|
|
label: "Layout",
|
|
render: (value) => <Badge variant="secondary">{String(value)}</Badge>,
|
|
},
|
|
{
|
|
key: "enabled",
|
|
label: "Enabled",
|
|
sortable: true,
|
|
filterKey: "filter_enabled",
|
|
filterOptions: [
|
|
{ label: "Enabled", value: "1" },
|
|
{ label: "Disabled", value: "0" },
|
|
],
|
|
render: (value, row) =>
|
|
canEdit ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => handleToggle(row.id, "toggleEnabled")}
|
|
disabled={isPending}
|
|
className="cursor-pointer disabled:opacity-50"
|
|
>
|
|
<Badge variant={value === "1" ? "default" : "outline"}>
|
|
{value === "1" ? "Yes" : "No"}
|
|
</Badge>
|
|
</button>
|
|
) : (
|
|
<Badge variant={value === "1" ? "default" : "outline"}>
|
|
{value === "1" ? "Yes" : "No"}
|
|
</Badge>
|
|
),
|
|
},
|
|
{
|
|
key: "visible",
|
|
label: "Visible",
|
|
sortable: true,
|
|
filterKey: "filter_visible",
|
|
filterOptions: [
|
|
{ label: "Visible", value: "1" },
|
|
{ label: "Hidden", value: "0" },
|
|
],
|
|
render: (value, row) =>
|
|
canEdit ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => handleToggle(row.id, "toggleVisible")}
|
|
disabled={isPending}
|
|
className="cursor-pointer disabled:opacity-50"
|
|
>
|
|
<Badge variant={value === "1" ? "default" : "outline"}>
|
|
{value === "1" ? "Yes" : "No"}
|
|
</Badge>
|
|
</button>
|
|
) : (
|
|
<Badge variant={value === "1" ? "default" : "outline"}>
|
|
{value === "1" ? "Yes" : "No"}
|
|
</Badge>
|
|
),
|
|
},
|
|
...(isBc
|
|
? []
|
|
: [
|
|
{
|
|
key: "minRank" as const,
|
|
label: "Min Rank",
|
|
sortable: true,
|
|
} satisfies DataTableColumn<CatalogPageRow>,
|
|
]),
|
|
{
|
|
key: "itemCount",
|
|
label: "Items",
|
|
render: (value) => <span>{String(value)}</span>,
|
|
},
|
|
{
|
|
key: "childCount",
|
|
label: "Children",
|
|
render: (value) => <span>{String(value)}</span>,
|
|
},
|
|
{
|
|
key: "actions",
|
|
label: "Actions",
|
|
render: (_, row) => (
|
|
<Button variant="ghost" size="icon" asChild>
|
|
<Link href={detailHref(row.id)}>
|
|
<Pencil className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<DataTable
|
|
data={data}
|
|
columns={columns}
|
|
searchPlaceholder="Search by caption, layout or item..."
|
|
bulkActions={
|
|
canEdit
|
|
? [
|
|
{
|
|
label: "Delete Selected",
|
|
value: "delete",
|
|
variant: "destructive",
|
|
},
|
|
]
|
|
: undefined
|
|
}
|
|
/>
|
|
);
|
|
}
|