"use client"; import { Broadcast } from "@phosphor-icons/react/dist/ssr/Broadcast"; import { Badge } from "@pythnetwork/component-library/Badge"; import { Card } from "@pythnetwork/component-library/Card"; import { Link } from "@pythnetwork/component-library/Link"; import { Paginator } from "@pythnetwork/component-library/Paginator"; import { SearchInput } from "@pythnetwork/component-library/SearchInput"; import { type RowConfig, type SortDescriptor, Table, } from "@pythnetwork/component-library/Table"; import { type ReactNode, Suspense, useMemo } from "react"; import { useFilter, useCollator } from "react-aria"; import { useQueryParamFilterPagination } from "../../hooks/use-query-param-filter-pagination"; import { ExplainActive, ExplainInactive } from "../Explanations"; import { NoResults } from "../NoResults"; import { PublisherTag } from "../PublisherTag"; import { Ranking } from "../Ranking"; import rootStyles from "../Root/index.module.scss"; import { Score } from "../Score"; const PUBLISHER_SCORE_WIDTH = 38; type Props = { className?: string | undefined; publishers: Publisher[]; explainAverage: ReactNode; }; type Publisher = { id: string; ranking: number; activeFeeds: number; inactiveFeeds: number; averageScore: number; } & ( | { name: string; icon: ReactNode } | { name?: undefined; icon?: undefined } ); export const PublishersCard = ({ publishers, ...props }: Props) => ( }> ); const ResolvedPublishersCard = ({ publishers, ...props }: Props) => { const collator = useCollator(); const filter = useFilter({ sensitivity: "base", usage: "search" }); const { search, sortDescriptor, page, pageSize, updateSearch, updateSortDescriptor, updatePage, updatePageSize, paginatedItems, numResults, numPages, mkPageLink, } = useQueryParamFilterPagination( publishers, (publisher, search) => filter.contains(publisher.id, search) || (publisher.name !== undefined && filter.contains(publisher.name, search)), (a, b, { column, direction }) => { switch (column) { case "ranking": case "activeFeeds": case "inactiveFeeds": case "averageScore": { return ( (direction === "descending" ? -1 : 1) * (a[column] - b[column]) ); } case "name": { return ( (direction === "descending" ? -1 : 1) * collator.compare(a.name ?? a.id, b.name ?? b.id) ); } default: { return ( (direction === "descending" ? -1 : 1) * (a.ranking - b.ranking) ); } } }, { defaultSort: "ranking" }, ); const rows = useMemo( () => paginatedItems.map( ({ id, ranking, averageScore, activeFeeds, inactiveFeeds, ...publisher }) => ({ id, href: `/publishers/${id}`, data: { ranking: {ranking}, name: ( ), activeFeeds: ( {activeFeeds} ), inactiveFeeds: ( {inactiveFeeds} ), averageScore: ( ), }, }), ), [paginatedItems], ); return ( ); }; type PublishersCardContentsProps = Pick & ( | { isLoading: true } | { isLoading?: false; numResults: number; search: string; sortDescriptor: SortDescriptor; numPages: number; page: number; pageSize: number; onSearchChange: (newSearch: string) => void; onSortChange: (newSort: SortDescriptor) => void; onPageSizeChange: (newPageSize: number) => void; onPageChange: (newPage: number) => void; mkPageLink: (page: number) => string; rows: RowConfig< "ranking" | "name" | "activeFeeds" | "inactiveFeeds" | "averageScore" >[]; } ); const PublishersCardContents = ({ className, explainAverage, ...props }: PublishersCardContentsProps) => ( } title={ <> Publishers {!props.isLoading && ( {props.numResults} )} } toolbar={ } {...(!props.isLoading && { footer: ( ), })} > , allowsSorting: true, }, { id: "name", name: "NAME / ID", isRowHeader: true, alignment: "left", loadingSkeleton: , allowsSorting: true, }, { id: "activeFeeds", name: ( <> ACTIVE FEEDS ), alignment: "center", width: 30, allowsSorting: true, }, { id: "inactiveFeeds", name: ( <> INACTIVE FEEDS ), alignment: "center", width: 30, allowsSorting: true, }, { id: "averageScore", name: ( <> AVERAGE SCORE {explainAverage} ), alignment: "right", width: PUBLISHER_SCORE_WIDTH, loadingSkeleton: , allowsSorting: true, }, ]} {...(props.isLoading ? { isLoading: true, } : { rows: props.rows, sortDescriptor: props.sortDescriptor, onSortChange: props.onSortChange, emptyState: ( { props.onSearchChange(""); }} /> ), })} /> );