@exxatdesignux/ui 0.3.0 → 0.4.1
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 +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +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-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- 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 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- 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 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- 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 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- 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/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- package/template/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Library folder tree (mock) — OS-style icon folders with appearance + hierarchy.
|
|
3
3
|
* Production: replace with API + optimistic updates.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type LibraryFolderColorKey =
|
|
7
7
|
| "brand"
|
|
8
8
|
| "success"
|
|
9
9
|
| "warning"
|
|
@@ -13,19 +13,19 @@ export type QuestionBankFolderColorKey =
|
|
|
13
13
|
| "chart2"
|
|
14
14
|
| "chart3"
|
|
15
15
|
|
|
16
|
-
export interface
|
|
16
|
+
export interface LibraryFolder {
|
|
17
17
|
id: string
|
|
18
18
|
name: string
|
|
19
19
|
/** `null` = top-level folder */
|
|
20
20
|
parentId: string | null
|
|
21
21
|
/** Font Awesome icon without weight prefix (e.g. `fa-folder`, `fa-flask`). */
|
|
22
22
|
icon: string
|
|
23
|
-
colorKey:
|
|
23
|
+
colorKey: LibraryFolderColorKey
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Tile + icon tint classes (semantic tokens). */
|
|
27
|
-
export const
|
|
28
|
-
|
|
27
|
+
export const LIBRARY_FOLDER_COLOR_STYLES: Record<
|
|
28
|
+
LibraryFolderColorKey,
|
|
29
29
|
{ tile: string; iconWrap: string; icon: string }
|
|
30
30
|
> = {
|
|
31
31
|
brand: {
|
|
@@ -71,7 +71,7 @@ export const QUESTION_BANK_FOLDER_COLOR_STYLES: Record<
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/** Icon color classes using Tailwind — for use in text-based contexts (list views, panels). */
|
|
74
|
-
export const
|
|
74
|
+
export const LIBRARY_FOLDER_ICON_COLORS: Record<LibraryFolderColorKey, string> = {
|
|
75
75
|
brand: "text-orange-600 dark:text-orange-400",
|
|
76
76
|
success: "text-emerald-600 dark:text-emerald-400",
|
|
77
77
|
warning: "text-amber-600 dark:text-amber-400",
|
|
@@ -83,25 +83,25 @@ export const QUESTION_BANK_FOLDER_ICON_COLORS: Record<QuestionBankFolderColorKey
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** Preset icons for folder appearance picker. */
|
|
86
|
-
export const
|
|
86
|
+
export const LIBRARY_FOLDER_ICON_OPTIONS: readonly string[] = [
|
|
87
87
|
"fa-folder",
|
|
88
88
|
"fa-folder-open",
|
|
89
89
|
"fa-book",
|
|
90
|
-
"fa-
|
|
91
|
-
"fa-
|
|
92
|
-
"fa-
|
|
93
|
-
"fa-
|
|
94
|
-
"fa-
|
|
90
|
+
"fa-bookmark",
|
|
91
|
+
"fa-box",
|
|
92
|
+
"fa-box-archive",
|
|
93
|
+
"fa-tag",
|
|
94
|
+
"fa-flag",
|
|
95
|
+
"fa-star",
|
|
95
96
|
"fa-file-lines",
|
|
96
97
|
"fa-layer-group",
|
|
97
98
|
"fa-clipboard-check",
|
|
98
|
-
"fa-
|
|
99
|
-
"fa-
|
|
100
|
-
"fa-
|
|
101
|
-
"fa-notes-medical",
|
|
99
|
+
"fa-list-check",
|
|
100
|
+
"fa-grid-2",
|
|
101
|
+
"fa-folder-tree",
|
|
102
102
|
] as const
|
|
103
103
|
|
|
104
|
-
export const
|
|
104
|
+
export const DEFAULT_LIBRARY_FOLDERS: LibraryFolder[] = [
|
|
105
105
|
{
|
|
106
106
|
id: "fld-favorites",
|
|
107
107
|
name: "Favorites",
|
|
@@ -111,37 +111,37 @@ export const DEFAULT_QUESTION_BANK_FOLDERS: QuestionBankFolder[] = [
|
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
id: "fld-clinical",
|
|
114
|
-
name: "
|
|
114
|
+
name: "Folder 1",
|
|
115
115
|
parentId: null,
|
|
116
|
-
icon: "fa-
|
|
116
|
+
icon: "fa-folder",
|
|
117
117
|
colorKey: "brand",
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
id: "fld-science",
|
|
121
|
-
name: "
|
|
121
|
+
name: "Folder 2",
|
|
122
122
|
parentId: null,
|
|
123
|
-
icon: "fa-
|
|
123
|
+
icon: "fa-folder",
|
|
124
124
|
colorKey: "chart2",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
id: "fld-ops",
|
|
128
|
-
name: "
|
|
128
|
+
name: "Folder 3",
|
|
129
129
|
parentId: null,
|
|
130
|
-
icon: "fa-
|
|
130
|
+
icon: "fa-folder",
|
|
131
131
|
colorKey: "warning",
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
id: "fld-ethics",
|
|
135
|
-
name: "
|
|
135
|
+
name: "Folder 4",
|
|
136
136
|
parentId: null,
|
|
137
|
-
icon: "fa-
|
|
137
|
+
icon: "fa-folder",
|
|
138
138
|
colorKey: "muted",
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
id: "fld-skills-lab",
|
|
142
|
-
name: "
|
|
142
|
+
name: "Folder 1.A",
|
|
143
143
|
parentId: "fld-clinical",
|
|
144
|
-
icon: "fa-
|
|
144
|
+
icon: "fa-folder",
|
|
145
145
|
colorKey: "success",
|
|
146
146
|
},
|
|
147
147
|
]
|
|
@@ -150,7 +150,7 @@ export function newFolderId(): string {
|
|
|
150
150
|
return `fld-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
export function collectFolderDescendantIds(folders:
|
|
153
|
+
export function collectFolderDescendantIds(folders: LibraryFolder[], rootId: string): Set<string> {
|
|
154
154
|
const out = new Set<string>()
|
|
155
155
|
function walk(id: string) {
|
|
156
156
|
out.add(id)
|
|
@@ -163,7 +163,7 @@ export function collectFolderDescendantIds(folders: QuestionBankFolder[], rootId
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export function isValidFolderMove(
|
|
166
|
-
folders:
|
|
166
|
+
folders: LibraryFolder[],
|
|
167
167
|
folderId: string,
|
|
168
168
|
newParentId: string | null,
|
|
169
169
|
): boolean {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo collaborators for Library collaboration header (stacked faces).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { stockPortraitUrl } from "@/lib/stock-portrait"
|
|
6
|
+
import type { PageHeaderCollaborator } from "@/components/page-header"
|
|
7
|
+
|
|
8
|
+
export const LIBRARY_HEADER_COLLABORATORS: PageHeaderCollaborator[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "1",
|
|
11
|
+
name: "Owner A",
|
|
12
|
+
email: "owner.a@demo.exxat.io",
|
|
13
|
+
imageUrl: stockPortraitUrl("qb-collab-a"),
|
|
14
|
+
initials: "OA",
|
|
15
|
+
access: "owner",
|
|
16
|
+
roles: ["Role 1", "Role 2"],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "2",
|
|
20
|
+
name: "Owner B",
|
|
21
|
+
email: "owner.b@demo.exxat.io",
|
|
22
|
+
imageUrl: stockPortraitUrl("qb-collab-b"),
|
|
23
|
+
initials: "OB",
|
|
24
|
+
access: "editor",
|
|
25
|
+
roles: ["Role 3"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "3",
|
|
29
|
+
name: "Owner C",
|
|
30
|
+
email: "owner.c@demo.exxat.io",
|
|
31
|
+
imageUrl: stockPortraitUrl("qb-collab-c"),
|
|
32
|
+
initials: "OC",
|
|
33
|
+
access: "editor",
|
|
34
|
+
roles: ["Role 2"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "4",
|
|
38
|
+
name: "Owner D",
|
|
39
|
+
email: "owner.d@demo.exxat.io",
|
|
40
|
+
imageUrl: stockPortraitUrl("qb-collab-d"),
|
|
41
|
+
initials: "OD",
|
|
42
|
+
access: "commenter",
|
|
43
|
+
roles: ["Role 2"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "5",
|
|
47
|
+
name: "Owner E",
|
|
48
|
+
email: "owner.e@demo.exxat.io",
|
|
49
|
+
imageUrl: stockPortraitUrl("qb-collab-e"),
|
|
50
|
+
initials: "OE",
|
|
51
|
+
access: "viewer",
|
|
52
|
+
roles: ["Role 3"],
|
|
53
|
+
},
|
|
54
|
+
]
|
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derived labels for the
|
|
2
|
+
* Derived labels for the library inspector (mock — replace with API fields).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import { collectFolderDescendantIds } from "@/lib/mock/
|
|
5
|
+
import type { LibraryLevel, LibraryItem, LibraryItemType } from "@/lib/mock/library"
|
|
6
|
+
import type { LibraryFolder } from "@/lib/mock/library-folders"
|
|
7
|
+
import { collectFolderDescendantIds } from "@/lib/mock/library-folders"
|
|
8
8
|
|
|
9
|
-
export const QUESTION_TYPE_ABBREV: Record<
|
|
10
|
-
multiple_choice: "
|
|
11
|
-
true_false: "
|
|
12
|
-
short_answer: "Short
|
|
9
|
+
export const QUESTION_TYPE_ABBREV: Record<LibraryItemType, string> = {
|
|
10
|
+
multiple_choice: "Choice",
|
|
11
|
+
true_false: "Toggle",
|
|
12
|
+
short_answer: "Short text",
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function deriveQuestionItemCode(q:
|
|
15
|
+
export function deriveQuestionItemCode(q: LibraryItem): string {
|
|
16
16
|
const raw = q.itemCode?.trim()
|
|
17
17
|
if (raw) return raw
|
|
18
18
|
return q.questionId
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function deriveBloomLevel(q:
|
|
21
|
+
export function deriveBloomLevel(q: LibraryItem): string {
|
|
22
22
|
if (q.bloomLevel && String(q.bloomLevel).trim()) return String(q.bloomLevel)
|
|
23
23
|
switch (q.difficulty) {
|
|
24
24
|
case "easy":
|
|
25
|
-
return "
|
|
25
|
+
return "Tier 1"
|
|
26
26
|
case "medium":
|
|
27
|
-
return "
|
|
27
|
+
return "Tier 2"
|
|
28
28
|
case "hard":
|
|
29
|
-
return "
|
|
29
|
+
return "Tier 3"
|
|
30
30
|
default:
|
|
31
|
-
return "
|
|
31
|
+
return "Tier 2"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Relative “last edited” clause when `lastEditedSummary` is absent. */
|
|
36
|
-
export function deriveLastEditedLine(q:
|
|
36
|
+
export function deriveLastEditedLine(q: LibraryItem): string {
|
|
37
37
|
if (q.lastEditedSummary?.trim()) return q.lastEditedSummary.trim()
|
|
38
38
|
const editor = q.lastEditedBy ?? q.author
|
|
39
39
|
const d = new Date(q.updatedAt)
|
|
@@ -48,41 +48,41 @@ export function deriveLastEditedLine(q: QuestionBankItem): string {
|
|
|
48
48
|
return `${years} year${years === 1 ? "" : "s"} ago · ${editor}`
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function deriveTags(q:
|
|
51
|
+
export function deriveTags(q: LibraryItem): string[] {
|
|
52
52
|
if (q.tags && q.tags.length > 0) return q.tags
|
|
53
53
|
return [q.topic].filter(Boolean)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/** Tier row order for folder aggregate charts. */
|
|
57
57
|
export const BLOOM_LEVEL_ORDER = [
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
58
|
+
"Tier 1",
|
|
59
|
+
"Tier 2",
|
|
60
|
+
"Tier 3",
|
|
61
|
+
"Tier 4",
|
|
62
|
+
"Tier 5",
|
|
63
|
+
"Tier 6",
|
|
64
64
|
] as const
|
|
65
65
|
|
|
66
66
|
export function questionsInFolderSubtree(
|
|
67
|
-
folders:
|
|
68
|
-
questions:
|
|
67
|
+
folders: LibraryFolder[],
|
|
68
|
+
questions: LibraryItem[],
|
|
69
69
|
folderId: string,
|
|
70
|
-
):
|
|
70
|
+
): LibraryItem[] {
|
|
71
71
|
const scope = collectFolderDescendantIds(folders, folderId)
|
|
72
72
|
return questions.filter(q => scope.has(q.folderId))
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export interface FolderQuestionAggregate {
|
|
76
76
|
totalQuestions: number
|
|
77
|
-
difficulty: Record<
|
|
77
|
+
difficulty: Record<LibraryLevel, number>
|
|
78
78
|
/** Counts keyed by Bloom label (includes derived levels). */
|
|
79
79
|
bloom: Record<string, number>
|
|
80
80
|
avgPbi: number | null
|
|
81
81
|
scoredCount: number
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function aggregateFolderQuestions(rows:
|
|
85
|
-
const difficulty: Record<
|
|
84
|
+
export function aggregateFolderQuestions(rows: LibraryItem[]): FolderQuestionAggregate {
|
|
85
|
+
const difficulty: Record<LibraryLevel, number> = {
|
|
86
86
|
easy: 0,
|
|
87
87
|
medium: 0,
|
|
88
88
|
hard: 0,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
2
|
-
import type {
|
|
2
|
+
import type { LibraryItem, LibraryItemType } from "@/lib/mock/library"
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** Quality score below this mock threshold counts as a review flag. */
|
|
5
5
|
const PBI_REVIEW_THRESHOLD = 0.2
|
|
6
6
|
|
|
7
|
-
const TYPE_LABEL: Record<
|
|
8
|
-
multiple_choice: "
|
|
9
|
-
true_false: "
|
|
10
|
-
short_answer: "
|
|
7
|
+
const TYPE_LABEL: Record<LibraryItemType, string> = {
|
|
8
|
+
multiple_choice: "Type 1",
|
|
9
|
+
true_false: "Type 2",
|
|
10
|
+
short_answer: "Type 3",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function libraryKpiMetrics(rows: LibraryItem[]): MetricItem[] {
|
|
14
14
|
const mcq = rows.filter(r => r.type === "multiple_choice").length
|
|
15
15
|
const tf = rows.filter(r => r.type === "true_false").length
|
|
16
16
|
const sa = rows.filter(r => r.type === "short_answer").length
|
|
@@ -20,9 +20,9 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
|
|
|
20
20
|
return [
|
|
21
21
|
{
|
|
22
22
|
id: "total",
|
|
23
|
-
label: "Total
|
|
23
|
+
label: "Total items",
|
|
24
24
|
value: rows.length,
|
|
25
|
-
delta: "
|
|
25
|
+
delta: "",
|
|
26
26
|
trend: "neutral",
|
|
27
27
|
href: "#",
|
|
28
28
|
metricVariant: "hero",
|
|
@@ -31,23 +31,23 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
|
|
|
31
31
|
id: "mcq",
|
|
32
32
|
label: TYPE_LABEL.multiple_choice,
|
|
33
33
|
value: mcq,
|
|
34
|
-
delta: "
|
|
34
|
+
delta: "",
|
|
35
35
|
trend: "neutral",
|
|
36
36
|
href: "#",
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
id: "written",
|
|
40
|
-
label: "
|
|
40
|
+
label: "Type 2 & 3",
|
|
41
41
|
value: writtenTypes,
|
|
42
|
-
delta: "
|
|
42
|
+
delta: "",
|
|
43
43
|
trend: "neutral",
|
|
44
44
|
href: "#",
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
id: "pbi-flags",
|
|
48
|
-
label: "
|
|
48
|
+
label: "Review flags",
|
|
49
49
|
value: lowPbiFlags,
|
|
50
|
-
delta: lowPbiFlags >= 2 ? "+1" : "
|
|
50
|
+
delta: lowPbiFlags >= 2 ? "+1" : "",
|
|
51
51
|
trend: lowPbiFlags >= 2 ? "up" : "neutral",
|
|
52
52
|
trendPolarity: "lower_is_better",
|
|
53
53
|
href: "#",
|
|
@@ -55,18 +55,18 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
|
|
|
55
55
|
]
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function
|
|
58
|
+
export function libraryKpiInsight(rows: LibraryItem[]): MetricInsight {
|
|
59
59
|
const hard = rows.filter(r => r.difficulty === "hard").length
|
|
60
60
|
const topics = new Set(rows.map(r => r.topic)).size
|
|
61
61
|
return {
|
|
62
|
-
title: "
|
|
62
|
+
title: "Library snapshot",
|
|
63
63
|
description:
|
|
64
64
|
rows.length === 0
|
|
65
|
-
? "Add
|
|
65
|
+
? "Add items to populate metrics and charts."
|
|
66
66
|
: hard > 3
|
|
67
|
-
? `${hard}
|
|
68
|
-
: `${rows.length}
|
|
69
|
-
href: "/
|
|
67
|
+
? `${hard} high-level items in this view — balance with lower-level items where helpful.`
|
|
68
|
+
: `${rows.length} item${rows.length === 1 ? "" : "s"} across ${topics} categor${topics === 1 ? "y" : "ies"} in the filtered set.`,
|
|
69
|
+
href: "/library/all",
|
|
70
70
|
severity: hard > 3 ? "warning" : "info",
|
|
71
71
|
actionLabel: "Ask Leo",
|
|
72
72
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock library items — replace with API in production.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type LibraryItemType = "multiple_choice" | "true_false" | "short_answer"
|
|
6
|
+
export type LibraryLevel = "easy" | "medium" | "hard"
|
|
7
|
+
|
|
8
|
+
/** Tier label for inspector / analytics (optional on mock rows). */
|
|
9
|
+
export type LibraryTierLevel =
|
|
10
|
+
| "Tier 1"
|
|
11
|
+
| "Tier 2"
|
|
12
|
+
| "Tier 3"
|
|
13
|
+
| "Tier 4"
|
|
14
|
+
| "Tier 5"
|
|
15
|
+
| "Tier 6"
|
|
16
|
+
|
|
17
|
+
export interface LibraryItem extends Record<string, unknown> {
|
|
18
|
+
id: string
|
|
19
|
+
/** Stable human-facing identifier (catalog, search, citations). */
|
|
20
|
+
questionId: string
|
|
21
|
+
/** Short preview / stem */
|
|
22
|
+
stem: string
|
|
23
|
+
topic: string
|
|
24
|
+
type: LibraryItemType
|
|
25
|
+
difficulty: LibraryLevel
|
|
26
|
+
author: string
|
|
27
|
+
/** Work email for the primary author (demo; optional when API omits). */
|
|
28
|
+
authorEmail?: string
|
|
29
|
+
updatedAt: string
|
|
30
|
+
/** Folder tree id (`lib/mock/library-folders.ts`). */
|
|
31
|
+
folderId: string
|
|
32
|
+
/** Multiple choice options (only for type: "multiple_choice") */
|
|
33
|
+
options?: {
|
|
34
|
+
text: string
|
|
35
|
+
isCorrect?: boolean
|
|
36
|
+
}[]
|
|
37
|
+
/** Stable bank code for lists / inspector (e.g. PH-ANA-001). */
|
|
38
|
+
itemCode?: string
|
|
39
|
+
bloomLevel?: LibraryTierLevel | string
|
|
40
|
+
/** Topic-style tags for inspector (# displayed in UI). */
|
|
41
|
+
tags?: string[]
|
|
42
|
+
/** ISO-ish created date (inspector “Creator & history”). */
|
|
43
|
+
createdAt?: string
|
|
44
|
+
/** Display name for “Created by” when different from `author`. */
|
|
45
|
+
createdBy?: string
|
|
46
|
+
/** Last editor display name (inspector copy). */
|
|
47
|
+
lastEditedBy?: string
|
|
48
|
+
/** Precomposed “Last edited” line; overrides derived copy when set. */
|
|
49
|
+
lastEditedSummary?: string
|
|
50
|
+
/** Revision label e.g. v4 */
|
|
51
|
+
version?: string
|
|
52
|
+
examUsageCount?: number
|
|
53
|
+
/** Point-biserial index (psychometrics preview). */
|
|
54
|
+
pbi?: number
|
|
55
|
+
/** Mean percent correct when used in assessments. */
|
|
56
|
+
avgScoreCorrectPct?: number
|
|
57
|
+
/** Where / when the item was last used on an exam. */
|
|
58
|
+
lastUsedLabel?: string
|
|
59
|
+
/** Starred outside the Favorites folder (list landing demo). */
|
|
60
|
+
isStarred?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** New mock rows — assign a unique `questionId` when creating client-side. */
|
|
64
|
+
export function newLibraryQuestionId(): string {
|
|
65
|
+
return `LIB-NEW-${Date.now().toString(36).toUpperCase()}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const LIBRARY_ITEMS: LibraryItem[] = [
|
|
69
|
+
{
|
|
70
|
+
id: "q1",
|
|
71
|
+
questionId: "LIB-2026-001",
|
|
72
|
+
stem: "Item 01 — sample primary entry for the demo dataset.",
|
|
73
|
+
topic: "Category 1",
|
|
74
|
+
type: "multiple_choice",
|
|
75
|
+
difficulty: "medium",
|
|
76
|
+
author: "Owner A",
|
|
77
|
+
authorEmail: "owner.a@demo.exxat.io",
|
|
78
|
+
updatedAt: "2026-03-28",
|
|
79
|
+
folderId: "fld-favorites",
|
|
80
|
+
itemCode: "LIB-2026-001",
|
|
81
|
+
bloomLevel: "Tier 2",
|
|
82
|
+
tags: ["Tag 1", "Tag 2", "Tag 3"],
|
|
83
|
+
createdAt: "2024-11-02",
|
|
84
|
+
createdBy: "Owner B",
|
|
85
|
+
lastEditedBy: "Owner C",
|
|
86
|
+
lastEditedSummary: "14 months ago · Owner C",
|
|
87
|
+
version: "v4",
|
|
88
|
+
examUsageCount: 22,
|
|
89
|
+
pbi: 0.48,
|
|
90
|
+
avgScoreCorrectPct: 88,
|
|
91
|
+
lastUsedLabel: "Reference set · 14 months ago",
|
|
92
|
+
options: [
|
|
93
|
+
{ text: "Option A", isCorrect: true },
|
|
94
|
+
{ text: "Option B", isCorrect: false },
|
|
95
|
+
{ text: "Option C", isCorrect: false },
|
|
96
|
+
{ text: "Option D", isCorrect: false },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "q2",
|
|
101
|
+
questionId: "LIB-2026-002",
|
|
102
|
+
stem: "Item 02 — sample entry with a single inline action.",
|
|
103
|
+
topic: "Category 2",
|
|
104
|
+
type: "true_false",
|
|
105
|
+
difficulty: "easy",
|
|
106
|
+
author: "Owner B",
|
|
107
|
+
authorEmail: "owner.b@demo.exxat.io",
|
|
108
|
+
updatedAt: "2026-03-27",
|
|
109
|
+
folderId: "fld-skills-lab",
|
|
110
|
+
isStarred: true,
|
|
111
|
+
pbi: 0.55,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "q3",
|
|
115
|
+
questionId: "LIB-2026-003",
|
|
116
|
+
stem: "Item 03 — short prompt entry for the catalog demo.",
|
|
117
|
+
topic: "Category 3",
|
|
118
|
+
type: "short_answer",
|
|
119
|
+
difficulty: "hard",
|
|
120
|
+
author: "Owner C",
|
|
121
|
+
authorEmail: "owner.c@demo.exxat.io",
|
|
122
|
+
updatedAt: "2026-03-26",
|
|
123
|
+
folderId: "fld-science",
|
|
124
|
+
isStarred: true,
|
|
125
|
+
pbi: 0.14,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "q4",
|
|
129
|
+
questionId: "LIB-2026-004",
|
|
130
|
+
stem: "Item 04 — multi-option entry showing the picker pattern.",
|
|
131
|
+
topic: "Category 4",
|
|
132
|
+
type: "multiple_choice",
|
|
133
|
+
difficulty: "medium",
|
|
134
|
+
author: "Owner D",
|
|
135
|
+
authorEmail: "owner.d@demo.exxat.io",
|
|
136
|
+
updatedAt: "2026-03-25",
|
|
137
|
+
folderId: "fld-ethics",
|
|
138
|
+
isStarred: true,
|
|
139
|
+
pbi: 0.19,
|
|
140
|
+
options: [
|
|
141
|
+
{ text: "Option A", isCorrect: true },
|
|
142
|
+
{ text: "Option B", isCorrect: false },
|
|
143
|
+
{ text: "Option C", isCorrect: false },
|
|
144
|
+
{ text: "Option D", isCorrect: false },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "q5",
|
|
149
|
+
questionId: "LIB-2026-005",
|
|
150
|
+
stem: "Item 05 — short-answer entry pinned to the Favorites folder.",
|
|
151
|
+
topic: "Category 1",
|
|
152
|
+
type: "short_answer",
|
|
153
|
+
difficulty: "easy",
|
|
154
|
+
author: "Owner A",
|
|
155
|
+
authorEmail: "owner.a@demo.exxat.io",
|
|
156
|
+
updatedAt: "2026-03-24",
|
|
157
|
+
folderId: "fld-favorites",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "q6",
|
|
161
|
+
questionId: "LIB-2026-006",
|
|
162
|
+
stem: "Item 06 — choice-list entry for the catalog demo.",
|
|
163
|
+
topic: "Category 3",
|
|
164
|
+
type: "multiple_choice",
|
|
165
|
+
difficulty: "medium",
|
|
166
|
+
author: "Owner E",
|
|
167
|
+
authorEmail: "owner.e@demo.exxat.io",
|
|
168
|
+
updatedAt: "2026-03-23",
|
|
169
|
+
folderId: "fld-ops",
|
|
170
|
+
options: [
|
|
171
|
+
{ text: "Option A", isCorrect: true },
|
|
172
|
+
{ text: "Option B", isCorrect: false },
|
|
173
|
+
{ text: "Option C", isCorrect: false },
|
|
174
|
+
{ text: "Option D", isCorrect: false },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "q7",
|
|
179
|
+
questionId: "LIB-2026-007",
|
|
180
|
+
stem: "Item 07 — boolean entry to show toggle-style answers.",
|
|
181
|
+
topic: "Category 2",
|
|
182
|
+
type: "true_false",
|
|
183
|
+
difficulty: "easy",
|
|
184
|
+
author: "Owner B",
|
|
185
|
+
authorEmail: "owner.b@demo.exxat.io",
|
|
186
|
+
updatedAt: "2026-03-22",
|
|
187
|
+
folderId: "fld-clinical",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "q8",
|
|
191
|
+
questionId: "LIB-2026-008",
|
|
192
|
+
stem: "Item 08 — multi-option entry filed under Folder 2.",
|
|
193
|
+
topic: "Category 3",
|
|
194
|
+
type: "multiple_choice",
|
|
195
|
+
difficulty: "hard",
|
|
196
|
+
author: "Owner C",
|
|
197
|
+
authorEmail: "owner.c@demo.exxat.io",
|
|
198
|
+
updatedAt: "2026-03-21",
|
|
199
|
+
folderId: "fld-science",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "q9",
|
|
203
|
+
questionId: "LIB-2026-009",
|
|
204
|
+
stem: "Item 09 — quick true / false entry for the demo set.",
|
|
205
|
+
topic: "Category 4",
|
|
206
|
+
type: "true_false",
|
|
207
|
+
difficulty: "medium",
|
|
208
|
+
author: "Owner D",
|
|
209
|
+
authorEmail: "owner.d@demo.exxat.io",
|
|
210
|
+
updatedAt: "2026-03-20",
|
|
211
|
+
folderId: "fld-clinical",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "q10",
|
|
215
|
+
questionId: "LIB-2026-010",
|
|
216
|
+
stem: "Item 10 — short-answer entry from Owner A.",
|
|
217
|
+
topic: "Category 1",
|
|
218
|
+
type: "short_answer",
|
|
219
|
+
difficulty: "hard",
|
|
220
|
+
author: "Owner A",
|
|
221
|
+
authorEmail: "owner.a@demo.exxat.io",
|
|
222
|
+
updatedAt: "2026-03-19",
|
|
223
|
+
folderId: "fld-science",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: "q11",
|
|
227
|
+
questionId: "LIB-2026-011",
|
|
228
|
+
stem: "Item 11 — true / false entry showing chip alignment.",
|
|
229
|
+
topic: "Category 2",
|
|
230
|
+
type: "true_false",
|
|
231
|
+
difficulty: "easy",
|
|
232
|
+
author: "Owner E",
|
|
233
|
+
authorEmail: "owner.e@demo.exxat.io",
|
|
234
|
+
updatedAt: "2026-03-18",
|
|
235
|
+
folderId: "fld-ops",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "q12",
|
|
239
|
+
questionId: "LIB-2026-012",
|
|
240
|
+
stem: "Item 12 — short-answer entry for the catalog demo.",
|
|
241
|
+
topic: "Category 3",
|
|
242
|
+
type: "short_answer",
|
|
243
|
+
difficulty: "medium",
|
|
244
|
+
author: "Owner B",
|
|
245
|
+
authorEmail: "owner.b@demo.exxat.io",
|
|
246
|
+
updatedAt: "2026-03-17",
|
|
247
|
+
folderId: "fld-ops",
|
|
248
|
+
},
|
|
249
|
+
]
|