@exxatdesignux/ui 0.2.17 → 0.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/consumer-extras/AGENTS.md +76 -0
- package/consumer-extras/README.md +5 -1
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
- package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +22 -7
- package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
- package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
- package/consumer-extras/patterns/data-views-pattern.md +42 -3
- package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
- package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
- package/package.json +2 -1
- package/src/components/ui/button-group.tsx +81 -0
- package/src/components/ui/button.tsx +4 -4
- package/src/components/ui/sidebar.tsx +2 -2
- package/src/globals.css +7 -1807
- package/src/theme.css +10 -1126
- package/src/tokens/README.md +15 -0
- package/src/tokens/base.css +337 -0
- package/src/tokens/high-contrast.css +1195 -0
- package/src/tokens/layers.css +224 -0
- package/src/tokens/tailwind-bridge.css +118 -0
- package/src/tokens/themes.css +201 -0
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
- package/template/AGENTS.md +66 -21
- package/template/app/(app)/dashboard/loading.tsx +3 -15
- package/template/app/(app)/dashboard/page.tsx +2 -14
- package/template/app/(app)/data-list/layout.tsx +43 -0
- package/template/app/(app)/data-list/page.tsx +2 -2
- package/template/app/(app)/error.tsx +22 -6
- package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
- package/template/app/(app)/examples/page.tsx +1 -0
- package/template/app/(app)/layout.tsx +13 -6
- package/template/app/(app)/loading.tsx +1 -18
- package/template/app/(app)/question-bank/find/page.tsx +2 -1
- package/template/app/(app)/question-bank/library/page.tsx +2 -1
- package/template/app/(app)/question-bank/list/page.tsx +2 -1
- package/template/app/(app)/question-bank/new/page.tsx +15 -23
- package/template/app/(app)/question-bank/page.tsx +2 -1
- package/template/app/(app)/settings/page.tsx +4 -5
- package/template/app/global-error.tsx +63 -0
- package/template/app/globals.css +7 -1934
- package/template/app/layout.tsx +2 -0
- package/template/components/app-route-loading.tsx +14 -0
- package/template/components/app-sidebar.tsx +71 -55
- package/template/components/data-table/index.tsx +31 -67
- package/template/components/data-table/use-table-state.ts +33 -6
- package/template/components/data-views/index.ts +37 -9
- package/template/components/data-views/list-page-calendar-view.tsx +593 -0
- package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
- package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
- package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
- package/template/components/dev-chunk-load-recovery.tsx +41 -0
- package/template/components/examples/focused-workflow-showcase.tsx +183 -0
- package/template/components/exxat-product-logo.tsx +2 -6
- package/template/components/key-metrics.tsx +54 -22
- package/template/components/list-hub-board-view.tsx +68 -0
- package/template/components/list-hub-client.tsx +186 -0
- package/template/components/list-hub-list-view.tsx +36 -0
- package/template/components/list-hub-panel-activator.tsx +8 -0
- package/template/components/list-hub-secondary-nav.tsx +121 -0
- package/template/components/list-hub-table.tsx +336 -0
- package/template/components/new-question-composer.tsx +6 -24
- package/template/components/product-switcher.tsx +5 -5
- package/template/components/product-wordmark.tsx +4 -7
- package/template/components/question-bank-client.tsx +4 -1
- package/template/components/question-bank-folder-columns-panel.tsx +104 -0
- package/template/components/question-bank-hub-client.tsx +2 -5
- package/template/components/question-bank-table.tsx +155 -509
- package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
- package/template/components/secondary-panel.tsx +4 -44
- package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
- package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
- package/template/components/secondary-panels/registry.tsx +15 -0
- package/template/components/settings-appearance-card.tsx +3 -2
- package/template/components/settings-client.tsx +59 -15
- package/template/components/settings-form-row.tsx +9 -4
- package/template/components/sidebar-shell.tsx +2 -1
- package/template/components/table-properties/drawer-button.tsx +51 -20
- package/template/components/table-properties/drawer.tsx +81 -17
- package/template/components/templates/focused-workflow-layouts.tsx +448 -0
- package/template/components/templates/focused-workflow-page-template.tsx +69 -0
- package/template/components/templates/list-page.tsx +40 -13
- package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
- package/template/components/templates/page-loading-shell.tsx +262 -0
- package/template/components/ui/button-group.tsx +1 -0
- package/template/contexts/product-context.tsx +21 -2
- package/template/docs/consumer-app-pattern.md +39 -0
- package/template/docs/data-views-pattern.md +42 -3
- package/template/docs/drawer-vs-dialog-pattern.md +3 -1
- package/template/docs/focused-workflow-page-pattern.md +84 -0
- package/template/docs/kpi-flat-band-pattern.md +57 -0
- package/template/docs/kpi-strip-max-four-pattern.md +1 -0
- package/template/docs/shell-surface-elevation-pattern.md +54 -0
- package/template/lib/chunk-load-error.ts +13 -0
- package/template/lib/command-menu-search-data.ts +11 -27
- package/template/lib/conditional-rule-match.ts +87 -22
- package/template/lib/data-list-display-options.ts +16 -2
- package/template/lib/data-list-view-registry.ts +104 -0
- package/template/lib/data-list-view-surface.ts +15 -1
- package/template/lib/data-list-view.ts +16 -1
- package/template/lib/data-view-dashboard-storage.ts +38 -35
- package/template/lib/hub-connected-view-renderers.ts +58 -0
- package/template/lib/list-hub-nav.ts +121 -0
- package/template/lib/list-hub-supported-views.ts +10 -0
- package/template/lib/list-page-table-properties.ts +3 -7
- package/template/lib/list-status-badges.ts +4 -97
- package/template/lib/mock/list-hub-directory.ts +27 -0
- package/template/lib/mock/list-hub-kpi.ts +27 -0
- package/template/lib/mock/navigation.tsx +1 -0
- package/template/lib/page-loading-variant.ts +40 -0
- package/template/lib/question-bank-supported-views.ts +13 -0
- package/template/lib/sidebar-state-cookie.ts +9 -0
- package/template/lib/table-state-lifecycle.ts +60 -13
- package/template/app/(app)/data-list/[id]/page.tsx +0 -44
- package/template/app/(app)/data-list/new/page.tsx +0 -34
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-list-view.tsx +0 -54
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -632
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -1068
- package/template/components/placement-board-card.tsx +0 -262
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -404
- package/template/components/placements-client.tsx +0 -252
- package/template/components/placements-list-view.tsx +0 -171
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -640
- package/template/components/placements-table.tsx +0 -1675
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-all-client.tsx +0 -154
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-list-view.tsx +0 -42
- package/template/components/sites-table.tsx +0 -402
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-list-view.tsx +0 -59
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -714
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -183
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/placement-lifecycle.ts +0 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Question bank — DataTable + TablePropertiesDrawer +
|
|
4
|
+
* Question bank — DataTable + TablePropertiesDrawer + connected views via `ListPageConnectedViewBody`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as React from "react"
|
|
@@ -10,9 +10,9 @@ import { mailtoHref } from "@/lib/mailto"
|
|
|
10
10
|
import { DataTable, DataTableToolbar } from "@/components/data-table"
|
|
11
11
|
import type { DataListViewType } from "@/lib/data-list-view"
|
|
12
12
|
import type { OpenTablePropertiesHandle } from "@/lib/list-page-table-properties"
|
|
13
|
+
import { QUESTION_BANK_SUPPORTED_VIEWS } from "@/lib/question-bank-supported-views"
|
|
13
14
|
import type { ColumnDef } from "@/components/data-table/types"
|
|
14
15
|
import { useTableState } from "@/components/data-table/use-table-state"
|
|
15
|
-
import { useTableStateLifecycle } from "@/lib/table-state-lifecycle"
|
|
16
16
|
import { TablePropertiesDrawerButton } from "@/components/table-properties"
|
|
17
17
|
import type { ConditionalRule, FilterFieldDef, FilterOperator } from "@/components/table-properties/types"
|
|
18
18
|
import { Button } from "@/components/ui/button"
|
|
@@ -24,23 +24,11 @@ import {
|
|
|
24
24
|
} from "@/components/ui/dropdown-menu"
|
|
25
25
|
import { Tip } from "@/components/ui/tip"
|
|
26
26
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
ResizablePanel,
|
|
30
|
-
ResizablePanelGroup,
|
|
31
|
-
} from "@/components/ui/resizable"
|
|
32
|
-
import {
|
|
33
|
-
Tooltip,
|
|
34
|
-
TooltipContent,
|
|
35
|
-
TooltipTrigger,
|
|
36
|
-
} from "@/components/ui/tooltip"
|
|
27
|
+
import { ListPageConnectedViewBody } from "@/components/data-views/list-page-connected-view-body"
|
|
28
|
+
import { ListPageCalendarView } from "@/components/data-views/list-page-calendar-view"
|
|
37
29
|
import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome"
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS,
|
|
41
|
-
LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS,
|
|
42
|
-
} from "@/components/data-views/list-page-split-hub-tokens"
|
|
43
|
-
import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
|
|
30
|
+
import { defineHubViewRenderers } from "@/lib/hub-connected-view-renderers"
|
|
31
|
+
import { QuestionBankFolderColumnsPanel } from "@/components/question-bank-folder-columns-panel"
|
|
44
32
|
import { QuestionBankBoardView, QUESTION_BANK_BOARD_GROUP_OPTIONS } from "@/components/question-bank-board-view"
|
|
45
33
|
import { QuestionBankListView } from "@/components/question-bank-list-view"
|
|
46
34
|
import {
|
|
@@ -61,7 +49,7 @@ import {
|
|
|
61
49
|
type QuestionBankItem,
|
|
62
50
|
type QuestionBankType,
|
|
63
51
|
} from "@/lib/mock/question-bank"
|
|
64
|
-
import {
|
|
52
|
+
import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
|
|
65
53
|
import {
|
|
66
54
|
toggleQuestionBankItemFavorite,
|
|
67
55
|
applyQuestionBankHubDisplayFilters,
|
|
@@ -329,312 +317,6 @@ function buildQuestionBankColumns(
|
|
|
329
317
|
return cols
|
|
330
318
|
}
|
|
331
319
|
|
|
332
|
-
interface HubFolderColumnsPanelProps {
|
|
333
|
-
folders: QuestionBankFolder[]
|
|
334
|
-
rows: QuestionBankItem[]
|
|
335
|
-
panelRenderDetail: (row: QuestionBankItem) => React.ReactNode
|
|
336
|
-
onAddFolder: (parentId: string | null) => void
|
|
337
|
-
onAddQuestion: (parentId: string | null) => void
|
|
338
|
-
onCustomizeFolder?: (folder: QuestionBankFolder) => void
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
type HierarchyItem = QuestionBankFolder | QuestionBankItem
|
|
342
|
-
|
|
343
|
-
function isFolder(item: HierarchyItem): item is QuestionBankFolder {
|
|
344
|
-
return 'parentId' in item
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function isQuestion(item: HierarchyItem): item is QuestionBankItem {
|
|
348
|
-
return 'stem' in item
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/** **Panel view** — multi-column folder explorer + optional detail column (Finder-style). */
|
|
352
|
-
function HubFolderColumnsPanel({
|
|
353
|
-
folders,
|
|
354
|
-
rows,
|
|
355
|
-
panelRenderDetail,
|
|
356
|
-
onAddFolder,
|
|
357
|
-
onAddQuestion,
|
|
358
|
-
onCustomizeFolder,
|
|
359
|
-
}: HubFolderColumnsPanelProps) {
|
|
360
|
-
// Track the selected path through the hierarchy
|
|
361
|
-
// Initialize with first folder selected by default
|
|
362
|
-
const [selectedPath, setSelectedPath] = React.useState<HierarchyItem[]>(() => {
|
|
363
|
-
const rootFolders = folders
|
|
364
|
-
.filter(f => f.parentId === null)
|
|
365
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
366
|
-
if (rootFolders.length > 0) {
|
|
367
|
-
return [rootFolders[0]]
|
|
368
|
-
}
|
|
369
|
-
return []
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
// Track if this is the first render for auto-selection
|
|
373
|
-
const isFirstRenderRef = React.useRef(true)
|
|
374
|
-
|
|
375
|
-
// Get root items (top-level folders)
|
|
376
|
-
const rootFolders = React.useMemo(() => {
|
|
377
|
-
return folders
|
|
378
|
-
.filter(f => f.parentId === null)
|
|
379
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
380
|
-
}, [folders])
|
|
381
|
-
|
|
382
|
-
// Handle selection at any depth
|
|
383
|
-
const handleSelect = (item: HierarchyItem, depth: number) => {
|
|
384
|
-
setSelectedPath(prev => [...prev.slice(0, depth), item])
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Auto-select first item at each level (only on first render). Intentional
|
|
388
|
-
// empty deps: we want this to run exactly once on mount; depending on the
|
|
389
|
-
// referenced values (folders / rows / selectedPath) would re-run on every
|
|
390
|
-
// edit and keep re-seeding the selection, undoing the user's choice.
|
|
391
|
-
React.useEffect(() => {
|
|
392
|
-
if (isFirstRenderRef.current && selectedPath.length > 0) {
|
|
393
|
-
const lastItem = selectedPath[selectedPath.length - 1]
|
|
394
|
-
if (isFolder(lastItem)) {
|
|
395
|
-
const folder = lastItem as QuestionBankFolder
|
|
396
|
-
const subfolders = folders.filter(f => f.parentId === folder.id).sort((a, b) => a.name.localeCompare(b.name))
|
|
397
|
-
const questionsInFolder = rows.filter(r => r.folderId === folder.id)
|
|
398
|
-
const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
|
|
399
|
-
|
|
400
|
-
if (items.length > 0 && !selectedPath[selectedPath.length + 1]) {
|
|
401
|
-
setSelectedPath(prev => [...prev, items[0]])
|
|
402
|
-
isFirstRenderRef.current = false
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
407
|
-
}, [])
|
|
408
|
-
|
|
409
|
-
// Build columns dynamically based on selected path
|
|
410
|
-
const columns: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = React.useMemo(() => {
|
|
411
|
-
const cols: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = [
|
|
412
|
-
{ items: rootFolders, depth: 0, parentId: null },
|
|
413
|
-
]
|
|
414
|
-
|
|
415
|
-
// For each selected folder in the path, add a column with its children
|
|
416
|
-
for (let i = 0; i < selectedPath.length; i++) {
|
|
417
|
-
const item = selectedPath[i]
|
|
418
|
-
if (isFolder(item)) {
|
|
419
|
-
// Get subfolders
|
|
420
|
-
const subfolders = folders
|
|
421
|
-
.filter(f => f.parentId === item.id)
|
|
422
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
423
|
-
|
|
424
|
-
// Get questions in this folder
|
|
425
|
-
const questionsInFolder = rows.filter(r => r.folderId === item.id)
|
|
426
|
-
|
|
427
|
-
// Combine folders and questions
|
|
428
|
-
const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
|
|
429
|
-
|
|
430
|
-
if (items.length > 0) {
|
|
431
|
-
cols.push({ items, depth: i + 1, parentId: item.id })
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return cols
|
|
437
|
-
}, [selectedPath, rootFolders, folders, rows])
|
|
438
|
-
|
|
439
|
-
const selectedLeaf = selectedPath.length > 0 ? selectedPath.at(-1)! : null
|
|
440
|
-
const selectedQuestion =
|
|
441
|
-
selectedLeaf && isQuestion(selectedLeaf) ? (selectedLeaf as QuestionBankItem) : null
|
|
442
|
-
const selectedFolderLeaf =
|
|
443
|
-
selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as QuestionBankFolder) : null
|
|
444
|
-
|
|
445
|
-
return (
|
|
446
|
-
<ResizablePanelGroup
|
|
447
|
-
direction="horizontal"
|
|
448
|
-
className="flex h-full min-h-0 w-full flex-1 overflow-hidden"
|
|
449
|
-
>
|
|
450
|
-
{/* Render all columns with handles between them */}
|
|
451
|
-
{columns.map(({ items, depth, parentId }, columnIdx) => (
|
|
452
|
-
<React.Fragment key={`col-${depth}`}>
|
|
453
|
-
{columnIdx > 0 && (
|
|
454
|
-
<ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
|
|
455
|
-
)}
|
|
456
|
-
<ResizablePanel
|
|
457
|
-
id={`col-${depth}`}
|
|
458
|
-
defaultSize={columnIdx === 0 ? 35 : columnIdx === 1 ? 35 : 30}
|
|
459
|
-
minSize={15}
|
|
460
|
-
className={LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS}
|
|
461
|
-
>
|
|
462
|
-
<ListPageTreeColumnHeader
|
|
463
|
-
title={
|
|
464
|
-
depth === 0
|
|
465
|
-
? "Categories"
|
|
466
|
-
: selectedPath[depth - 1] && isFolder(selectedPath[depth - 1])
|
|
467
|
-
? (selectedPath[depth - 1] as QuestionBankFolder).name
|
|
468
|
-
: "Items"
|
|
469
|
-
}
|
|
470
|
-
trailing={
|
|
471
|
-
<>
|
|
472
|
-
<span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums">
|
|
473
|
-
{items.length}
|
|
474
|
-
</span>
|
|
475
|
-
{depth < columns.length - 1 && items.length > 0 ? (
|
|
476
|
-
<div className="flex shrink-0 items-center gap-0.5">
|
|
477
|
-
<Tooltip>
|
|
478
|
-
<TooltipTrigger asChild>
|
|
479
|
-
<Button
|
|
480
|
-
size="icon-sm"
|
|
481
|
-
variant="ghost"
|
|
482
|
-
onClick={() => onAddFolder(parentId ?? null)}
|
|
483
|
-
aria-label="Add folder"
|
|
484
|
-
>
|
|
485
|
-
<i className="fa-light fa-folder-plus text-xs" aria-hidden="true" />
|
|
486
|
-
</Button>
|
|
487
|
-
</TooltipTrigger>
|
|
488
|
-
<TooltipContent side="top" sideOffset={4}>
|
|
489
|
-
Add folder
|
|
490
|
-
</TooltipContent>
|
|
491
|
-
</Tooltip>
|
|
492
|
-
<Tooltip>
|
|
493
|
-
<TooltipTrigger asChild>
|
|
494
|
-
<Button
|
|
495
|
-
size="icon-sm"
|
|
496
|
-
variant="ghost"
|
|
497
|
-
onClick={() => onAddQuestion(parentId ?? null)}
|
|
498
|
-
aria-label="Add question"
|
|
499
|
-
>
|
|
500
|
-
<i className="fa-light fa-plus text-xs" aria-hidden="true" />
|
|
501
|
-
</Button>
|
|
502
|
-
</TooltipTrigger>
|
|
503
|
-
<TooltipContent side="top" sideOffset={4}>
|
|
504
|
-
Add question
|
|
505
|
-
</TooltipContent>
|
|
506
|
-
</Tooltip>
|
|
507
|
-
</div>
|
|
508
|
-
) : null}
|
|
509
|
-
</>
|
|
510
|
-
}
|
|
511
|
-
/>
|
|
512
|
-
|
|
513
|
-
{/* Scrollable Items List */}
|
|
514
|
-
<div className="min-h-0 flex-1 overflow-y-auto py-1">
|
|
515
|
-
{items.map(item => {
|
|
516
|
-
const isSelected = selectedPath[depth]?.id === item.id
|
|
517
|
-
const isFolder_ = isFolder(item)
|
|
518
|
-
const folder = isFolder_ ? item : null
|
|
519
|
-
const question = isQuestion(item) ? item : null
|
|
520
|
-
|
|
521
|
-
// Get count for folders
|
|
522
|
-
const subfolderCount = isFolder_
|
|
523
|
-
? folders.filter(f => f.parentId === item.id).length
|
|
524
|
-
: 0
|
|
525
|
-
const questionCount = isFolder_
|
|
526
|
-
? rows.filter(r => r.folderId === item.id).length
|
|
527
|
-
: 0
|
|
528
|
-
const itemCount = subfolderCount + questionCount
|
|
529
|
-
|
|
530
|
-
return (
|
|
531
|
-
<div
|
|
532
|
-
key={item.id}
|
|
533
|
-
className="group flex items-center hover:bg-muted/50"
|
|
534
|
-
>
|
|
535
|
-
<button
|
|
536
|
-
onClick={() => handleSelect(item, depth)}
|
|
537
|
-
className={cn(
|
|
538
|
-
"flex flex-1 items-center gap-3 px-3 py-2 text-left text-sm transition-colors duration-75",
|
|
539
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
540
|
-
isSelected
|
|
541
|
-
? "bg-accent text-accent-foreground"
|
|
542
|
-
: "text-foreground",
|
|
543
|
-
// Apply folder background color if it's a folder and not selected, but NOT in the first column
|
|
544
|
-
isFolder_ && !isSelected && folder?.colorKey && depth > 0
|
|
545
|
-
? QUESTION_BANK_FOLDER_COLOR_STYLES[folder.colorKey]?.tile
|
|
546
|
-
: "",
|
|
547
|
-
)}
|
|
548
|
-
aria-selected={isSelected}
|
|
549
|
-
role="option"
|
|
550
|
-
>
|
|
551
|
-
{/* Icon - show for folders and questions */}
|
|
552
|
-
{isFolder_ ? (
|
|
553
|
-
<i className={cn(
|
|
554
|
-
"fa-folder text-sm shrink-0",
|
|
555
|
-
isSelected ? "fa-solid" : "fa-light",
|
|
556
|
-
// Apply folder color from customization (for both selected and unselected)
|
|
557
|
-
folder?.colorKey && QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey]
|
|
558
|
-
)} aria-hidden="true" />
|
|
559
|
-
) : (
|
|
560
|
-
<i className={cn("fa-file text-sm shrink-0", isSelected ? "fa-solid" : "fa-light")} aria-hidden="true" />
|
|
561
|
-
)}
|
|
562
|
-
|
|
563
|
-
{/* Name */}
|
|
564
|
-
<span className={cn(
|
|
565
|
-
"min-w-0 flex-1 truncate leading-tight",
|
|
566
|
-
isSelected && "font-medium"
|
|
567
|
-
)}>
|
|
568
|
-
{isFolder_ ? folder?.name : question?.stem}
|
|
569
|
-
</span>
|
|
570
|
-
|
|
571
|
-
{/* Count or metadata */}
|
|
572
|
-
<span className={cn(
|
|
573
|
-
"shrink-0 tabular-nums text-xs ml-auto",
|
|
574
|
-
isSelected ? "text-accent-foreground/70" : "text-muted-foreground",
|
|
575
|
-
)}>
|
|
576
|
-
{isFolder_ ? itemCount : (question?.type === 'multiple_choice' ? 'MCQ' : question?.difficulty?.charAt(0).toUpperCase())}
|
|
577
|
-
</span>
|
|
578
|
-
</button>
|
|
579
|
-
|
|
580
|
-
{/* Folder actions menu - only for folders */}
|
|
581
|
-
{isFolder_ && folder && (
|
|
582
|
-
<DropdownMenu>
|
|
583
|
-
<DropdownMenuTrigger asChild>
|
|
584
|
-
<Button
|
|
585
|
-
type="button"
|
|
586
|
-
size="icon-xs"
|
|
587
|
-
variant="ghost"
|
|
588
|
-
aria-label={`Actions for folder ${folder.name}`}
|
|
589
|
-
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
590
|
-
>
|
|
591
|
-
<i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
|
|
592
|
-
</Button>
|
|
593
|
-
</DropdownMenuTrigger>
|
|
594
|
-
<DropdownMenuContent align="end">
|
|
595
|
-
<DropdownMenuItem onSelect={() => onCustomizeFolder?.(folder)}>
|
|
596
|
-
<i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
|
|
597
|
-
Customize
|
|
598
|
-
</DropdownMenuItem>
|
|
599
|
-
</DropdownMenuContent>
|
|
600
|
-
</DropdownMenu>
|
|
601
|
-
)}
|
|
602
|
-
</div>
|
|
603
|
-
)
|
|
604
|
-
})}
|
|
605
|
-
</div>
|
|
606
|
-
</ResizablePanel>
|
|
607
|
-
</React.Fragment>
|
|
608
|
-
))}
|
|
609
|
-
|
|
610
|
-
{/* Details panel — question (summary) or folder (aggregates) */}
|
|
611
|
-
{(selectedQuestion || selectedFolderLeaf) && (
|
|
612
|
-
<>
|
|
613
|
-
<ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
|
|
614
|
-
<ResizablePanel id="col-detail" defaultSize={30} minSize={20} className={LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS}>
|
|
615
|
-
{selectedQuestion ? (
|
|
616
|
-
<>
|
|
617
|
-
<ListPageTreeColumnHeader title="Details" className="px-4" />
|
|
618
|
-
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
|
|
619
|
-
{panelRenderDetail(selectedQuestion)}
|
|
620
|
-
</div>
|
|
621
|
-
</>
|
|
622
|
-
) : selectedFolderLeaf ? (
|
|
623
|
-
<div className="min-h-0 flex-1 overflow-hidden">
|
|
624
|
-
<FolderDetailsShell
|
|
625
|
-
folder={selectedFolderLeaf}
|
|
626
|
-
folders={folders}
|
|
627
|
-
questions={rows}
|
|
628
|
-
/>
|
|
629
|
-
</div>
|
|
630
|
-
) : null}
|
|
631
|
-
</ResizablePanel>
|
|
632
|
-
</>
|
|
633
|
-
)}
|
|
634
|
-
</ResizablePanelGroup>
|
|
635
|
-
)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
320
|
export type QuestionBankTableHandle = OpenTablePropertiesHandle
|
|
639
321
|
|
|
640
322
|
export const QuestionBankTable = React.forwardRef<
|
|
@@ -651,12 +333,26 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
651
333
|
landingFilters?: QuestionBankLandingFilterState | null
|
|
652
334
|
view?: DataListViewType
|
|
653
335
|
onViewChange?: (v: DataListViewType) => void
|
|
336
|
+
/** Aligns Properties view tiles with `ListPageTemplate` `supportedViewTypes`. */
|
|
337
|
+
supportedViewTypes?: readonly DataListViewType[]
|
|
654
338
|
folders: QuestionBankFolder[]
|
|
655
339
|
onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
|
|
656
340
|
onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
|
|
657
341
|
}
|
|
658
342
|
>(function QuestionBankTable(
|
|
659
|
-
{
|
|
343
|
+
{
|
|
344
|
+
items,
|
|
345
|
+
navState,
|
|
346
|
+
urlListSearch,
|
|
347
|
+
searchLanding,
|
|
348
|
+
landingFilters,
|
|
349
|
+
view = "table",
|
|
350
|
+
onViewChange,
|
|
351
|
+
supportedViewTypes = QUESTION_BANK_SUPPORTED_VIEWS,
|
|
352
|
+
folders,
|
|
353
|
+
onFoldersChange,
|
|
354
|
+
onItemsChange,
|
|
355
|
+
},
|
|
660
356
|
ref,
|
|
661
357
|
) {
|
|
662
358
|
const tableSourceItems = React.useMemo(() => {
|
|
@@ -718,28 +414,6 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
718
414
|
searchLanding ? undefined : urlListSearch,
|
|
719
415
|
)
|
|
720
416
|
|
|
721
|
-
// Persist this hub's table lifecycle (sort / search / filters / column
|
|
722
|
-
// visibility / etc.) to localStorage. See `lib/table-state-lifecycle`.
|
|
723
|
-
// NOTE: tabId is `"main"` here — the question-bank folder scope is
|
|
724
|
-
// already URL-driven (`?scope=`, `?folderId=`), so we only persist
|
|
725
|
-
// table chrome, not navigation.
|
|
726
|
-
const lifecycleColumnKeys = React.useMemo(
|
|
727
|
-
() => new Set(columns.map(c => c.key)),
|
|
728
|
-
[columns],
|
|
729
|
-
)
|
|
730
|
-
useTableStateLifecycle({
|
|
731
|
-
namespace: "question-bank",
|
|
732
|
-
tabId: "main",
|
|
733
|
-
tableState,
|
|
734
|
-
columnKeys: lifecycleColumnKeys,
|
|
735
|
-
extras: { conditionalRules },
|
|
736
|
-
onLoadExtras: e => {
|
|
737
|
-
if (e && Array.isArray(e.conditionalRules)) {
|
|
738
|
-
setConditionalRules(e.conditionalRules as ConditionalRule[])
|
|
739
|
-
}
|
|
740
|
-
},
|
|
741
|
-
})
|
|
742
|
-
|
|
743
417
|
const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
|
|
744
418
|
setNewFolderParentId(parentId)
|
|
745
419
|
setCustomizingFolder(null)
|
|
@@ -861,6 +535,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
861
535
|
onUpdateConditionalRule: updateConditionalRule,
|
|
862
536
|
currentView: view,
|
|
863
537
|
onViewChange,
|
|
538
|
+
supportedViewTypes,
|
|
864
539
|
lifecycleTabLabel: "Question bank",
|
|
865
540
|
boardGroupByColumnOptions: [...QUESTION_BANK_BOARD_GROUP_OPTIONS],
|
|
866
541
|
renderFilterOptionValue,
|
|
@@ -911,172 +586,143 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
911
586
|
/>
|
|
912
587
|
)
|
|
913
588
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
if (view === "list") {
|
|
923
|
-
return (
|
|
924
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
925
|
-
{sharedToolbar}
|
|
926
|
-
<QuestionBankListView
|
|
927
|
-
rows={tableState.rows as QuestionBankItem[]}
|
|
928
|
-
onToggleFavorite={toggleFavorite}
|
|
929
|
-
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
930
|
-
/>
|
|
931
|
-
</div>
|
|
932
|
-
)
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (view === "board") {
|
|
936
|
-
return (
|
|
937
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
938
|
-
{sharedToolbar}
|
|
939
|
-
<QuestionBankBoardView
|
|
940
|
-
rows={tableState.rows as QuestionBankItem[]}
|
|
941
|
-
groupByColumnKey={questionBankBoardGroupKey}
|
|
942
|
-
onToggleFavorite={toggleFavorite}
|
|
943
|
-
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
944
|
-
/>
|
|
945
|
-
</div>
|
|
946
|
-
)
|
|
947
|
-
}
|
|
589
|
+
const toolbarShell = (body: React.ReactNode) => (
|
|
590
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
591
|
+
{sharedToolbar}
|
|
592
|
+
{body}
|
|
593
|
+
</div>
|
|
594
|
+
)
|
|
948
595
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
596
|
+
const handleFolderSheetCreated = React.useCallback(
|
|
597
|
+
(newFolder: {
|
|
598
|
+
name: string
|
|
599
|
+
icon: string
|
|
600
|
+
colorKey: QuestionBankFolder["colorKey"]
|
|
601
|
+
parentId: string | null
|
|
602
|
+
}) => {
|
|
603
|
+
if (customizingFolder) {
|
|
604
|
+
onFoldersChange(prev =>
|
|
605
|
+
prev.map(f =>
|
|
606
|
+
f.id === customizingFolder.id
|
|
607
|
+
? { ...f, name: newFolder.name, icon: newFolder.icon, colorKey: newFolder.colorKey }
|
|
608
|
+
: f,
|
|
609
|
+
),
|
|
610
|
+
)
|
|
611
|
+
setCustomizingFolder(null)
|
|
612
|
+
} else {
|
|
613
|
+
onFoldersChange(prev => [
|
|
614
|
+
...prev,
|
|
615
|
+
{
|
|
616
|
+
id: `fld-${Date.now()}`,
|
|
617
|
+
name: newFolder.name,
|
|
618
|
+
icon: newFolder.icon,
|
|
619
|
+
colorKey: newFolder.colorKey,
|
|
620
|
+
parentId: newFolder.parentId,
|
|
621
|
+
},
|
|
622
|
+
])
|
|
623
|
+
}
|
|
624
|
+
setNewFolderOpen(false)
|
|
625
|
+
},
|
|
626
|
+
[customizingFolder, onFoldersChange],
|
|
627
|
+
)
|
|
962
628
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
panelRenderDetail={panelRenderDetail}
|
|
973
|
-
onAddFolder={openNewFolderForColumn}
|
|
974
|
-
onAddQuestion={addQuestionInColumn}
|
|
975
|
-
onCustomizeFolder={openCustomizeFolderSheet}
|
|
976
|
-
/>
|
|
977
|
-
</ListPageSplitHubChrome>
|
|
978
|
-
</div>
|
|
979
|
-
<QuestionBankNewFolderSheet
|
|
980
|
-
open={newFolderOpen}
|
|
981
|
-
onOpenChange={setNewFolderOpen}
|
|
982
|
-
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
983
|
-
customizingFolder={customizingFolder}
|
|
984
|
-
onCreated={(newFolder) => {
|
|
985
|
-
if (customizingFolder) {
|
|
986
|
-
// Update existing folder
|
|
987
|
-
onFoldersChange(prev =>
|
|
988
|
-
prev.map(f =>
|
|
989
|
-
f.id === customizingFolder.id
|
|
990
|
-
? {
|
|
991
|
-
...f,
|
|
992
|
-
name: newFolder.name,
|
|
993
|
-
icon: newFolder.icon,
|
|
994
|
-
colorKey: newFolder.colorKey,
|
|
995
|
-
}
|
|
996
|
-
: f,
|
|
997
|
-
),
|
|
998
|
-
)
|
|
999
|
-
setCustomizingFolder(null)
|
|
1000
|
-
} else {
|
|
1001
|
-
// Create new folder
|
|
1002
|
-
onFoldersChange(prev => [
|
|
1003
|
-
...prev,
|
|
1004
|
-
{
|
|
1005
|
-
id: `fld-${Date.now()}`,
|
|
1006
|
-
name: newFolder.name,
|
|
1007
|
-
icon: newFolder.icon,
|
|
1008
|
-
colorKey: newFolder.colorKey,
|
|
1009
|
-
parentId: newFolder.parentId,
|
|
1010
|
-
},
|
|
1011
|
-
])
|
|
1012
|
-
}
|
|
1013
|
-
setNewFolderOpen(false)
|
|
1014
|
-
}}
|
|
1015
|
-
/>
|
|
1016
|
-
</>
|
|
1017
|
-
)
|
|
1018
|
-
}
|
|
629
|
+
const folderSheet = (
|
|
630
|
+
<QuestionBankNewFolderSheet
|
|
631
|
+
open={newFolderOpen}
|
|
632
|
+
onOpenChange={setNewFolderOpen}
|
|
633
|
+
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
634
|
+
customizingFolder={customizingFolder}
|
|
635
|
+
onCreated={handleFolderSheetCreated}
|
|
636
|
+
/>
|
|
637
|
+
)
|
|
1019
638
|
|
|
1020
|
-
|
|
1021
|
-
return (
|
|
1022
|
-
<>
|
|
1023
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
1024
|
-
{sharedToolbar}
|
|
1025
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
1026
|
-
<HubTreePanelView
|
|
1027
|
-
items={tableState.rows as QuestionBankItem[]}
|
|
1028
|
-
folders={folders}
|
|
1029
|
-
onItemsChange={onItemsChange}
|
|
1030
|
-
onFoldersChange={onFoldersChange}
|
|
1031
|
-
/>
|
|
1032
|
-
</div>
|
|
1033
|
-
</div>
|
|
1034
|
-
<QuestionBankNewFolderSheet
|
|
1035
|
-
open={newFolderOpen}
|
|
1036
|
-
onOpenChange={setNewFolderOpen}
|
|
1037
|
-
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
1038
|
-
customizingFolder={customizingFolder}
|
|
1039
|
-
onCreated={(newFolder) => {
|
|
1040
|
-
if (customizingFolder) {
|
|
1041
|
-
// Update existing folder
|
|
1042
|
-
onFoldersChange(prev =>
|
|
1043
|
-
prev.map(f =>
|
|
1044
|
-
f.id === customizingFolder.id
|
|
1045
|
-
? {
|
|
1046
|
-
...f,
|
|
1047
|
-
name: newFolder.name,
|
|
1048
|
-
icon: newFolder.icon,
|
|
1049
|
-
colorKey: newFolder.colorKey,
|
|
1050
|
-
}
|
|
1051
|
-
: f,
|
|
1052
|
-
),
|
|
1053
|
-
)
|
|
1054
|
-
setCustomizingFolder(null)
|
|
1055
|
-
} else {
|
|
1056
|
-
// Create new folder
|
|
1057
|
-
onFoldersChange(prev => [
|
|
1058
|
-
...prev,
|
|
1059
|
-
{
|
|
1060
|
-
id: `fld-${Date.now()}`,
|
|
1061
|
-
name: newFolder.name,
|
|
1062
|
-
icon: newFolder.icon,
|
|
1063
|
-
colorKey: newFolder.colorKey,
|
|
1064
|
-
parentId: newFolder.parentId,
|
|
1065
|
-
},
|
|
1066
|
-
])
|
|
1067
|
-
}
|
|
1068
|
-
setNewFolderOpen(false)
|
|
1069
|
-
}}
|
|
1070
|
-
/>
|
|
1071
|
-
</>
|
|
1072
|
-
)
|
|
1073
|
-
}
|
|
639
|
+
const rows = tableState.rows as QuestionBankItem[]
|
|
1074
640
|
|
|
1075
641
|
return (
|
|
1076
|
-
<
|
|
1077
|
-
{
|
|
1078
|
-
|
|
1079
|
-
|
|
642
|
+
<ListPageConnectedViewBody
|
|
643
|
+
view={view}
|
|
644
|
+
hubLabel="Question bank"
|
|
645
|
+
renderers={defineHubViewRenderers(QUESTION_BANK_SUPPORTED_VIEWS, {
|
|
646
|
+
"data-table": (
|
|
647
|
+
<div className="pb-6">
|
|
648
|
+
<DataTable<QuestionBankItem> {...tableProps} />
|
|
649
|
+
</div>
|
|
650
|
+
),
|
|
651
|
+
"list-with-toolbar": toolbarShell(
|
|
652
|
+
<QuestionBankListView
|
|
653
|
+
rows={rows}
|
|
654
|
+
onToggleFavorite={toggleFavorite}
|
|
655
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
656
|
+
/>,
|
|
657
|
+
),
|
|
658
|
+
"board-with-toolbar": toolbarShell(
|
|
659
|
+
<QuestionBankBoardView
|
|
660
|
+
rows={rows}
|
|
661
|
+
groupByColumnKey={questionBankBoardGroupKey}
|
|
662
|
+
onToggleFavorite={toggleFavorite}
|
|
663
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
664
|
+
/>,
|
|
665
|
+
),
|
|
666
|
+
"folder-with-toolbar": toolbarShell(
|
|
667
|
+
<QuestionBankOsFolderView
|
|
668
|
+
folders={folders}
|
|
669
|
+
onFoldersChange={onFoldersChange}
|
|
670
|
+
questions={rows}
|
|
671
|
+
onQuestionsChange={onItemsChange}
|
|
672
|
+
/>,
|
|
673
|
+
),
|
|
674
|
+
"panel-with-toolbar": (
|
|
675
|
+
<>
|
|
676
|
+
{toolbarShell(
|
|
677
|
+
<ListPageSplitHubChrome aria-label="Question bank folder columns">
|
|
678
|
+
<QuestionBankFolderColumnsPanel
|
|
679
|
+
folders={folders}
|
|
680
|
+
rows={rows}
|
|
681
|
+
panelRenderDetail={panelRenderDetail}
|
|
682
|
+
onAddFolder={openNewFolderForColumn}
|
|
683
|
+
onAddQuestion={addQuestionInColumn}
|
|
684
|
+
onCustomizeFolder={openCustomizeFolderSheet}
|
|
685
|
+
/>
|
|
686
|
+
</ListPageSplitHubChrome>,
|
|
687
|
+
)}
|
|
688
|
+
{folderSheet}
|
|
689
|
+
</>
|
|
690
|
+
),
|
|
691
|
+
"tree-panel-with-toolbar": (
|
|
692
|
+
<>
|
|
693
|
+
{toolbarShell(
|
|
694
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
695
|
+
<HubTreePanelView
|
|
696
|
+
items={rows}
|
|
697
|
+
folders={folders}
|
|
698
|
+
onItemsChange={onItemsChange}
|
|
699
|
+
onFoldersChange={onFoldersChange}
|
|
700
|
+
/>
|
|
701
|
+
</div>,
|
|
702
|
+
)}
|
|
703
|
+
{folderSheet}
|
|
704
|
+
</>
|
|
705
|
+
),
|
|
706
|
+
"calendar-with-toolbar": toolbarShell(
|
|
707
|
+
<ListPageCalendarView
|
|
708
|
+
rows={rows}
|
|
709
|
+
getRowId={row => row.id}
|
|
710
|
+
getEventDate={row => row.updatedAt}
|
|
711
|
+
getEventLabel={row => row.stem}
|
|
712
|
+
getEventMeta={row => row.topic}
|
|
713
|
+
emptyMonthLabel="No questions on this day."
|
|
714
|
+
ariaLabel="Question bank calendar"
|
|
715
|
+
showSummaryPanel={displayOptions.showCalendarSummaryPanel}
|
|
716
|
+
calendarMainView={displayOptions.calendarMainView}
|
|
717
|
+
onCalendarMainViewChange={v => patchDisplay({ calendarMainView: v })}
|
|
718
|
+
onEventActivate={row => tableState.toggleRow(row.id)}
|
|
719
|
+
/>,
|
|
720
|
+
),
|
|
721
|
+
"dashboard-with-toolbar": toolbarShell(
|
|
722
|
+
<QuestionBankDashboardChartsSection rows={rows} />,
|
|
723
|
+
),
|
|
724
|
+
})}
|
|
725
|
+
/>
|
|
1080
726
|
)
|
|
1081
727
|
})
|
|
1082
728
|
|