"use client" /** * Library — thin wrapper around the centralized ``. Owns column defs, * folder/panel/tree-panel custom views, the new-folder + customize-folder sheet, and * forwards URL search via `HubTable.syncedSearchFromUrl`. * * Single dataset rule: `HubTable` runs one `useTableState(tableSourceItems, columns, …)`. * Every non-table renderer (list, board, folder, panel, tree-panel, dashboard) reads * `state.rows` — the same filtered/sorted/searched bag as the grid. */ import * as React from "react" import { mailtoHref } from "@/lib/mailto" import type { DataListViewType } from "@/lib/data-list-view" import type { ColumnDef } from "@/components/data-table/types" import { HubTable, type HubTableHandle, type HubTableRenderers, type BulkAction, type CreatedViewSpec, } from "@/components/data-views" import { Skeleton } from "@/components/ui/skeleton" import { LIBRARY_SUPPORTED_VIEWS } from "@/lib/library-supported-views" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Tip } from "@/components/ui/tip" import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome" import { LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS, LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS, LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS, } from "@/components/data-views/list-page-split-hub-tokens" import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header" import { LibraryBoardView, LIBRARY_BOARD_GROUP_OPTIONS } from "@/components/library-board-view" import { ListPageBoardCard } from "@/components/data-views/list-page-board-card" import { LibraryFavoriteButton, LIBRARY_FAVORITE_HOVER_GROUP, } from "@/components/library-favorite-button" import { LibraryOsFolderView } from "@/components/library-os-folder-view" import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet" import { FolderDetailsShell } from "@/components/folder-details-shell" import { HubTreePanelView } from "@/components/hub-tree-panel-view" import { AvatarInitials } from "@/components/ui/avatar" import { cn } from "@/lib/utils" import { formatDateUS } from "@/lib/date-filter" import { initialsFromDisplayName } from "@/lib/initials-from-name" import { newLibraryQuestionId, type LibraryLevel, type LibraryItem, type LibraryItemType, } from "@/lib/mock/library" import { type LibraryFolder, LIBRARY_FOLDER_COLOR_STYLES, LIBRARY_FOLDER_ICON_COLORS, } from "@/lib/mock/library-folders" import { toggleLibraryItemFavorite, applyLibraryHubDisplayFilters, type LibraryLandingFilterState, type LibraryNavState, } from "@/lib/library-nav" // ─── Dynamic dashboard charts section ──────────────────────────────────────── const LibraryDashboardChartsSectionLazy = React.lazy(() => import("@/components/library-dashboard-charts").then(mod => ({ default: mod.LibraryDashboardChartsSection, })), ) function LibraryDashboardChartsSectionFallback() { return (
) } function LibraryDashboardChartsSection( props: React.ComponentProps, ) { return ( }> ) } // ─── Constants ─────────────────────────────────────────────────────────────── const TYPE_LABEL: Record = { multiple_choice: "Type 1", true_false: "Type 2", short_answer: "Type 3", } const DIFFICULTY_LABEL: Record = { easy: "Low", medium: "Normal", hard: "High", } const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as LibraryItemType[]).map(k => ({ value: k, label: TYPE_LABEL[k], })) const DIFFICULTY_FILTER_OPTS = (Object.keys(DIFFICULTY_LABEL) as LibraryLevel[]).map(k => ({ value: k, label: DIFFICULTY_LABEL[k], })) function newLibraryItemId() { return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}` } function defaultFolderIdForColumnParent(parentId: string | null, folders: LibraryFolder[]): string | null { if (parentId !== null) return parentId const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name)) return roots[0]?.id ?? null } function uniqueTopics(items: LibraryItem[]) { return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t })) } function buildLibraryColumns( items: LibraryItem[], opts: { onToggleFavorite: (row: LibraryItem) => void }, ): ColumnDef[] { const topicOpts = uniqueTopics(items) const { onToggleFavorite } = opts return [ { key: "select", label: "", width: 40, minWidth: 40, defaultPin: "left", lockPin: true }, { key: "stem", label: "Question", width: 300, minWidth: 160, sortable: true, sortKey: "stem", defaultPin: "left", filter: { type: "text", icon: "fa-file-lines", operators: ["contains", "not_contains"] }, cell: row => (
{row.stem} {row.questionId}
), }, { key: "topic", label: "Topic", width: 160, minWidth: 120, sortable: true, sortKey: "topic", filter: { type: "select", icon: "fa-layer-group", operators: ["is", "is_not"], options: topicOpts }, cell: row => {row.topic}, }, { key: "type", label: "Type", width: 140, minWidth: 120, sortable: true, sortKey: "type", filter: { type: "select", icon: "fa-list-check", operators: ["is", "is_not"], options: TYPE_FILTER_OPTS }, cell: row => {TYPE_LABEL[row.type]}, }, { key: "difficulty", label: "Difficulty", width: 110, minWidth: 96, sortable: true, sortKey: "difficulty", filter: { type: "select", icon: "fa-signal", operators: ["is", "is_not"], options: DIFFICULTY_FILTER_OPTS }, cell: row => {DIFFICULTY_LABEL[row.difficulty]}, }, { key: "updatedAt", label: "Updated", width: 120, minWidth: 100, sortable: true, sortKey: "updatedAt", filter: { type: "date", icon: "fa-calendar-days", operators: ["is", "is_not"] }, cell: row => ( {formatDateUS(row.updatedAt)} ), }, { key: "author", label: "Author", width: 260, minWidth: 200, sortable: true, sortKey: "author", filter: { type: "text", icon: "fa-user", operators: ["contains", "not_contains"] }, cell: row => { const initials = initialsFromDisplayName(row.author) return (
{row.author} {row.authorEmail ? ( e.stopPropagation()} > {row.authorEmail} ) : null}
) }, }, { key: "actions", label: "", width: 48, minWidth: 48, defaultPin: "right", lockPin: true, cell: row => (