import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'; import { ApiErrorPanel } from '../ApiErrorPanel'; import type { Column } from '../DataTable'; import { DataTable } from '../DataTable'; import { DataTableSkeleton } from '../Skeleton'; import { useDebouncedValue } from '../../../hooks/useDebouncedValue'; import { useWpTermCollection } from '../../../hooks/useWpTermCollection'; import { columnVisibilityStorageKey, loadColumnVisibility, saveColumnVisibility } from '../../../lib/columnVisibility'; import type { WpTerm } from '../../../types'; import { BulkActionsBar } from './BulkActionsBar'; import { ColumnVisibilityMenu } from './ColumnVisibilityMenu'; import { ListEmptyState } from './ListEmptyState'; import { ListPanel } from './ListPanel'; import { ListSearchToolbar, type SortFieldOption } from './ListSearchToolbar'; import { DEFAULT_LIST_PER_PAGE, ListPaginationBar } from './ListPaginationBar'; import { resolveColumnPickerLabel } from '../../../lib/columnPickerLabel'; type Props = { taxonomyRestBase: string; /** Increment after mutations so the list refetches. */ listRefreshNonce?: number; contextHint: string; searchPlaceholder: string; sortFieldOptions: SortFieldOption[]; defaultSortField: 'name' | 'count'; columns: Column[]; emptyMessage: string; skeletonHeaders?: string[]; columnPickerStorageKey?: string; useMockPlaceholder?: boolean; mockPlaceholderRows?: WpTerm[]; mockBannerMessage?: string; emptyStateTitle?: string; emptyStateDescription?: string; emptyStateAction?: ReactNode; }; /** * Term collection list (same chrome as course list, without post status pills). */ export function TermEntityListView({ taxonomyRestBase, listRefreshNonce = 0, contextHint, searchPlaceholder, sortFieldOptions, defaultSortField, columns, emptyMessage, skeletonHeaders: skeletonHeadersProp, columnPickerStorageKey, useMockPlaceholder = false, mockPlaceholderRows = [], mockBannerMessage = 'Sample data preview — no terms returned yet.', emptyStateTitle, emptyStateDescription, emptyStateAction, }: Props) { const [search, setSearch] = useState(''); const debouncedSearch = useDebouncedValue(search, 320); const [page, setPage] = useState(1); const [orderby, setOrderby] = useState<'name' | 'count'>(defaultSortField); const [order, setOrder] = useState<'asc' | 'desc'>('asc'); const [bulkValue, setBulkValue] = useState(''); const noopBulkApply = useCallback(() => {}, []); useEffect(() => { setPage(1); }, [debouncedSearch, orderby, order, taxonomyRestBase, listRefreshNonce]); const pickable = useMemo(() => columns.filter((c) => !c.alwaysVisible), [columns]); const [colVis, setColVis] = useState>({}); useEffect(() => { if (!columnPickerStorageKey || pickable.length === 0) { setColVis({}); return; } const key = columnVisibilityStorageKey(columnPickerStorageKey); setColVis( loadColumnVisibility( key, pickable.map((c) => ({ id: c.id, defaultHidden: c.defaultHidden })) ) ); }, [columnPickerStorageKey, pickable]); const onColumnToggle = useCallback( (id: string, next: boolean) => { if (!columnPickerStorageKey) { return; } const key = columnVisibilityStorageKey(columnPickerStorageKey); setColVis((prev) => { const merged = { ...prev, [id]: next }; saveColumnVisibility(key, merged); return merged; }); }, [columnPickerStorageKey] ); const pickerVisibility = useMemo(() => { const o: Record = {}; for (const c of pickable) { const v = colVis[c.id]; o[c.id] = v === undefined ? !c.defaultHidden : v; } return o; }, [pickable, colVis]); const visibleColumns = useMemo(() => { if (!columnPickerStorageKey) { return columns; } return columns.filter((c) => { if (c.alwaysVisible) { return true; } const v = colVis[c.id]; if (v === undefined) { return !c.defaultHidden; } return v; }); }, [columns, columnPickerStorageKey, colVis]); const skeletonHeaders = useMemo(() => { if (skeletonHeadersProp?.length) { return skeletonHeadersProp; } return columns.map((c) => resolveColumnPickerLabel(c) || '\u00a0'); }, [columns, skeletonHeadersProp]); const listQuery = useWpTermCollection({ taxonomyRestBase, search: debouncedSearch, orderby, order, page, perPage: DEFAULT_LIST_PER_PAGE, refreshNonce: listRefreshNonce, }); const apiRows = Array.isArray(listQuery.data?.data) ? listQuery.data.data : []; const showMockRows = useMockPlaceholder && mockPlaceholderRows.length > 0 && !listQuery.loading && !listQuery.error && apiRows.length === 0 && debouncedSearch.trim() === ''; const rows = showMockRows ? mockPlaceholderRows : apiRows; const totalLine = useMemo(() => { if (showMockRows) { return `Showing sample rows (${rows.length})`; } const t = listQuery.data?.total; if (t == null) { return null; } return `Showing ${rows.length} of ${t}`; }, [listQuery.data?.total, rows.length, showMockRows]); const onSortOrderToggle = () => setOrder((o) => (o === 'asc' ? 'desc' : 'asc')); const onSortColumn = useCallback( (key: string) => { if (key === orderby) { setOrder((o) => (o === 'asc' ? 'desc' : 'asc')); } else { setOrderby(key as 'name' | 'count'); setOrder('asc'); } }, [orderby] ); const columnPicker = columnPickerStorageKey && pickable.length > 0 ? ( ({ id: c.id, label: resolveColumnPickerLabel(c) }))} visibility={pickerVisibility} onChange={onColumnToggle} /> ) : null; const emptyContent = ( ); return ( setOrderby(v as 'name' | 'count')} sortOrder={order} onSortOrderToggle={onSortOrderToggle} trailing={columnPicker} />

{contextHint}

{showMockRows ? (
{mockBannerMessage}
) : null} {totalLine ? (
{totalLine}
) : null} {listQuery.error ? (
) : listQuery.loading ? ( ) : ( <> {!showMockRows ? ( ) : null} r.id} emptyContent={emptyContent} wrapInCard={false} sortState={{ orderby, order }} onSortColumn={onSortColumn} /> {!showMockRows ? ( ) : null} )}
); }