import { useMemo, useState } from "react"; import Link from "next/link"; import { type ColumnDef, type OnChangeFn, type SortingState, type Updater, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import type { MarketplaceSkillCardRead } from "@/api/generated/model"; import { DataTable, type DataTableEmptyState } from "@/components/tables/DataTable"; import { dateCell } from "@/components/tables/cell-formatters"; import { Button, buttonVariants } from "@/components/ui/button"; import { truncateText as truncate } from "@/lib/formatters"; type MarketplaceSkillsTableProps = { skills: MarketplaceSkillCardRead[]; installedGatewayNamesBySkillId?: Record; isLoading?: boolean; sorting?: SortingState; onSortingChange?: OnChangeFn; stickyHeader?: boolean; disableSorting?: boolean; isMutating?: boolean; onSkillClick?: (skill: MarketplaceSkillCardRead) => void; onDelete?: (skill: MarketplaceSkillCardRead) => void; getEditHref?: (skill: MarketplaceSkillCardRead) => string; emptyState?: Omit & { icon?: DataTableEmptyState["icon"]; }; }; const DEFAULT_EMPTY_ICON = ( ); const toPackUrl = (sourceUrl: string): string => { try { const parsed = new URL(sourceUrl); const treeMarker = "/tree/"; const markerIndex = parsed.pathname.indexOf(treeMarker); if (markerIndex > 0) { const repoPath = parsed.pathname.slice(0, markerIndex); return `${parsed.origin}${repoPath}`; } return sourceUrl; } catch { return sourceUrl; } }; const toPackLabel = (packUrl: string): string => { try { const parsed = new URL(packUrl); const segments = parsed.pathname.split("/").filter(Boolean); if (segments.length >= 2) { return `${segments[0]}/${segments[1]}`; } return parsed.host; } catch { return "Open pack"; } }; const toPackDetailHref = (packUrl: string): string => { const params = new URLSearchParams({ source_url: packUrl }); return `/skills/packs/detail?${params.toString()}`; }; export function MarketplaceSkillsTable({ skills, installedGatewayNamesBySkillId, isLoading = false, sorting, onSortingChange, stickyHeader = false, disableSorting = false, isMutating = false, onSkillClick, onDelete, getEditHref, emptyState, }: MarketplaceSkillsTableProps) { const [internalSorting, setInternalSorting] = useState([ { id: "name", desc: false }, ]); const resolvedSorting = sorting ?? internalSorting; const handleSortingChange: OnChangeFn = onSortingChange ?? ((updater: Updater) => { setInternalSorting(updater); }); const columns = useMemo[]>(() => { const baseColumns: ColumnDef[] = [ { accessorKey: "name", header: "Skill", cell: ({ row }) => (
{onSkillClick ? ( ) : (

{row.original.name}

)}

{row.original.description || "No description provided."}

), }, { accessorKey: "source_url", header: "Pack", cell: ({ row }) => { const packUrl = toPackUrl(row.original.source_url); return ( {truncate(toPackLabel(packUrl), 40)} ); }, }, { accessorKey: "category", header: "Category", cell: ({ row }) => ( {row.original.category || "uncategorized"} ), }, { accessorKey: "risk", header: "Risk", cell: ({ row }) => ( {row.original.risk || "unknown"} ), }, { accessorKey: "source", header: "Source", cell: ({ row }) => { const sourceHref = row.original.source || row.original.source_url; return ( {truncate(sourceHref, 36)} ); }, }, { id: "installed_on", header: "Installed On", enableSorting: false, cell: ({ row }) => { const installedOn = installedGatewayNamesBySkillId?.[row.original.id] ?? []; if (installedOn.length === 0) { return -; } const installedOnText = installedOn.join(", "); return ( {installedOnText} ); }, }, { accessorKey: "updated_at", header: "Updated", cell: ({ row }) => dateCell(row.original.updated_at), }, { id: "actions", header: "", enableSorting: false, cell: ({ row }) => (
{getEditHref ? ( Edit ) : null} {onDelete ? ( ) : null}
), }, ]; return baseColumns; }, [ getEditHref, installedGatewayNamesBySkillId, isMutating, onDelete, onSkillClick, ]); // eslint-disable-next-line react-hooks/incompatible-library const table = useReactTable({ data: skills, columns, enableSorting: !disableSorting, state: { ...(!disableSorting ? { sorting: resolvedSorting } : {}), }, ...(disableSorting ? {} : { onSortingChange: handleSortingChange }), getCoreRowModel: getCoreRowModel(), ...(disableSorting ? {} : { getSortedRowModel: getSortedRowModel() }), }); return ( ); }