Files
EpicNext-Cms/src/app/admin/catalog/catalog-table.tsx
T
openhands 17847545dd
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m52s
Improvements: remove dead config, fix ESM, add URL validation, unify types, add missing logging
- 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
2026-07-26 20:28:11 +02:00

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
}
/>
);
}