@exxatdesignux/ui 0.2.18 → 0.3.0
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 +69 -1
- package/bin/sync-extras.mjs +116 -29
- package/consumer-extras/README.md +43 -4
- package/consumer-extras/cursor-rules/exxat-accessibility.mdc +39 -0
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +26 -0
- package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +44 -0
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +32 -0
- package/consumer-extras/cursor-rules/exxat-command-menu.mdc +22 -0
- package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +53 -0
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +41 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +25 -0
- package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +22 -0
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +56 -0
- package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +100 -0
- package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +24 -0
- package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +30 -0
- package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +78 -0
- package/consumer-extras/cursor-rules/exxat-no-toast.mdc +25 -0
- package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +23 -0
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +47 -0
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +52 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +34 -0
- package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +77 -0
- package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +103 -0
- package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +9 -9
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +1 -1
- package/consumer-extras/handbook/HANDBOOK.md +185 -0
- package/consumer-extras/handbook/glossary.md +57 -0
- package/consumer-extras/handbook/reference-implementations.md +126 -0
- package/consumer-extras/handbook/voice-and-tone.md +262 -0
- package/consumer-extras/patterns/command-menu-pattern.md +1 -1
- package/consumer-extras/patterns/data-views-pattern.md +14 -14
- package/dist/components/data-table/filter-date-calendar.d.ts +10 -0
- package/dist/components/data-table/filter-date-calendar.js +280 -0
- package/dist/components/data-table/filter-date-calendar.js.map +1 -0
- package/dist/components/data-table/filter-text-value-input.d.ts +15 -0
- package/dist/components/data-table/filter-text-value-input.js +561 -0
- package/dist/components/data-table/filter-text-value-input.js.map +1 -0
- package/dist/components/data-table/index.d.ts +45 -0
- package/dist/components/data-table/index.js +3085 -0
- package/dist/components/data-table/index.js.map +1 -0
- package/dist/components/data-table/pagination.d.ts +28 -0
- package/dist/components/data-table/pagination.js +3264 -0
- package/dist/components/data-table/pagination.js.map +1 -0
- package/dist/components/data-table/types.d.ts +84 -0
- package/dist/components/data-table/types.js +3 -0
- package/dist/components/data-table/types.js.map +1 -0
- package/dist/components/data-table/use-table-state.d.ts +116 -0
- package/dist/components/data-table/use-table-state.js +670 -0
- package/dist/components/data-table/use-table-state.js.map +1 -0
- package/dist/components/data-views/board-card-primitives.d.ts +22 -0
- package/dist/components/data-views/board-card-primitives.js +84 -0
- package/dist/components/data-views/board-card-primitives.js.map +1 -0
- package/dist/components/data-views/data-row-list.d.ts +33 -0
- package/dist/components/data-views/data-row-list.js +106 -0
- package/dist/components/data-views/data-row-list.js.map +1 -0
- package/dist/components/data-views/finder-panel-view.d.ts +54 -0
- package/dist/components/data-views/finder-panel-view.js +388 -0
- package/dist/components/data-views/finder-panel-view.js.map +1 -0
- package/dist/components/data-views/folder-grid-view.d.ts +22 -0
- package/dist/components/data-views/folder-grid-view.js +58 -0
- package/dist/components/data-views/folder-grid-view.js.map +1 -0
- package/dist/components/data-views/hub-table.d.ts +167 -0
- package/dist/components/data-views/hub-table.js +5561 -0
- package/dist/components/data-views/hub-table.js.map +1 -0
- package/dist/components/data-views/index.d.ts +27 -0
- package/dist/components/data-views/index.js +6575 -0
- package/dist/components/data-views/index.js.map +1 -0
- package/dist/components/data-views/list-page-board-card.d.ts +72 -0
- package/dist/components/data-views/list-page-board-card.js +264 -0
- package/dist/components/data-views/list-page-board-card.js.map +1 -0
- package/dist/components/data-views/list-page-board-template.d.ts +24 -0
- package/dist/components/data-views/list-page-board-template.js +137 -0
- package/dist/components/data-views/list-page-board-template.js.map +1 -0
- package/dist/components/data-views/list-page-connected-view-body.d.ts +19 -0
- package/dist/components/data-views/list-page-connected-view-body.js +116 -0
- package/dist/components/data-views/list-page-connected-view-body.js.map +1 -0
- package/dist/components/data-views/list-page-split-details-placeholder.d.ts +14 -0
- package/dist/components/data-views/list-page-split-details-placeholder.js +38 -0
- package/dist/components/data-views/list-page-split-details-placeholder.js.map +1 -0
- package/dist/components/data-views/list-page-split-hub-chrome.d.ts +17 -0
- package/dist/components/data-views/list-page-split-hub-chrome.js +54 -0
- package/dist/components/data-views/list-page-split-hub-chrome.js.map +1 -0
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +12 -0
- package/dist/components/data-views/list-page-split-hub-tokens.js +8 -0
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -0
- package/dist/components/data-views/list-page-tree-column-header.d.ts +15 -0
- package/dist/components/data-views/list-page-tree-column-header.js +22 -0
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -0
- package/dist/components/data-views/list-page-tree-panel-shell.d.ts +25 -0
- package/dist/components/data-views/list-page-tree-panel-shell.js +146 -0
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -0
- package/dist/components/data-views/os-folder-glyph.d.ts +35 -0
- package/dist/components/data-views/os-folder-glyph.js +104 -0
- package/dist/components/data-views/os-folder-glyph.js.map +1 -0
- package/dist/components/data-views/outline-tree-menu.d.ts +36 -0
- package/dist/components/data-views/outline-tree-menu.js +131 -0
- package/dist/components/data-views/outline-tree-menu.js.map +1 -0
- package/dist/components/table-properties/column-row.d.ts +22 -0
- package/dist/components/table-properties/column-row.js +153 -0
- package/dist/components/table-properties/column-row.js.map +1 -0
- package/dist/components/table-properties/draggable-list.d.ts +24 -0
- package/dist/components/table-properties/draggable-list.js +53 -0
- package/dist/components/table-properties/draggable-list.js.map +1 -0
- package/dist/components/table-properties/drawer-button.d.ts +110 -0
- package/dist/components/table-properties/drawer-button.js +2748 -0
- package/dist/components/table-properties/drawer-button.js.map +1 -0
- package/dist/components/table-properties/drawer.d.ts +100 -0
- package/dist/components/table-properties/drawer.js +2595 -0
- package/dist/components/table-properties/drawer.js.map +1 -0
- package/dist/components/table-properties/filter-card.d.ts +24 -0
- package/dist/components/table-properties/filter-card.js +854 -0
- package/dist/components/table-properties/filter-card.js.map +1 -0
- package/dist/components/table-properties/index.d.ts +14 -0
- package/dist/components/table-properties/index.js +2768 -0
- package/dist/components/table-properties/index.js.map +1 -0
- package/dist/components/table-properties/sort-card.d.ts +20 -0
- package/dist/components/table-properties/sort-card.js +102 -0
- package/dist/components/table-properties/sort-card.js.map +1 -0
- package/dist/components/templates/dedicated-search-landing-template.d.ts +21 -0
- package/dist/components/templates/dedicated-search-landing-template.js +254 -0
- package/dist/components/templates/dedicated-search-landing-template.js.map +1 -0
- package/dist/components/templates/dedicated-search-results-template.d.ts +15 -0
- package/dist/components/templates/dedicated-search-results-template.js +16 -0
- package/dist/components/templates/dedicated-search-results-template.js.map +1 -0
- package/dist/components/templates/index.d.ts +9 -0
- package/dist/components/templates/index.js +2720 -0
- package/dist/components/templates/index.js.map +1 -0
- package/dist/components/templates/list-page.d.ts +83 -0
- package/dist/components/templates/list-page.js +2433 -0
- package/dist/components/templates/list-page.js.map +1 -0
- package/dist/components/templates/nested-secondary-panel-shell.d.ts +20 -0
- package/dist/components/templates/nested-secondary-panel-shell.js +54 -0
- package/dist/components/templates/nested-secondary-panel-shell.js.map +1 -0
- package/dist/components/ui/accordion.d.ts +10 -0
- package/dist/components/ui/accordion.js +74 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +37 -0
- package/dist/components/ui/alert-dialog.js +201 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +84 -0
- package/dist/components/ui/avatar.js +328 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +13 -0
- package/dist/components/ui/badge.js +49 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/banner.d.ts +62 -0
- package/dist/components/ui/banner.js +364 -0
- package/dist/components/ui/banner.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +14 -0
- package/dist/components/ui/breadcrumb.js +114 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +16 -0
- package/dist/components/ui/button.js +59 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +13 -0
- package/dist/components/ui/calendar.js +238 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +14 -0
- package/dist/components/ui/card.js +102 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/chart.d.ts +58 -0
- package/dist/components/ui/chart.js +292 -0
- package/dist/components/ui/chart.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +23 -0
- package/dist/components/ui/checkbox.js +155 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/coach-mark.d.ts +27 -0
- package/dist/components/ui/coach-mark.js +306 -0
- package/dist/components/ui/coach-mark.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +8 -0
- package/dist/components/ui/collapsible.js +35 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/command.d.ts +36 -0
- package/dist/components/ui/command.js +274 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +32 -0
- package/dist/components/ui/context-menu.js +245 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/date-picker-field.d.ts +38 -0
- package/dist/components/ui/date-picker-field.js +550 -0
- package/dist/components/ui/date-picker-field.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +22 -0
- package/dist/components/ui/dialog.js +200 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dot-pattern.d.ts +21 -0
- package/dist/components/ui/dot-pattern.js +139 -0
- package/dist/components/ui/dot-pattern.js.map +1 -0
- package/dist/components/ui/drag-handle-grip.d.ts +10 -0
- package/dist/components/ui/drag-handle-grip.js +15 -0
- package/dist/components/ui/drag-handle-grip.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +16 -0
- package/dist/components/ui/drawer.js +125 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +45 -0
- package/dist/components/ui/dropdown-menu.js +353 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/export-drawer.d.ts +11 -0
- package/dist/components/ui/export-drawer.js +1658 -0
- package/dist/components/ui/export-drawer.js.map +1 -0
- package/dist/components/ui/field.d.ts +30 -0
- package/dist/components/ui/field.js +249 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/form.d.ts +28 -0
- package/dist/components/ui/form.js +110 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +9 -0
- package/dist/components/ui/hover-card.js +43 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +20 -0
- package/dist/components/ui/input-group.js +219 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-mask.d.ts +39 -0
- package/dist/components/ui/input-mask.js +118 -0
- package/dist/components/ui/input-mask.js.map +1 -0
- package/dist/components/ui/input.d.ts +5 -0
- package/dist/components/ui/input.js +30 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +20 -0
- package/dist/components/ui/kbd.js +45 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-metrics-context.d.ts +19 -0
- package/dist/components/ui/key-metrics-context.js +26 -0
- package/dist/components/ui/key-metrics-context.js.map +1 -0
- package/dist/components/ui/key-metrics.d.ts +131 -0
- package/dist/components/ui/key-metrics.js +1015 -0
- package/dist/components/ui/key-metrics.js.map +1 -0
- package/dist/components/ui/label.d.ts +6 -0
- package/dist/components/ui/label.js +28 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/list-page-view-frame.d.ts +22 -0
- package/dist/components/ui/list-page-view-frame.js +24 -0
- package/dist/components/ui/list-page-view-frame.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +51 -0
- package/dist/components/ui/page-header.js +372 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/payment-card-fields.d.ts +10 -0
- package/dist/components/ui/payment-card-fields.js +80 -0
- package/dist/components/ui/payment-card-fields.js.map +1 -0
- package/dist/components/ui/popover.d.ts +10 -0
- package/dist/components/ui/popover.js +47 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +29 -0
- package/dist/components/ui/radio-group.js +190 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +16 -0
- package/dist/components/ui/resizable.js +51 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +8 -0
- package/dist/components/ui/scroll-area.js +66 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +18 -0
- package/dist/components/ui/select.js +186 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/selection-tile-grid.d.ts +52 -0
- package/dist/components/ui/selection-tile-grid.js +347 -0
- package/dist/components/ui/selection-tile-grid.js.map +1 -0
- package/dist/components/ui/separator.d.ts +7 -0
- package/dist/components/ui/separator.js +33 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +18 -0
- package/dist/components/ui/sheet.js +181 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +94 -0
- package/dist/components/ui/sidebar.js +805 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +5 -0
- package/dist/components/ui/skeleton.js +22 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +7 -0
- package/dist/components/ui/slider.js +66 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +6 -0
- package/dist/components/ui/sonner.js +38 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/status-badge.d.ts +38 -0
- package/dist/components/ui/status-badge.js +77 -0
- package/dist/components/ui/status-badge.js.map +1 -0
- package/dist/components/ui/table.d.ts +13 -0
- package/dist/components/ui/table.js +115 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +15 -0
- package/dist/components/ui/tabs.js +93 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +6 -0
- package/dist/components/ui/textarea.js +25 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/tip.d.ts +12 -0
- package/dist/components/ui/tip.js +61 -0
- package/dist/components/ui/tip.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +14 -0
- package/dist/components/ui/toggle-group.js +104 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle-switch.d.ts +10 -0
- package/dist/components/ui/toggle-switch.js +33 -0
- package/dist/components/ui/toggle-switch.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +13 -0
- package/dist/components/ui/toggle.js +51 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +10 -0
- package/dist/components/ui/tooltip.js +68 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/components/ui/view-segmented-control.d.ts +31 -0
- package/dist/components/ui/view-segmented-control.js +167 -0
- package/dist/components/ui/view-segmented-control.js.map +1 -0
- package/dist/data-list-view-registry-CyBoBML4.d.ts +73 -0
- package/dist/hooks/use-app-theme.d.ts +24 -0
- package/dist/hooks/use-app-theme.js +286 -0
- package/dist/hooks/use-app-theme.js.map +1 -0
- package/dist/hooks/use-coach-mark.d.ts +86 -0
- package/dist/hooks/use-coach-mark.js +218 -0
- package/dist/hooks/use-coach-mark.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +3 -0
- package/dist/hooks/use-mobile.js +29 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-mod-key-label.d.ts +6 -0
- package/dist/hooks/use-mod-key-label.js +25 -0
- package/dist/hooks/use-mod-key-label.js.map +1 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +13324 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/compose-refs.d.ts +6 -0
- package/dist/lib/compose-refs.js +17 -0
- package/dist/lib/compose-refs.js.map +1 -0
- package/dist/lib/conditional-rule-match.d.ts +30 -0
- package/dist/lib/conditional-rule-match.js +66 -0
- package/dist/lib/conditional-rule-match.js.map +1 -0
- package/dist/lib/data-list-display-options.d.ts +26 -0
- package/dist/lib/data-list-display-options.js +14 -0
- package/dist/lib/data-list-display-options.js.map +1 -0
- package/dist/lib/data-list-view-registry.d.ts +2 -0
- package/dist/lib/data-list-view-registry.js +102 -0
- package/dist/lib/data-list-view-registry.js.map +1 -0
- package/dist/lib/data-list-view-surface.d.ts +2 -0
- package/dist/lib/data-list-view-surface.js +80 -0
- package/dist/lib/data-list-view-surface.js.map +1 -0
- package/dist/lib/data-list-view.d.ts +21 -0
- package/dist/lib/data-list-view.js +25 -0
- package/dist/lib/data-list-view.js.map +1 -0
- package/dist/lib/date-filter.d.ts +22 -0
- package/dist/lib/date-filter.js +61 -0
- package/dist/lib/date-filter.js.map +1 -0
- package/dist/lib/dev-log.d.ts +8 -0
- package/dist/lib/dev-log.js +10 -0
- package/dist/lib/dev-log.js.map +1 -0
- package/dist/lib/dropdown-menu-surface.d.ts +14 -0
- package/dist/lib/dropdown-menu-surface.js +6 -0
- package/dist/lib/dropdown-menu-surface.js.map +1 -0
- package/dist/lib/editable-target.d.ts +12 -0
- package/dist/lib/editable-target.js +12 -0
- package/dist/lib/editable-target.js.map +1 -0
- package/dist/lib/list-page-table-properties.d.ts +35 -0
- package/dist/lib/list-page-table-properties.js +81 -0
- package/dist/lib/list-page-table-properties.js.map +1 -0
- package/dist/lib/raf-throttle.d.ts +23 -0
- package/dist/lib/raf-throttle.js +27 -0
- package/dist/lib/raf-throttle.js.map +1 -0
- package/dist/lib/row-height.d.ts +16 -0
- package/dist/lib/row-height.js +10 -0
- package/dist/lib/row-height.js.map +1 -0
- package/dist/lib/table-properties-types.d.ts +83 -0
- package/dist/lib/table-properties-types.js +19 -0
- package/dist/lib/table-properties-types.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +83 -18
- package/src/components/data-table/filter-date-calendar.tsx +38 -0
- package/src/components/data-table/filter-text-value-input.tsx +77 -0
- package/src/components/data-table/index.tsx +1678 -0
- package/src/components/data-table/pagination.tsx +255 -0
- package/src/components/data-table/types.ts +96 -0
- package/src/components/data-table/use-table-state.ts +767 -0
- package/src/components/data-views/board-card-primitives.tsx +93 -0
- package/src/components/data-views/data-row-list.tsx +183 -0
- package/src/components/data-views/finder-panel-view.tsx +405 -0
- package/src/components/data-views/folder-grid-view.tsx +86 -0
- package/src/components/data-views/hub-table.tsx +498 -0
- package/src/components/data-views/index.ts +28 -0
- package/src/components/data-views/list-page-board-card.tsx +192 -0
- package/src/components/data-views/list-page-board-template.tsx +122 -0
- package/src/components/data-views/list-page-connected-view-body.tsx +66 -0
- package/src/components/data-views/list-page-split-details-placeholder.tsx +39 -0
- package/src/components/data-views/list-page-split-hub-chrome.tsx +60 -0
- package/src/components/data-views/list-page-split-hub-tokens.ts +16 -0
- package/src/components/data-views/list-page-tree-column-header.tsx +31 -0
- package/src/components/data-views/list-page-tree-panel-shell.tsx +91 -0
- package/src/components/data-views/os-folder-glyph.tsx +141 -0
- package/src/components/data-views/outline-tree-menu.tsx +157 -0
- package/src/components/table-properties/column-row.tsx +90 -0
- package/src/components/table-properties/draggable-list.ts +54 -0
- package/src/components/table-properties/drawer-button.tsx +300 -0
- package/src/components/table-properties/drawer.tsx +1148 -0
- package/src/components/table-properties/filter-card.tsx +251 -0
- package/src/components/table-properties/index.ts +36 -0
- package/src/components/table-properties/sort-card.tsx +63 -0
- package/src/components/templates/dedicated-search-landing-template.tsx +124 -0
- package/src/components/templates/dedicated-search-results-template.tsx +19 -0
- package/src/components/templates/index.ts +33 -0
- package/src/components/templates/list-page.tsx +602 -0
- package/src/components/templates/nested-secondary-panel-shell.tsx +70 -0
- package/src/components/ui/accordion.tsx +92 -0
- package/src/components/ui/alert-dialog.tsx +221 -0
- package/src/components/ui/avatar.tsx +13 -2
- package/src/components/ui/banner.tsx +2 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/coach-mark.tsx +1 -1
- package/src/components/ui/context-menu.tsx +291 -0
- package/src/components/ui/date-picker-field.tsx +2 -2
- package/src/components/ui/dot-pattern.tsx +183 -0
- package/src/components/ui/export-drawer.tsx +375 -0
- package/src/components/ui/hover-card.tsx +66 -0
- package/src/components/ui/key-metrics-context.tsx +78 -0
- package/src/components/ui/key-metrics.tsx +1133 -0
- package/src/components/ui/list-page-view-frame.tsx +64 -0
- package/src/components/ui/page-header.tsx +244 -0
- package/src/components/ui/payment-card-fields.tsx +2 -2
- package/src/components/ui/resizable.tsx +68 -0
- package/src/components/ui/scroll-area.tsx +72 -0
- package/src/components/ui/selection-tile-grid.tsx +9 -2
- package/src/components/ui/sidebar.tsx +84 -12
- package/src/components/ui/slider.tsx +83 -0
- package/src/globals.css +494 -151
- package/src/globals.d.ts +20 -0
- package/src/index.ts +68 -1
- package/src/lib/conditional-rule-match.ts +119 -0
- package/src/lib/data-list-display-options.ts +35 -0
- package/src/lib/data-list-view-registry.ts +104 -0
- package/src/lib/data-list-view-surface.ts +83 -0
- package/src/lib/data-list-view.ts +47 -0
- package/src/lib/dev-log.ts +10 -0
- package/src/lib/editable-target.ts +20 -0
- package/src/lib/list-page-table-properties.ts +48 -0
- package/src/lib/raf-throttle.ts +45 -0
- package/src/lib/row-height.ts +19 -0
- package/src/lib/table-properties-types.ts +98 -0
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +3 -3
- package/template/.cursor/rules/exxat-data-tables.mdc +1 -1
- package/template/.cursor/rules/exxat-ds-agents.mdc +2 -2
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +2 -2
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +84 -20
- package/template/app/(app)/examples/page.tsx +0 -1
- package/template/app/(app)/layout.tsx +17 -4
- package/template/app/(app)/question-bank/layout.tsx +1 -1
- package/template/app/(app)/question-bank/new/page.tsx +11 -24
- package/template/app/globals.css +13 -1972
- package/template/components/ask-leo-sidebar.tsx +5 -1
- package/template/components/brand-color-picker.tsx +2 -2
- package/template/components/charts-overview.tsx +1 -1
- package/template/components/compliance-table.tsx +240 -384
- package/template/components/dashboard-report-charts.tsx +1 -1
- package/template/components/dashboard-tabs.tsx +1 -1
- package/template/components/data-table/filter-date-calendar.tsx +1 -38
- package/template/components/data-table/filter-text-value-input.tsx +1 -77
- package/template/components/data-table/index.tsx +1 -1634
- package/template/components/data-table/pagination.tsx +1 -255
- package/template/components/data-table/types.ts +1 -94
- package/template/components/data-table/use-table-state.test.ts +420 -0
- package/template/components/data-table/use-table-state.ts +1 -758
- package/template/components/data-view-dashboard-charts-compliance.tsx +2 -2
- package/template/components/data-view-dashboard-charts-team.tsx +2 -2
- package/template/components/data-view-dashboard-charts.tsx +2 -2
- package/template/components/data-views/board-card-primitives.tsx +1 -93
- package/template/components/data-views/data-row-list.tsx +1 -183
- package/template/components/data-views/finder-panel-view.tsx +1 -405
- package/template/components/data-views/folder-grid-view.tsx +1 -86
- package/template/components/data-views/hub-table.tsx +1 -0
- package/template/components/data-views/index.ts +42 -1
- package/template/components/data-views/list-page-board-card.tsx +1 -192
- package/template/components/data-views/list-page-board-template.tsx +1 -122
- package/template/components/data-views/list-page-connected-view-body.tsx +1 -0
- package/template/components/data-views/list-page-split-details-placeholder.tsx +1 -39
- package/template/components/data-views/list-page-split-hub-chrome.tsx +1 -60
- package/template/components/data-views/list-page-split-hub-tokens.ts +1 -16
- package/template/components/data-views/list-page-tree-column-header.tsx +1 -31
- package/template/components/data-views/list-page-tree-panel-shell.tsx +1 -91
- package/template/components/data-views/list-page-view-frame.tsx +5 -53
- package/template/components/data-views/os-folder-glyph.tsx +1 -129
- package/template/components/data-views/outline-tree-menu.tsx +1 -157
- package/template/components/export-drawer.test.tsx +71 -0
- package/template/components/export-drawer.tsx +1 -375
- package/template/components/exxat-product-logo.tsx +5 -5
- package/template/components/hub-tree-panel-view.tsx +2 -2
- package/template/components/invite-collaborators-drawer.tsx +3 -3
- package/template/components/key-metrics-ask-leo-bridge.tsx +40 -0
- package/template/components/key-metrics.tsx +1 -1063
- package/template/components/leo-insight-indicator.tsx +2 -2
- package/template/components/new-placement-back-btn.tsx +1 -1
- package/template/components/new-placement-form.tsx +63 -189
- package/template/components/new-question-composer.tsx +432 -402
- package/template/components/onboarding/index.ts +9 -0
- package/template/components/onboarding/onboarding-01.tsx +1 -1
- package/template/components/onboarding/onboarding-02.tsx +1 -1
- package/template/components/onboarding/onboarding-03.tsx +1 -1
- package/template/components/onboarding/onboarding-04.tsx +1 -1
- package/template/components/page-header.tsx +8 -226
- package/template/components/placement-board-card.tsx +71 -83
- package/template/components/placements-board-view.tsx +3 -10
- package/template/components/placements-client.tsx +10 -42
- package/template/components/placements-list-view.tsx +22 -69
- package/template/components/placements-table-columns.tsx +8 -438
- package/template/components/placements-table.tsx +588 -1296
- package/template/components/product-switcher.tsx +1 -1
- package/template/components/product-wordmark.tsx +2 -1
- package/template/components/question-bank-client.tsx +4 -1
- package/template/components/question-bank-hub-client.tsx +1 -1
- package/template/components/question-bank-new-folder-sheet.tsx +2 -2
- package/template/components/question-bank-secondary-nav.tsx +3 -3
- package/template/components/question-bank-table.tsx +294 -526
- package/template/components/rotations-empty-state.tsx +1 -1
- package/template/components/rotations-panel-activator.tsx +1 -1
- package/template/components/settings-appearance-card.tsx +1 -1
- package/template/components/{app-sidebar-dynamic.tsx → sidebar/app-sidebar-dynamic.tsx} +1 -1
- package/template/components/{app-sidebar.tsx → sidebar/app-sidebar.tsx} +4 -4
- package/template/components/sidebar/index.ts +16 -0
- package/template/components/{secondary-nav.tsx → sidebar/secondary-nav.tsx} +2 -2
- package/template/components/{secondary-panel.tsx → sidebar/secondary-panel.tsx} +6 -3
- package/template/components/{sidebar-auto-collapse.tsx → sidebar/sidebar-auto-collapse.tsx} +6 -2
- package/template/components/{sidebar-shell.tsx → sidebar/sidebar-shell.tsx} +1 -1
- package/template/components/site-header.tsx +1 -1
- package/template/components/{sites-all-client.tsx → sites-client.tsx} +1 -1
- package/template/components/sites-table.tsx +124 -257
- package/template/components/table-properties/column-row.tsx +1 -90
- package/template/components/table-properties/draggable-list.ts +1 -49
- package/template/components/table-properties/drawer-button.tsx +1 -249
- package/template/components/table-properties/drawer.tsx +1 -1105
- package/template/components/table-properties/filter-card.tsx +1 -251
- package/template/components/table-properties/sort-card.tsx +1 -59
- package/template/components/table-properties/types.ts +28 -71
- package/template/components/team-table.tsx +242 -382
- package/template/components/templates/dedicated-search-landing-template.tsx +1 -124
- package/template/components/templates/dedicated-search-results-template.tsx +1 -19
- package/template/components/templates/list-page.tsx +1 -584
- package/template/components/templates/nested-secondary-panel-shell.tsx +1 -62
- package/template/components/templates/new-focus-template.tsx +659 -0
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/ui/accordion.tsx +1 -0
- package/template/components/ui/alert-dialog.tsx +1 -0
- package/template/components/ui/context-menu.tsx +1 -0
- package/template/components/ui/dot-pattern.tsx +1 -183
- package/template/components/ui/hover-card.tsx +1 -0
- package/template/components/ui/resizable.tsx +1 -68
- package/template/components/ui/scroll-area.tsx +1 -0
- package/template/components/ui/slider.tsx +1 -0
- package/template/docs/blueprints/README.md +86 -0
- package/template/docs/blueprints/_template.md +91 -0
- package/template/docs/blueprints/board-card.md +123 -0
- package/template/docs/blueprints/data-table.md +139 -0
- package/template/docs/blueprints/key-metrics.md +128 -0
- package/template/docs/blueprints/list-page-template.md +123 -0
- package/template/docs/blueprints/page-header.md +130 -0
- package/template/docs/command-menu-pattern.md +1 -1
- package/template/docs/component-selection-guide.md +224 -0
- package/template/docs/components-audit-2026-05.md +158 -0
- package/template/docs/data-views-pattern.md +14 -14
- package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +95 -0
- package/template/docs/migrations/0002-exxat-token-namespace.md +154 -0
- package/template/docs/migrations/0003-globals-css-canonical.md +110 -0
- package/template/docs/migrations/README.md +100 -0
- package/template/docs/migrations/_template.md +64 -0
- package/template/docs/token-taxonomy.md +416 -0
- package/template/eslint.config.mjs +27 -0
- package/template/hooks/use-secondary-panel-hub-nav.ts +1 -1
- package/template/lib/command-menu-config.ts +0 -1
- package/template/lib/compliance-supported-views.ts +10 -0
- package/template/lib/conditional-rule-match.ts +6 -97
- package/template/lib/data-list-display-options.ts +1 -35
- package/template/lib/data-list-view-registry.ts +1 -0
- package/template/lib/data-list-view-surface.ts +1 -69
- package/template/lib/data-list-view.ts +1 -38
- package/template/lib/dev-log.ts +1 -8
- package/template/lib/editable-target.ts +1 -10
- package/template/lib/hub-connected-view-renderers.ts +58 -0
- package/template/lib/list-hub-supported-views.ts +10 -0
- package/template/lib/list-page-table-properties.ts +1 -52
- package/template/lib/mock/navigation.tsx +0 -8
- package/template/lib/mock/placements.ts +0 -7
- package/template/lib/placement-board-card-layout.ts +41 -41
- package/template/lib/placements-supported-views.ts +12 -0
- package/template/lib/question-bank-supported-views.ts +12 -0
- package/template/lib/raf-throttle.ts +1 -45
- package/template/lib/row-height.ts +4 -10
- package/template/lib/sidebar-state-cookie.ts +11 -2
- package/template/lib/sites-supported-views.ts +10 -0
- package/template/lib/team-supported-views.ts +10 -0
- package/template/package.json +1 -0
- package/template/tests/setup.ts +25 -0
- package/src/theme.css +0 -1132
- package/template/app/(app)/data-list/[id]/page.tsx +0 -44
- package/template/app/(app)/data-list/new/page.tsx +0 -34
- package/template/app/(app)/data-list/page.tsx +0 -10
- package/template/components/compliance-list-view.tsx +0 -54
- package/template/components/dashboard-onboarding-gallery.tsx +0 -13
- package/template/components/dashboard-onboarding.tsx +0 -21
- package/template/components/question-bank-list-view.tsx +0 -53
- package/template/components/section-cards.tsx +0 -106
- package/template/components/sites-list-view.tsx +0 -42
- package/template/components/team-list-view.tsx +0 -59
- package/template/lib/placement-lifecycle.ts +0 -5
- /package/template/components/{getting-started.tsx → onboarding/getting-started.tsx} +0 -0
- /package/template/components/{nav-documents.tsx → sidebar/nav-documents.tsx} +0 -0
- /package/template/components/{nav-main.tsx → sidebar/nav-main.tsx} +0 -0
- /package/template/components/{nav-secondary.tsx → sidebar/nav-secondary.tsx} +0 -0
- /package/template/components/{nav-user.tsx → sidebar/nav-user.tsx} +0 -0
- /package/template/components/{sidebar-auto-open.tsx → sidebar/sidebar-auto-open.tsx} +0 -0
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Question bank —
|
|
4
|
+
* Question bank — thin wrapper around the centralized `<HubTable>`. Owns column defs,
|
|
5
|
+
* folder/panel/tree-panel custom views, the new-folder + customize-folder sheet, and
|
|
6
|
+
* forwards URL search via `HubTable.syncedSearchFromUrl`.
|
|
7
|
+
*
|
|
8
|
+
* Single dataset rule: `HubTable` runs one `useTableState(tableSourceItems, columns, …)`.
|
|
9
|
+
* Every non-table renderer (list, board, folder, panel, tree-panel, dashboard) reads
|
|
10
|
+
* `state.rows` — the same filtered/sorted/searched bag as the grid.
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
import * as React from "react"
|
|
8
14
|
import dynamic from "next/dynamic"
|
|
9
15
|
import { mailtoHref } from "@/lib/mailto"
|
|
10
|
-
import { DataTable, DataTableToolbar } from "@/components/data-table"
|
|
11
16
|
import type { DataListViewType } from "@/lib/data-list-view"
|
|
12
|
-
import type { OpenTablePropertiesHandle } from "@/lib/list-page-table-properties"
|
|
13
17
|
import type { ColumnDef } from "@/components/data-table/types"
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
import {
|
|
19
|
+
HubTable,
|
|
20
|
+
type HubTableHandle,
|
|
21
|
+
type HubTableRenderers,
|
|
22
|
+
type HubTableRendererArgs,
|
|
23
|
+
} from "@/components/data-views"
|
|
24
|
+
import { QUESTION_BANK_SUPPORTED_VIEWS } from "@/lib/question-bank-supported-views"
|
|
17
25
|
import { Button } from "@/components/ui/button"
|
|
18
26
|
import {
|
|
19
27
|
DropdownMenu,
|
|
@@ -41,7 +49,7 @@ import {
|
|
|
41
49
|
} from "@/components/data-views/list-page-split-hub-tokens"
|
|
42
50
|
import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
|
|
43
51
|
import { QuestionBankBoardView, QUESTION_BANK_BOARD_GROUP_OPTIONS } from "@/components/question-bank-board-view"
|
|
44
|
-
import {
|
|
52
|
+
import { ListPageBoardCard } from "@/components/data-views/list-page-board-card"
|
|
45
53
|
import {
|
|
46
54
|
QuestionBankFavoriteButton,
|
|
47
55
|
QUESTION_BANK_FAVORITE_HOVER_GROUP,
|
|
@@ -60,17 +68,19 @@ import {
|
|
|
60
68
|
type QuestionBankItem,
|
|
61
69
|
type QuestionBankType,
|
|
62
70
|
} from "@/lib/mock/question-bank"
|
|
63
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
type QuestionBankFolder,
|
|
73
|
+
QUESTION_BANK_FOLDER_COLOR_STYLES,
|
|
74
|
+
QUESTION_BANK_FOLDER_ICON_COLORS,
|
|
75
|
+
} from "@/lib/mock/question-bank-folders"
|
|
64
76
|
import {
|
|
65
77
|
toggleQuestionBankItemFavorite,
|
|
66
78
|
applyQuestionBankHubDisplayFilters,
|
|
67
79
|
type QuestionBankLandingFilterState,
|
|
68
80
|
type QuestionBankNavState,
|
|
69
81
|
} from "@/lib/question-bank-nav"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
type DataListDisplayOptions,
|
|
73
|
-
} from "@/lib/data-list-display-options"
|
|
82
|
+
|
|
83
|
+
// ─── Dynamic dashboard charts section ────────────────────────────────────────
|
|
74
84
|
|
|
75
85
|
const QuestionBankDashboardChartsSection = dynamic(
|
|
76
86
|
() =>
|
|
@@ -102,6 +112,8 @@ const QuestionBankDashboardChartsSection = dynamic(
|
|
|
102
112
|
},
|
|
103
113
|
)
|
|
104
114
|
|
|
115
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
105
117
|
const TYPE_LABEL: Record<QuestionBankType, string> = {
|
|
106
118
|
multiple_choice: "Multiple choice",
|
|
107
119
|
true_false: "True / false",
|
|
@@ -114,21 +126,6 @@ const DIFFICULTY_LABEL: Record<QuestionBankDifficulty, string> = {
|
|
|
114
126
|
hard: "Hard",
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
function newQuestionBankItemId() {
|
|
118
|
-
return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Folder id to use when adding a question from the root column (`parentId` null). */
|
|
122
|
-
function defaultFolderIdForColumnParent(parentId: string | null, folders: QuestionBankFolder[]): string | null {
|
|
123
|
-
if (parentId !== null) return parentId
|
|
124
|
-
const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name))
|
|
125
|
-
return roots[0]?.id ?? null
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function uniqueTopics(items: QuestionBankItem[]) {
|
|
129
|
-
return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
|
|
130
|
-
}
|
|
131
|
-
|
|
132
129
|
const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as QuestionBankType[]).map(k => ({
|
|
133
130
|
value: k,
|
|
134
131
|
label: TYPE_LABEL[k],
|
|
@@ -139,26 +136,18 @@ const DIFFICULTY_FILTER_OPTS = (Object.keys(DIFFICULTY_LABEL) as QuestionBankDif
|
|
|
139
136
|
label: DIFFICULTY_LABEL[k],
|
|
140
137
|
}))
|
|
141
138
|
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
const f = c.filter
|
|
145
|
-
const defaultOps: FilterOperator[] =
|
|
146
|
-
f.type === "select" || f.type === "date"
|
|
147
|
-
? ["is", "is_not"]
|
|
148
|
-
: ["contains", "not_contains"]
|
|
149
|
-
return {
|
|
150
|
-
key: c.key,
|
|
151
|
-
label: c.label,
|
|
152
|
-
icon: f.icon ?? "fa-filter",
|
|
153
|
-
type: f.type,
|
|
154
|
-
operators: (f.operators ?? defaultOps) as FilterOperator[],
|
|
155
|
-
options: f.options,
|
|
156
|
-
...(f.textMask ? { textMask: f.textMask } : {}),
|
|
157
|
-
}
|
|
139
|
+
function newQuestionBankItemId() {
|
|
140
|
+
return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
158
141
|
}
|
|
159
142
|
|
|
160
|
-
function
|
|
161
|
-
|
|
143
|
+
function defaultFolderIdForColumnParent(parentId: string | null, folders: QuestionBankFolder[]): string | null {
|
|
144
|
+
if (parentId !== null) return parentId
|
|
145
|
+
const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name))
|
|
146
|
+
return roots[0]?.id ?? null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function uniqueTopics(items: QuestionBankItem[]) {
|
|
150
|
+
return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
|
|
162
151
|
}
|
|
163
152
|
|
|
164
153
|
function buildQuestionBankColumns(
|
|
@@ -167,18 +156,8 @@ function buildQuestionBankColumns(
|
|
|
167
156
|
): ColumnDef<QuestionBankItem>[] {
|
|
168
157
|
const topicOpts = uniqueTopics(items)
|
|
169
158
|
const { onToggleFavorite } = opts
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
key: "select",
|
|
173
|
-
label: "",
|
|
174
|
-
width: 40,
|
|
175
|
-
minWidth: 40,
|
|
176
|
-
defaultPin: "left",
|
|
177
|
-
lockPin: true,
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const cols: ColumnDef<QuestionBankItem>[] = [COLUMN_SELECT]
|
|
181
|
-
cols.push(
|
|
159
|
+
return [
|
|
160
|
+
{ key: "select", label: "", width: 40, minWidth: 40, defaultPin: "left", lockPin: true },
|
|
182
161
|
{
|
|
183
162
|
key: "stem",
|
|
184
163
|
label: "Question",
|
|
@@ -187,14 +166,10 @@ function buildQuestionBankColumns(
|
|
|
187
166
|
sortable: true,
|
|
188
167
|
sortKey: "stem",
|
|
189
168
|
defaultPin: "left",
|
|
190
|
-
filter: {
|
|
191
|
-
type: "text",
|
|
192
|
-
icon: "fa-file-lines",
|
|
193
|
-
operators: ["contains", "not_contains"],
|
|
194
|
-
},
|
|
169
|
+
filter: { type: "text", icon: "fa-file-lines", operators: ["contains", "not_contains"] },
|
|
195
170
|
cell: row => (
|
|
196
171
|
<div className={cn(QUESTION_BANK_FAVORITE_HOVER_GROUP, "flex min-w-0 items-start gap-2")}>
|
|
197
|
-
<div className="flex min-w-0 flex-1 flex-col gap-0.5
|
|
172
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5 pe-1">
|
|
198
173
|
<span className="line-clamp-2 text-sm font-medium text-foreground">{row.stem}</span>
|
|
199
174
|
<span className="font-mono text-xs text-muted-foreground">{row.questionId}</span>
|
|
200
175
|
</div>
|
|
@@ -209,12 +184,7 @@ function buildQuestionBankColumns(
|
|
|
209
184
|
minWidth: 120,
|
|
210
185
|
sortable: true,
|
|
211
186
|
sortKey: "topic",
|
|
212
|
-
filter: {
|
|
213
|
-
type: "select",
|
|
214
|
-
icon: "fa-layer-group",
|
|
215
|
-
operators: ["is", "is_not"],
|
|
216
|
-
options: topicOpts,
|
|
217
|
-
},
|
|
187
|
+
filter: { type: "select", icon: "fa-layer-group", operators: ["is", "is_not"], options: topicOpts },
|
|
218
188
|
cell: row => <span className="text-sm text-foreground/90">{row.topic}</span>,
|
|
219
189
|
},
|
|
220
190
|
{
|
|
@@ -224,12 +194,7 @@ function buildQuestionBankColumns(
|
|
|
224
194
|
minWidth: 120,
|
|
225
195
|
sortable: true,
|
|
226
196
|
sortKey: "type",
|
|
227
|
-
filter: {
|
|
228
|
-
type: "select",
|
|
229
|
-
icon: "fa-list-check",
|
|
230
|
-
operators: ["is", "is_not"],
|
|
231
|
-
options: TYPE_FILTER_OPTS,
|
|
232
|
-
},
|
|
197
|
+
filter: { type: "select", icon: "fa-list-check", operators: ["is", "is_not"], options: TYPE_FILTER_OPTS },
|
|
233
198
|
cell: row => <span className="text-sm text-foreground/90">{TYPE_LABEL[row.type]}</span>,
|
|
234
199
|
},
|
|
235
200
|
{
|
|
@@ -239,15 +204,8 @@ function buildQuestionBankColumns(
|
|
|
239
204
|
minWidth: 96,
|
|
240
205
|
sortable: true,
|
|
241
206
|
sortKey: "difficulty",
|
|
242
|
-
filter: {
|
|
243
|
-
|
|
244
|
-
icon: "fa-signal",
|
|
245
|
-
operators: ["is", "is_not"],
|
|
246
|
-
options: DIFFICULTY_FILTER_OPTS,
|
|
247
|
-
},
|
|
248
|
-
cell: row => (
|
|
249
|
-
<span className="text-sm text-foreground/90">{DIFFICULTY_LABEL[row.difficulty]}</span>
|
|
250
|
-
),
|
|
207
|
+
filter: { type: "select", icon: "fa-signal", operators: ["is", "is_not"], options: DIFFICULTY_FILTER_OPTS },
|
|
208
|
+
cell: row => <span className="text-sm text-foreground/90">{DIFFICULTY_LABEL[row.difficulty]}</span>,
|
|
251
209
|
},
|
|
252
210
|
{
|
|
253
211
|
key: "updatedAt",
|
|
@@ -268,11 +226,7 @@ function buildQuestionBankColumns(
|
|
|
268
226
|
minWidth: 200,
|
|
269
227
|
sortable: true,
|
|
270
228
|
sortKey: "author",
|
|
271
|
-
filter: {
|
|
272
|
-
type: "text",
|
|
273
|
-
icon: "fa-user",
|
|
274
|
-
operators: ["contains", "not_contains"],
|
|
275
|
-
},
|
|
229
|
+
filter: { type: "text", icon: "fa-user", operators: ["contains", "not_contains"] },
|
|
276
230
|
cell: row => {
|
|
277
231
|
const initials = initialsFromDisplayName(row.author)
|
|
278
232
|
return (
|
|
@@ -323,11 +277,11 @@ function buildQuestionBankColumns(
|
|
|
323
277
|
</div>
|
|
324
278
|
),
|
|
325
279
|
},
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return cols
|
|
280
|
+
]
|
|
329
281
|
}
|
|
330
282
|
|
|
283
|
+
// ─── Folder columns panel (custom multi-column miller view) ─────────────────
|
|
284
|
+
|
|
331
285
|
interface HubFolderColumnsPanelProps {
|
|
332
286
|
folders: QuestionBankFolder[]
|
|
333
287
|
rows: QuestionBankItem[]
|
|
@@ -340,14 +294,13 @@ interface HubFolderColumnsPanelProps {
|
|
|
340
294
|
type HierarchyItem = QuestionBankFolder | QuestionBankItem
|
|
341
295
|
|
|
342
296
|
function isFolder(item: HierarchyItem): item is QuestionBankFolder {
|
|
343
|
-
return
|
|
297
|
+
return "parentId" in item
|
|
344
298
|
}
|
|
345
299
|
|
|
346
300
|
function isQuestion(item: HierarchyItem): item is QuestionBankItem {
|
|
347
|
-
return
|
|
301
|
+
return "stem" in item
|
|
348
302
|
}
|
|
349
303
|
|
|
350
|
-
/** **Panel view** — multi-column folder explorer + optional detail column (Finder-style). */
|
|
351
304
|
function HubFolderColumnsPanel({
|
|
352
305
|
folders,
|
|
353
306
|
rows,
|
|
@@ -356,37 +309,25 @@ function HubFolderColumnsPanel({
|
|
|
356
309
|
onAddQuestion,
|
|
357
310
|
onCustomizeFolder,
|
|
358
311
|
}: HubFolderColumnsPanelProps) {
|
|
359
|
-
// Track the selected path through the hierarchy
|
|
360
|
-
// Initialize with first folder selected by default
|
|
361
312
|
const [selectedPath, setSelectedPath] = React.useState<HierarchyItem[]>(() => {
|
|
362
313
|
const rootFolders = folders
|
|
363
314
|
.filter(f => f.parentId === null)
|
|
364
315
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
365
|
-
if (rootFolders.length > 0)
|
|
366
|
-
return [rootFolders[0]]
|
|
367
|
-
}
|
|
316
|
+
if (rootFolders.length > 0) return [rootFolders[0]]
|
|
368
317
|
return []
|
|
369
318
|
})
|
|
370
319
|
|
|
371
|
-
// Track if this is the first render for auto-selection
|
|
372
320
|
const isFirstRenderRef = React.useRef(true)
|
|
373
321
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
379
|
-
}, [folders])
|
|
322
|
+
const rootFolders = React.useMemo(
|
|
323
|
+
() => folders.filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name)),
|
|
324
|
+
[folders],
|
|
325
|
+
)
|
|
380
326
|
|
|
381
|
-
// Handle selection at any depth
|
|
382
327
|
const handleSelect = (item: HierarchyItem, depth: number) => {
|
|
383
328
|
setSelectedPath(prev => [...prev.slice(0, depth), item])
|
|
384
329
|
}
|
|
385
330
|
|
|
386
|
-
// Auto-select first item at each level (only on first render). Intentional
|
|
387
|
-
// empty deps: we want this to run exactly once on mount; depending on the
|
|
388
|
-
// referenced values (folders / rows / selectedPath) would re-run on every
|
|
389
|
-
// edit and keep re-seeding the selection, undoing the user's choice.
|
|
390
331
|
React.useEffect(() => {
|
|
391
332
|
if (isFirstRenderRef.current && selectedPath.length > 0) {
|
|
392
333
|
const lastItem = selectedPath[selectedPath.length - 1]
|
|
@@ -395,7 +336,6 @@ function HubFolderColumnsPanel({
|
|
|
395
336
|
const subfolders = folders.filter(f => f.parentId === folder.id).sort((a, b) => a.name.localeCompare(b.name))
|
|
396
337
|
const questionsInFolder = rows.filter(r => r.folderId === folder.id)
|
|
397
338
|
const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
|
|
398
|
-
|
|
399
339
|
if (items.length > 0 && !selectedPath[selectedPath.length + 1]) {
|
|
400
340
|
setSelectedPath(prev => [...prev, items[0]])
|
|
401
341
|
isFirstRenderRef.current = false
|
|
@@ -405,53 +345,31 @@ function HubFolderColumnsPanel({
|
|
|
405
345
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
406
346
|
}, [])
|
|
407
347
|
|
|
408
|
-
// Build columns dynamically based on selected path
|
|
409
348
|
const columns: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = React.useMemo(() => {
|
|
410
349
|
const cols: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = [
|
|
411
350
|
{ items: rootFolders, depth: 0, parentId: null },
|
|
412
351
|
]
|
|
413
|
-
|
|
414
|
-
// For each selected folder in the path, add a column with its children
|
|
415
352
|
for (let i = 0; i < selectedPath.length; i++) {
|
|
416
353
|
const item = selectedPath[i]
|
|
417
354
|
if (isFolder(item)) {
|
|
418
|
-
|
|
419
|
-
const subfolders = folders
|
|
420
|
-
.filter(f => f.parentId === item.id)
|
|
421
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
422
|
-
|
|
423
|
-
// Get questions in this folder
|
|
355
|
+
const subfolders = folders.filter(f => f.parentId === item.id).sort((a, b) => a.name.localeCompare(b.name))
|
|
424
356
|
const questionsInFolder = rows.filter(r => r.folderId === item.id)
|
|
425
|
-
|
|
426
|
-
// Combine folders and questions
|
|
427
357
|
const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
|
|
428
|
-
|
|
429
|
-
if (items.length > 0) {
|
|
430
|
-
cols.push({ items, depth: i + 1, parentId: item.id })
|
|
431
|
-
}
|
|
358
|
+
if (items.length > 0) cols.push({ items, depth: i + 1, parentId: item.id })
|
|
432
359
|
}
|
|
433
360
|
}
|
|
434
|
-
|
|
435
361
|
return cols
|
|
436
362
|
}, [selectedPath, rootFolders, folders, rows])
|
|
437
363
|
|
|
438
364
|
const selectedLeaf = selectedPath.length > 0 ? selectedPath.at(-1)! : null
|
|
439
|
-
const selectedQuestion =
|
|
440
|
-
|
|
441
|
-
const selectedFolderLeaf =
|
|
442
|
-
selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as QuestionBankFolder) : null
|
|
365
|
+
const selectedQuestion = selectedLeaf && isQuestion(selectedLeaf) ? (selectedLeaf as QuestionBankItem) : null
|
|
366
|
+
const selectedFolderLeaf = selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as QuestionBankFolder) : null
|
|
443
367
|
|
|
444
368
|
return (
|
|
445
|
-
<ResizablePanelGroup
|
|
446
|
-
direction="horizontal"
|
|
447
|
-
className="flex h-full min-h-0 w-full flex-1 overflow-hidden"
|
|
448
|
-
>
|
|
449
|
-
{/* Render all columns with handles between them */}
|
|
369
|
+
<ResizablePanelGroup direction="horizontal" className="flex h-full min-h-0 w-full flex-1 overflow-hidden">
|
|
450
370
|
{columns.map(({ items, depth, parentId }, columnIdx) => (
|
|
451
371
|
<React.Fragment key={`col-${depth}`}>
|
|
452
|
-
{columnIdx > 0 &&
|
|
453
|
-
<ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
|
|
454
|
-
)}
|
|
372
|
+
{columnIdx > 0 && <ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />}
|
|
455
373
|
<ResizablePanel
|
|
456
374
|
id={`col-${depth}`}
|
|
457
375
|
defaultSize={columnIdx === 0 ? 35 : columnIdx === 1 ? 35 : 30}
|
|
@@ -468,9 +386,7 @@ function HubFolderColumnsPanel({
|
|
|
468
386
|
}
|
|
469
387
|
trailing={
|
|
470
388
|
<>
|
|
471
|
-
<span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums">
|
|
472
|
-
{items.length}
|
|
473
|
-
</span>
|
|
389
|
+
<span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums">{items.length}</span>
|
|
474
390
|
{depth < columns.length - 1 && items.length > 0 ? (
|
|
475
391
|
<div className="flex shrink-0 items-center gap-0.5">
|
|
476
392
|
<Tooltip>
|
|
@@ -508,38 +424,23 @@ function HubFolderColumnsPanel({
|
|
|
508
424
|
</>
|
|
509
425
|
}
|
|
510
426
|
/>
|
|
511
|
-
|
|
512
|
-
{/* Scrollable Items List */}
|
|
513
427
|
<div className="min-h-0 flex-1 overflow-y-auto py-1">
|
|
514
428
|
{items.map(item => {
|
|
515
429
|
const isSelected = selectedPath[depth]?.id === item.id
|
|
516
430
|
const isFolder_ = isFolder(item)
|
|
517
431
|
const folder = isFolder_ ? item : null
|
|
518
432
|
const question = isQuestion(item) ? item : null
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const subfolderCount = isFolder_
|
|
522
|
-
? folders.filter(f => f.parentId === item.id).length
|
|
523
|
-
: 0
|
|
524
|
-
const questionCount = isFolder_
|
|
525
|
-
? rows.filter(r => r.folderId === item.id).length
|
|
526
|
-
: 0
|
|
433
|
+
const subfolderCount = isFolder_ ? folders.filter(f => f.parentId === item.id).length : 0
|
|
434
|
+
const questionCount = isFolder_ ? rows.filter(r => r.folderId === item.id).length : 0
|
|
527
435
|
const itemCount = subfolderCount + questionCount
|
|
528
|
-
|
|
529
436
|
return (
|
|
530
|
-
<div
|
|
531
|
-
key={item.id}
|
|
532
|
-
className="group flex items-center hover:bg-muted/50"
|
|
533
|
-
>
|
|
437
|
+
<div key={item.id} className="group flex items-center hover:bg-muted/50">
|
|
534
438
|
<button
|
|
535
439
|
onClick={() => handleSelect(item, depth)}
|
|
536
440
|
className={cn(
|
|
537
441
|
"flex flex-1 items-center gap-3 px-3 py-2 text-left text-sm transition-colors duration-75",
|
|
538
442
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
539
|
-
isSelected
|
|
540
|
-
? "bg-accent text-accent-foreground"
|
|
541
|
-
: "text-foreground",
|
|
542
|
-
// Apply folder background color if it's a folder and not selected, but NOT in the first column
|
|
443
|
+
isSelected ? "bg-accent text-accent-foreground" : "text-foreground",
|
|
543
444
|
isFolder_ && !isSelected && folder?.colorKey && depth > 0
|
|
544
445
|
? QUESTION_BANK_FOLDER_COLOR_STYLES[folder.colorKey]?.tile
|
|
545
446
|
: "",
|
|
@@ -547,36 +448,34 @@ function HubFolderColumnsPanel({
|
|
|
547
448
|
aria-selected={isSelected}
|
|
548
449
|
role="option"
|
|
549
450
|
>
|
|
550
|
-
{/* Icon - show for folders and questions */}
|
|
551
451
|
{isFolder_ ? (
|
|
552
|
-
<i
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
452
|
+
<i
|
|
453
|
+
className={cn(
|
|
454
|
+
"fa-folder text-sm shrink-0",
|
|
455
|
+
isSelected ? "fa-solid" : "fa-light",
|
|
456
|
+
folder?.colorKey && QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey],
|
|
457
|
+
)}
|
|
458
|
+
aria-hidden="true"
|
|
459
|
+
/>
|
|
558
460
|
) : (
|
|
559
461
|
<i className={cn("fa-file text-sm shrink-0", isSelected ? "fa-solid" : "fa-light")} aria-hidden="true" />
|
|
560
462
|
)}
|
|
561
|
-
|
|
562
|
-
{/* Name */}
|
|
563
|
-
<span className={cn(
|
|
564
|
-
"min-w-0 flex-1 truncate leading-tight",
|
|
565
|
-
isSelected && "font-medium"
|
|
566
|
-
)}>
|
|
463
|
+
<span className={cn("min-w-0 flex-1 truncate leading-tight", isSelected && "font-medium")}>
|
|
567
464
|
{isFolder_ ? folder?.name : question?.stem}
|
|
568
465
|
</span>
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
{isFolder_
|
|
466
|
+
<span
|
|
467
|
+
className={cn(
|
|
468
|
+
"shrink-0 tabular-nums text-xs ms-auto",
|
|
469
|
+
isSelected ? "text-accent-foreground/70" : "text-muted-foreground",
|
|
470
|
+
)}
|
|
471
|
+
>
|
|
472
|
+
{isFolder_
|
|
473
|
+
? itemCount
|
|
474
|
+
: question?.type === "multiple_choice"
|
|
475
|
+
? "MCQ"
|
|
476
|
+
: question?.difficulty?.charAt(0).toUpperCase()}
|
|
576
477
|
</span>
|
|
577
478
|
</button>
|
|
578
|
-
|
|
579
|
-
{/* Folder actions menu - only for folders */}
|
|
580
479
|
{isFolder_ && folder && (
|
|
581
480
|
<DropdownMenu>
|
|
582
481
|
<DropdownMenuTrigger asChild>
|
|
@@ -605,8 +504,6 @@ function HubFolderColumnsPanel({
|
|
|
605
504
|
</ResizablePanel>
|
|
606
505
|
</React.Fragment>
|
|
607
506
|
))}
|
|
608
|
-
|
|
609
|
-
{/* Details panel — question (summary) or folder (aggregates) */}
|
|
610
507
|
{(selectedQuestion || selectedFolderLeaf) && (
|
|
611
508
|
<>
|
|
612
509
|
<ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
|
|
@@ -620,11 +517,7 @@ function HubFolderColumnsPanel({
|
|
|
620
517
|
</>
|
|
621
518
|
) : selectedFolderLeaf ? (
|
|
622
519
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
623
|
-
<FolderDetailsShell
|
|
624
|
-
folder={selectedFolderLeaf}
|
|
625
|
-
folders={folders}
|
|
626
|
-
questions={rows}
|
|
627
|
-
/>
|
|
520
|
+
<FolderDetailsShell folder={selectedFolderLeaf} folders={folders} questions={rows} />
|
|
628
521
|
</div>
|
|
629
522
|
) : null}
|
|
630
523
|
</ResizablePanel>
|
|
@@ -634,160 +527,10 @@ function HubFolderColumnsPanel({
|
|
|
634
527
|
)
|
|
635
528
|
}
|
|
636
529
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
export const QuestionBankTable = React.forwardRef<
|
|
640
|
-
QuestionBankTableHandle,
|
|
641
|
-
{
|
|
642
|
-
items: QuestionBankItem[]
|
|
643
|
-
/** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
|
|
644
|
-
navState?: QuestionBankNavState
|
|
645
|
-
/** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
|
|
646
|
-
urlListSearch?: string
|
|
647
|
-
/** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
|
|
648
|
-
searchLanding?: boolean
|
|
649
|
-
/** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
|
|
650
|
-
landingFilters?: QuestionBankLandingFilterState | null
|
|
651
|
-
view?: DataListViewType
|
|
652
|
-
onViewChange?: (v: DataListViewType) => void
|
|
653
|
-
folders: QuestionBankFolder[]
|
|
654
|
-
onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
|
|
655
|
-
onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
|
|
656
|
-
}
|
|
657
|
-
>(function QuestionBankTable(
|
|
658
|
-
{
|
|
659
|
-
items,
|
|
660
|
-
navState,
|
|
661
|
-
urlListSearch,
|
|
662
|
-
searchLanding,
|
|
663
|
-
landingFilters,
|
|
664
|
-
view = "table",
|
|
665
|
-
onViewChange,
|
|
666
|
-
folders,
|
|
667
|
-
onFoldersChange,
|
|
668
|
-
onItemsChange,
|
|
669
|
-
},
|
|
670
|
-
ref,
|
|
671
|
-
) {
|
|
672
|
-
const tableSourceItems = React.useMemo(() => {
|
|
673
|
-
const nav = navState ?? { scope: "all" as const, folderId: null }
|
|
674
|
-
const landing = searchLanding ? (landingFilters ?? null) : null
|
|
675
|
-
return applyQuestionBankHubDisplayFilters(items, folders, nav, landing)
|
|
676
|
-
}, [items, folders, navState, searchLanding, landingFilters])
|
|
677
|
-
|
|
678
|
-
const toggleFavorite = React.useCallback(
|
|
679
|
-
(row: QuestionBankItem) => {
|
|
680
|
-
onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleQuestionBankItemFavorite(r) : r)))
|
|
681
|
-
},
|
|
682
|
-
[onItemsChange],
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
const columns = React.useMemo(
|
|
686
|
-
() => buildQuestionBankColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
|
|
687
|
-
[tableSourceItems, toggleFavorite],
|
|
688
|
-
)
|
|
689
|
-
const filterFields = React.useMemo(() => columnsToFilterFields(columns), [columns])
|
|
690
|
-
const fieldDefinitionsForDrawer = React.useMemo(
|
|
691
|
-
() =>
|
|
692
|
-
columns
|
|
693
|
-
.filter(c => c.key !== "select" && c.key !== "favorite" && c.key !== "actions")
|
|
694
|
-
.map(c => ({ key: c.key, label: c.label, sortable: !!(c.sortable && (c.sortKey ?? c.key)) })),
|
|
695
|
-
[columns],
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
const resolveColumnLabel = React.useCallback(
|
|
699
|
-
(key: string) => columns.find(c => c.key === key)?.label ?? key,
|
|
700
|
-
[columns],
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
const [displayOptions, setDisplayOptions] = React.useState<DataListDisplayOptions>(DEFAULT_DATA_LIST_DISPLAY_OPTIONS)
|
|
704
|
-
const patchDisplay = React.useCallback((patch: Partial<DataListDisplayOptions>) => {
|
|
705
|
-
setDisplayOptions(prev => ({ ...prev, ...patch }))
|
|
706
|
-
}, [])
|
|
707
|
-
|
|
708
|
-
const [newFolderOpen, setNewFolderOpen] = React.useState(false)
|
|
709
|
-
const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
|
|
710
|
-
const [customizingFolder, setCustomizingFolder] = React.useState<QuestionBankFolder | null>(null)
|
|
711
|
-
|
|
712
|
-
const [conditionalRules, setConditionalRules] = React.useState<ConditionalRule[]>([])
|
|
713
|
-
const addConditionalRule = React.useCallback((rule: Omit<ConditionalRule, "id">) => {
|
|
714
|
-
setConditionalRules(prev => [...prev, { ...rule, id: `cr-${Date.now()}` }])
|
|
715
|
-
}, [])
|
|
716
|
-
const removeConditionalRule = React.useCallback((id: string) => {
|
|
717
|
-
setConditionalRules(prev => prev.filter(r => r.id !== id))
|
|
718
|
-
}, [])
|
|
719
|
-
const updateConditionalRule = React.useCallback((id: string, patch: Partial<ConditionalRule>) => {
|
|
720
|
-
setConditionalRules(prev => prev.map(r => r.id === id ? { ...r, ...patch } : r))
|
|
721
|
-
}, [])
|
|
722
|
-
|
|
723
|
-
const tableState = useTableState(
|
|
724
|
-
tableSourceItems,
|
|
725
|
-
columns,
|
|
726
|
-
{ key: "updatedAt", dir: "desc" },
|
|
727
|
-
undefined,
|
|
728
|
-
searchLanding ? undefined : urlListSearch,
|
|
729
|
-
)
|
|
730
|
-
|
|
731
|
-
const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
|
|
732
|
-
setNewFolderParentId(parentId)
|
|
733
|
-
setCustomizingFolder(null)
|
|
734
|
-
setNewFolderOpen(true)
|
|
735
|
-
}, [])
|
|
736
|
-
|
|
737
|
-
const openCustomizeFolderSheet = React.useCallback((folder: QuestionBankFolder) => {
|
|
738
|
-
setCustomizingFolder(folder)
|
|
739
|
-
setNewFolderOpen(true)
|
|
740
|
-
}, [])
|
|
741
|
-
|
|
742
|
-
const addQuestionInColumn = React.useCallback(
|
|
743
|
-
(parentId: string | null) => {
|
|
744
|
-
const folderId = defaultFolderIdForColumnParent(parentId, folders)
|
|
745
|
-
if (!folderId) return
|
|
746
|
-
const today = new Date()
|
|
747
|
-
const y = today.getFullYear()
|
|
748
|
-
const m = String(today.getMonth() + 1).padStart(2, "0")
|
|
749
|
-
const d = String(today.getDate()).padStart(2, "0")
|
|
750
|
-
onItemsChange(prev => [
|
|
751
|
-
...prev,
|
|
752
|
-
{
|
|
753
|
-
id: newQuestionBankItemId(),
|
|
754
|
-
questionId: newQuestionBankQuestionId(),
|
|
755
|
-
stem: "New question",
|
|
756
|
-
topic: "General",
|
|
757
|
-
type: "short_answer",
|
|
758
|
-
difficulty: "medium",
|
|
759
|
-
author: "Demo user",
|
|
760
|
-
authorEmail: "demo.user@demo.exxat.io",
|
|
761
|
-
updatedAt: `${y}-${m}-${d}`,
|
|
762
|
-
folderId,
|
|
763
|
-
},
|
|
764
|
-
])
|
|
765
|
-
},
|
|
766
|
-
[folders, onItemsChange],
|
|
767
|
-
)
|
|
768
|
-
|
|
769
|
-
const renderFilterOptionValue = React.useCallback(
|
|
770
|
-
(fieldKey: string, value: string): React.ReactNode => {
|
|
771
|
-
const col = columns.find(c => c.key === fieldKey)
|
|
772
|
-
const opt = col?.filter?.options?.find(o => o.value === value)
|
|
773
|
-
return <span className="text-foreground">{opt?.label ?? value}</span>
|
|
774
|
-
},
|
|
775
|
-
[columns],
|
|
776
|
-
)
|
|
777
|
-
|
|
778
|
-
React.useImperativeHandle(ref, () => ({
|
|
779
|
-
openPropertiesDrawer: () => {
|
|
780
|
-
tableState.setSheetOpen(true)
|
|
781
|
-
},
|
|
782
|
-
}), [tableState])
|
|
783
|
-
|
|
784
|
-
const questionBankBoardGroupKey = QUESTION_BANK_BOARD_GROUP_OPTIONS.some(
|
|
785
|
-
o => o.key === displayOptions.boardGroupByColumnKey,
|
|
786
|
-
)
|
|
787
|
-
? displayOptions.boardGroupByColumnKey
|
|
788
|
-
: "topic"
|
|
530
|
+
// ─── Detail renderer reused by panel + tree-panel ───────────────────────────
|
|
789
531
|
|
|
790
|
-
|
|
532
|
+
function questionBankPanelDetail(row: QuestionBankItem) {
|
|
533
|
+
return (
|
|
791
534
|
<div className="flex min-w-0 flex-col gap-4">
|
|
792
535
|
<div>
|
|
793
536
|
<h3 className="text-sm font-semibold text-foreground mb-2">Question</h3>
|
|
@@ -819,14 +562,11 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
819
562
|
<span className="text-xs font-medium text-muted-foreground mt-0.5 shrink-0">
|
|
820
563
|
{String.fromCharCode(65 + idx)}.
|
|
821
564
|
</span>
|
|
822
|
-
<span className={cn(
|
|
823
|
-
"text-sm",
|
|
824
|
-
option.isCorrect ? "text-foreground font-medium" : "text-foreground/80"
|
|
825
|
-
)}>
|
|
565
|
+
<span className={cn("text-sm", option.isCorrect ? "text-foreground font-medium" : "text-foreground/80")}>
|
|
826
566
|
{option.text}
|
|
827
567
|
</span>
|
|
828
568
|
{option.isCorrect && (
|
|
829
|
-
<i className="fa-light fa-check text-emerald-600 text-sm
|
|
569
|
+
<i className="fa-light fa-check text-emerald-600 text-sm ms-auto shrink-0" aria-hidden="true" />
|
|
830
570
|
)}
|
|
831
571
|
</div>
|
|
832
572
|
))}
|
|
@@ -835,190 +575,232 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
835
575
|
)}
|
|
836
576
|
</div>
|
|
837
577
|
)
|
|
578
|
+
}
|
|
838
579
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
580
|
+
// ─── Public component ───────────────────────────────────────────────────────
|
|
581
|
+
|
|
582
|
+
export type QuestionBankTableHandle = HubTableHandle
|
|
583
|
+
|
|
584
|
+
export interface QuestionBankTableProps {
|
|
585
|
+
items: QuestionBankItem[]
|
|
586
|
+
/** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
|
|
587
|
+
navState?: QuestionBankNavState
|
|
588
|
+
/** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
|
|
589
|
+
urlListSearch?: string
|
|
590
|
+
/** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
|
|
591
|
+
searchLanding?: boolean
|
|
592
|
+
/** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
|
|
593
|
+
landingFilters?: QuestionBankLandingFilterState | null
|
|
594
|
+
view?: DataListViewType
|
|
595
|
+
onViewChange?: (v: DataListViewType) => void
|
|
596
|
+
folders: QuestionBankFolder[]
|
|
597
|
+
onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
|
|
598
|
+
onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
|
|
599
|
+
}
|
|
856
600
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
renderFilterOptionValue,
|
|
871
|
-
toolbarSlot: (s: ReturnType<typeof useTableState<QuestionBankItem>>) => (
|
|
872
|
-
<TablePropertiesDrawerButton {...drawerToolbarProps} state={s} />
|
|
873
|
-
),
|
|
874
|
-
bulkActionsSlot: (selected: Set<string | number>) => {
|
|
875
|
-
const n = selected.size
|
|
876
|
-
if (n === 0) return null
|
|
877
|
-
return (
|
|
878
|
-
<>
|
|
879
|
-
<span className="sr-only">{n} selected</span>
|
|
880
|
-
<Tip label="Export selection (demo)">
|
|
881
|
-
<Button size="sm" variant="outline" type="button">
|
|
882
|
-
<i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
|
|
883
|
-
Export
|
|
884
|
-
</Button>
|
|
885
|
-
</Tip>
|
|
886
|
-
</>
|
|
887
|
-
)
|
|
601
|
+
export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, QuestionBankTableProps>(
|
|
602
|
+
function QuestionBankTable(
|
|
603
|
+
{
|
|
604
|
+
items,
|
|
605
|
+
navState,
|
|
606
|
+
urlListSearch,
|
|
607
|
+
searchLanding,
|
|
608
|
+
landingFilters,
|
|
609
|
+
view = "table",
|
|
610
|
+
onViewChange,
|
|
611
|
+
folders,
|
|
612
|
+
onFoldersChange,
|
|
613
|
+
onItemsChange,
|
|
888
614
|
},
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
if (view === "table") {
|
|
903
|
-
return (
|
|
904
|
-
<div className="pb-6">
|
|
905
|
-
<DataTable<QuestionBankItem> {...tableProps} />
|
|
906
|
-
</div>
|
|
615
|
+
ref,
|
|
616
|
+
) {
|
|
617
|
+
const tableSourceItems = React.useMemo(() => {
|
|
618
|
+
const nav = navState ?? { scope: "all" as const, folderId: null }
|
|
619
|
+
const landing = searchLanding ? (landingFilters ?? null) : null
|
|
620
|
+
return applyQuestionBankHubDisplayFilters(items, folders, nav, landing)
|
|
621
|
+
}, [items, folders, navState, searchLanding, landingFilters])
|
|
622
|
+
|
|
623
|
+
const toggleFavorite = React.useCallback(
|
|
624
|
+
(row: QuestionBankItem) => {
|
|
625
|
+
onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleQuestionBankItemFavorite(r) : r)))
|
|
626
|
+
},
|
|
627
|
+
[onItemsChange],
|
|
907
628
|
)
|
|
908
|
-
}
|
|
909
629
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
{sharedToolbar}
|
|
914
|
-
<QuestionBankListView
|
|
915
|
-
rows={tableState.rows as QuestionBankItem[]}
|
|
916
|
-
onToggleFavorite={toggleFavorite}
|
|
917
|
-
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
918
|
-
/>
|
|
919
|
-
</div>
|
|
630
|
+
const columns = React.useMemo(
|
|
631
|
+
() => buildQuestionBankColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
|
|
632
|
+
[tableSourceItems, toggleFavorite],
|
|
920
633
|
)
|
|
921
|
-
}
|
|
922
634
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
635
|
+
// ─ New-folder / customize-folder modal state (shared by panel + tree-panel) ────
|
|
636
|
+
const [newFolderOpen, setNewFolderOpen] = React.useState(false)
|
|
637
|
+
const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
|
|
638
|
+
const [customizingFolder, setCustomizingFolder] = React.useState<QuestionBankFolder | null>(null)
|
|
639
|
+
|
|
640
|
+
const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
|
|
641
|
+
setNewFolderParentId(parentId)
|
|
642
|
+
setCustomizingFolder(null)
|
|
643
|
+
setNewFolderOpen(true)
|
|
644
|
+
}, [])
|
|
645
|
+
|
|
646
|
+
const openCustomizeFolderSheet = React.useCallback((folder: QuestionBankFolder) => {
|
|
647
|
+
setCustomizingFolder(folder)
|
|
648
|
+
setNewFolderOpen(true)
|
|
649
|
+
}, [])
|
|
650
|
+
|
|
651
|
+
const addQuestionInColumn = React.useCallback(
|
|
652
|
+
(parentId: string | null) => {
|
|
653
|
+
const folderId = defaultFolderIdForColumnParent(parentId, folders)
|
|
654
|
+
if (!folderId) return
|
|
655
|
+
const today = new Date()
|
|
656
|
+
const y = today.getFullYear()
|
|
657
|
+
const m = String(today.getMonth() + 1).padStart(2, "0")
|
|
658
|
+
const d = String(today.getDate()).padStart(2, "0")
|
|
659
|
+
onItemsChange(prev => [
|
|
660
|
+
...prev,
|
|
661
|
+
{
|
|
662
|
+
id: newQuestionBankItemId(),
|
|
663
|
+
questionId: newQuestionBankQuestionId(),
|
|
664
|
+
stem: "New question",
|
|
665
|
+
topic: "General",
|
|
666
|
+
type: "short_answer",
|
|
667
|
+
difficulty: "medium",
|
|
668
|
+
author: "Demo user",
|
|
669
|
+
authorEmail: "demo.user@demo.exxat.io",
|
|
670
|
+
updatedAt: `${y}-${m}-${d}`,
|
|
671
|
+
folderId,
|
|
672
|
+
},
|
|
673
|
+
])
|
|
674
|
+
},
|
|
675
|
+
[folders, onItemsChange],
|
|
934
676
|
)
|
|
935
|
-
}
|
|
936
677
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
<
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
questions={tableState.rows as QuestionBankItem[]}
|
|
945
|
-
onQuestionsChange={onItemsChange}
|
|
946
|
-
/>
|
|
947
|
-
</div>
|
|
678
|
+
const renderFilterOptionValue = React.useCallback(
|
|
679
|
+
(fieldKey: string, value: string): React.ReactNode => {
|
|
680
|
+
const col = columns.find(c => c.key === fieldKey)
|
|
681
|
+
const opt = col?.filter?.options?.find(o => o.value === value)
|
|
682
|
+
return <span className="text-foreground">{opt?.label ?? value}</span>
|
|
683
|
+
},
|
|
684
|
+
[columns],
|
|
948
685
|
)
|
|
949
|
-
}
|
|
950
686
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
687
|
+
// ─ Renderers ──────────────────────────────────────────────────────────────
|
|
688
|
+
const renderers: HubTableRenderers<QuestionBankItem> = {
|
|
689
|
+
"board-with-toolbar": ({ state, toolbarShell, displayOptions }) => {
|
|
690
|
+
const boardGroupKey = QUESTION_BANK_BOARD_GROUP_OPTIONS.some(
|
|
691
|
+
o => o.key === displayOptions.boardGroupByColumnKey,
|
|
692
|
+
)
|
|
693
|
+
? displayOptions.boardGroupByColumnKey
|
|
694
|
+
: "topic"
|
|
695
|
+
return toolbarShell(
|
|
696
|
+
<QuestionBankBoardView
|
|
697
|
+
rows={state.rows as QuestionBankItem[]}
|
|
698
|
+
groupByColumnKey={boardGroupKey}
|
|
699
|
+
onToggleFavorite={toggleFavorite}
|
|
700
|
+
onRowActivate={row => state.toggleRow(row.id)}
|
|
701
|
+
/>,
|
|
702
|
+
)
|
|
703
|
+
},
|
|
704
|
+
"folder-with-toolbar": ({ state, toolbarShell }) =>
|
|
705
|
+
toolbarShell(
|
|
706
|
+
<QuestionBankOsFolderView
|
|
707
|
+
folders={folders}
|
|
708
|
+
onFoldersChange={onFoldersChange}
|
|
709
|
+
questions={state.rows as QuestionBankItem[]}
|
|
710
|
+
onQuestionsChange={onItemsChange}
|
|
711
|
+
/>,
|
|
712
|
+
),
|
|
713
|
+
"panel-with-toolbar": ({ state, toolbarShell }) =>
|
|
714
|
+
toolbarShell(
|
|
956
715
|
<ListPageSplitHubChrome aria-label="Question bank folder columns">
|
|
957
716
|
<HubFolderColumnsPanel
|
|
958
717
|
folders={folders}
|
|
959
|
-
rows={
|
|
960
|
-
panelRenderDetail={
|
|
718
|
+
rows={state.rows as QuestionBankItem[]}
|
|
719
|
+
panelRenderDetail={questionBankPanelDetail}
|
|
961
720
|
onAddFolder={openNewFolderForColumn}
|
|
962
721
|
onAddQuestion={addQuestionInColumn}
|
|
963
722
|
onCustomizeFolder={openCustomizeFolderSheet}
|
|
964
723
|
/>
|
|
965
|
-
</ListPageSplitHubChrome
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
onOpenChange={setNewFolderOpen}
|
|
970
|
-
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
971
|
-
customizingFolder={customizingFolder}
|
|
972
|
-
onCreated={(newFolder) => {
|
|
973
|
-
if (customizingFolder) {
|
|
974
|
-
// Update existing folder
|
|
975
|
-
onFoldersChange(prev =>
|
|
976
|
-
prev.map(f =>
|
|
977
|
-
f.id === customizingFolder.id
|
|
978
|
-
? {
|
|
979
|
-
...f,
|
|
980
|
-
name: newFolder.name,
|
|
981
|
-
icon: newFolder.icon,
|
|
982
|
-
colorKey: newFolder.colorKey,
|
|
983
|
-
}
|
|
984
|
-
: f,
|
|
985
|
-
),
|
|
986
|
-
)
|
|
987
|
-
setCustomizingFolder(null)
|
|
988
|
-
} else {
|
|
989
|
-
// Create new folder
|
|
990
|
-
onFoldersChange(prev => [
|
|
991
|
-
...prev,
|
|
992
|
-
{
|
|
993
|
-
id: `fld-${Date.now()}`,
|
|
994
|
-
name: newFolder.name,
|
|
995
|
-
icon: newFolder.icon,
|
|
996
|
-
colorKey: newFolder.colorKey,
|
|
997
|
-
parentId: newFolder.parentId,
|
|
998
|
-
},
|
|
999
|
-
])
|
|
1000
|
-
}
|
|
1001
|
-
setNewFolderOpen(false)
|
|
1002
|
-
}}
|
|
1003
|
-
/>
|
|
1004
|
-
</>
|
|
1005
|
-
)
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
if (view === "tree-panel") {
|
|
1009
|
-
return (
|
|
1010
|
-
<>
|
|
1011
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
1012
|
-
{sharedToolbar}
|
|
724
|
+
</ListPageSplitHubChrome>,
|
|
725
|
+
),
|
|
726
|
+
"tree-panel-with-toolbar": ({ state, toolbarShell }) =>
|
|
727
|
+
toolbarShell(
|
|
1013
728
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
1014
729
|
<HubTreePanelView
|
|
1015
|
-
items={
|
|
730
|
+
items={state.rows as QuestionBankItem[]}
|
|
1016
731
|
folders={folders}
|
|
1017
732
|
onItemsChange={onItemsChange}
|
|
1018
733
|
onFoldersChange={onFoldersChange}
|
|
1019
734
|
/>
|
|
1020
|
-
</div
|
|
735
|
+
</div>,
|
|
736
|
+
),
|
|
737
|
+
"dashboard-with-toolbar": ({ state, toolbar }) => (
|
|
738
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
739
|
+
{toolbar}
|
|
740
|
+
<QuestionBankDashboardChartsSection rows={state.rows as QuestionBankItem[]} />
|
|
1021
741
|
</div>
|
|
742
|
+
),
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return (
|
|
746
|
+
<>
|
|
747
|
+
<HubTable<QuestionBankItem>
|
|
748
|
+
rows={tableSourceItems}
|
|
749
|
+
columns={columns}
|
|
750
|
+
view={view}
|
|
751
|
+
onViewChange={onViewChange}
|
|
752
|
+
supportedViewTypes={QUESTION_BANK_SUPPORTED_VIEWS}
|
|
753
|
+
hubLabel="Question bank"
|
|
754
|
+
lifecycleTabLabel="Question bank"
|
|
755
|
+
searchAriaLabel="Search questions"
|
|
756
|
+
getRowId={row => row.id}
|
|
757
|
+
getRowSelectionLabel={row => row.stem}
|
|
758
|
+
defaultSort={{ key: "updatedAt", dir: "desc" }}
|
|
759
|
+
emptyState={<p className="text-sm text-muted-foreground">No questions in the bank.</p>}
|
|
760
|
+
boardGroupByColumnOptions={[...QUESTION_BANK_BOARD_GROUP_OPTIONS]}
|
|
761
|
+
renderFilterOptionValue={renderFilterOptionValue}
|
|
762
|
+
syncedSearchFromUrl={searchLanding ? undefined : urlListSearch}
|
|
763
|
+
listAriaLabel="Questions"
|
|
764
|
+
listEmptyState="No questions match your filters."
|
|
765
|
+
renderListRow={row => (
|
|
766
|
+
<ListPageBoardCard
|
|
767
|
+
className={QUESTION_BANK_FAVORITE_HOVER_GROUP}
|
|
768
|
+
layout="row"
|
|
769
|
+
rowContainerClassName="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:gap-4"
|
|
770
|
+
rowEnd={
|
|
771
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
772
|
+
<QuestionBankFavoriteButton row={row} onToggleFavorite={toggleFavorite} />
|
|
773
|
+
<i className="fa-light fa-chevron-right text-xs text-muted-foreground" aria-hidden="true" />
|
|
774
|
+
</div>
|
|
775
|
+
}
|
|
776
|
+
>
|
|
777
|
+
<div className="space-y-0.5">
|
|
778
|
+
<p className="line-clamp-2 text-sm font-semibold text-foreground">{row.stem}</p>
|
|
779
|
+
<p className="font-mono text-xs text-muted-foreground">{row.questionId}</p>
|
|
780
|
+
<p className="text-xs text-muted-foreground">
|
|
781
|
+
{row.topic} · Updated {formatDateUS(row.updatedAt)}
|
|
782
|
+
</p>
|
|
783
|
+
<p className="text-xs text-muted-foreground">{row.author}</p>
|
|
784
|
+
</div>
|
|
785
|
+
</ListPageBoardCard>
|
|
786
|
+
)}
|
|
787
|
+
bulkActionsSlot={selected => {
|
|
788
|
+
if (selected.size === 0) return null
|
|
789
|
+
return (
|
|
790
|
+
<>
|
|
791
|
+
<span className="sr-only">{selected.size} selected</span>
|
|
792
|
+
<Tip label="Export selection (demo)">
|
|
793
|
+
<Button size="sm" variant="outline" type="button">
|
|
794
|
+
<i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
|
|
795
|
+
Export
|
|
796
|
+
</Button>
|
|
797
|
+
</Tip>
|
|
798
|
+
</>
|
|
799
|
+
)
|
|
800
|
+
}}
|
|
801
|
+
renderers={renderers}
|
|
802
|
+
handleRef={ref}
|
|
803
|
+
/>
|
|
1022
804
|
<QuestionBankNewFolderSheet
|
|
1023
805
|
open={newFolderOpen}
|
|
1024
806
|
onOpenChange={setNewFolderOpen}
|
|
@@ -1026,22 +808,15 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1026
808
|
customizingFolder={customizingFolder}
|
|
1027
809
|
onCreated={(newFolder) => {
|
|
1028
810
|
if (customizingFolder) {
|
|
1029
|
-
// Update existing folder
|
|
1030
811
|
onFoldersChange(prev =>
|
|
1031
812
|
prev.map(f =>
|
|
1032
813
|
f.id === customizingFolder.id
|
|
1033
|
-
? {
|
|
1034
|
-
...f,
|
|
1035
|
-
name: newFolder.name,
|
|
1036
|
-
icon: newFolder.icon,
|
|
1037
|
-
colorKey: newFolder.colorKey,
|
|
1038
|
-
}
|
|
814
|
+
? { ...f, name: newFolder.name, icon: newFolder.icon, colorKey: newFolder.colorKey }
|
|
1039
815
|
: f,
|
|
1040
816
|
),
|
|
1041
817
|
)
|
|
1042
818
|
setCustomizingFolder(null)
|
|
1043
819
|
} else {
|
|
1044
|
-
// Create new folder
|
|
1045
820
|
onFoldersChange(prev => [
|
|
1046
821
|
...prev,
|
|
1047
822
|
{
|
|
@@ -1058,14 +833,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1058
833
|
/>
|
|
1059
834
|
</>
|
|
1060
835
|
)
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
return (
|
|
1064
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
1065
|
-
{sharedToolbar}
|
|
1066
|
-
<QuestionBankDashboardChartsSection rows={tableState.rows as QuestionBankItem[]} />
|
|
1067
|
-
</div>
|
|
1068
|
-
)
|
|
1069
|
-
})
|
|
836
|
+
},
|
|
837
|
+
)
|
|
1070
838
|
|
|
1071
839
|
QuestionBankTable.displayName = "QuestionBankTable"
|