@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,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Placements **Data** view dashboard — card ids, defaults, and persistence.
|
|
3
|
-
* Split from `data-view-dashboard-charts.tsx` so list-hub code can import layout
|
|
4
|
-
* without pulling Recharts / chart canvas into the main chunk.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
KEY_METRICS_KPI_COUNT_DEFAULT,
|
|
9
|
-
mergeDashboardLayoutGeneric,
|
|
10
|
-
} from "@/lib/dashboard-layout-merge"
|
|
11
|
-
import {
|
|
12
|
-
loadDataViewLayout as loadStoredDataViewLayout,
|
|
13
|
-
saveDataViewLayout as saveStoredDataViewLayout,
|
|
14
|
-
} from "@/lib/data-view-dashboard-storage"
|
|
15
|
-
|
|
16
|
-
export type ChartType =
|
|
17
|
-
| "bar"
|
|
18
|
-
| "horizontal-bar"
|
|
19
|
-
| "pie"
|
|
20
|
-
| "area"
|
|
21
|
-
| "line"
|
|
22
|
-
| "radial"
|
|
23
|
-
| "stacked-bar"
|
|
24
|
-
|
|
25
|
-
export interface ChartTypeOption {
|
|
26
|
-
type: ChartType
|
|
27
|
-
label: string
|
|
28
|
-
icon: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface DashboardCardDef {
|
|
32
|
-
id: string
|
|
33
|
-
title: string
|
|
34
|
-
description: string
|
|
35
|
-
defaultSpan: 1 | 2
|
|
36
|
-
defaultChartType: ChartType
|
|
37
|
-
chartTypes: ChartTypeOption[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Virtual “card” for the KPI strip — reorderable with charts in edit mode */
|
|
41
|
-
export const KEY_METRICS_CARD_ID = "key-metrics"
|
|
42
|
-
|
|
43
|
-
export const ALL_DASHBOARD_CARDS: DashboardCardDef[] = [
|
|
44
|
-
{
|
|
45
|
-
id: KEY_METRICS_CARD_ID,
|
|
46
|
-
title: "Key metrics",
|
|
47
|
-
description: "Summary KPIs for filtered placements",
|
|
48
|
-
defaultSpan: 1,
|
|
49
|
-
defaultChartType: "bar",
|
|
50
|
-
chartTypes: [],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: "status-pipeline",
|
|
54
|
-
title: "Status Pipeline",
|
|
55
|
-
description: "Where placements are in the workflow",
|
|
56
|
-
defaultSpan: 1,
|
|
57
|
-
defaultChartType: "bar",
|
|
58
|
-
chartTypes: [
|
|
59
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
60
|
-
{ type: "horizontal-bar", label: "Horizontal Bar", icon: "fa-light fa-chart-bar fa-rotate-90" },
|
|
61
|
-
{ type: "pie", label: "Donut", icon: "fa-light fa-chart-pie" },
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: "program-mix",
|
|
66
|
-
title: "Placements by Program",
|
|
67
|
-
description: "Distribution across active programs",
|
|
68
|
-
defaultSpan: 1,
|
|
69
|
-
defaultChartType: "pie",
|
|
70
|
-
chartTypes: [
|
|
71
|
-
{ type: "pie", label: "Donut", icon: "fa-light fa-chart-pie" },
|
|
72
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
73
|
-
{ type: "horizontal-bar", label: "Horizontal Bar", icon: "fa-light fa-chart-bar fa-rotate-90" },
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
id: "compliance-status",
|
|
78
|
-
title: "Compliance Status",
|
|
79
|
-
description: "Document readiness for upcoming placements",
|
|
80
|
-
defaultSpan: 1,
|
|
81
|
-
defaultChartType: "horizontal-bar",
|
|
82
|
-
chartTypes: [
|
|
83
|
-
{ type: "horizontal-bar", label: "Horizontal Bar", icon: "fa-light fa-chart-bar fa-rotate-90" },
|
|
84
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
85
|
-
{ type: "pie", label: "Donut", icon: "fa-light fa-chart-pie" },
|
|
86
|
-
],
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: "readiness-overview",
|
|
90
|
-
title: "Student Readiness",
|
|
91
|
-
description: "How prepared students are for their placements",
|
|
92
|
-
defaultSpan: 1,
|
|
93
|
-
defaultChartType: "bar",
|
|
94
|
-
chartTypes: [
|
|
95
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
96
|
-
{ type: "horizontal-bar", label: "Horizontal Bar", icon: "fa-light fa-chart-bar fa-rotate-90" },
|
|
97
|
-
{ type: "pie", label: "Donut", icon: "fa-light fa-chart-pie" },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: "progress-tracker",
|
|
102
|
-
title: "Ongoing Progress",
|
|
103
|
-
description: "How far along each ongoing placement is",
|
|
104
|
-
defaultSpan: 2,
|
|
105
|
-
defaultChartType: "stacked-bar",
|
|
106
|
-
chartTypes: [
|
|
107
|
-
{ type: "stacked-bar", label: "Stacked Bar", icon: "fa-light fa-layer-group" },
|
|
108
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
109
|
-
],
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: "site-utilisation",
|
|
113
|
-
title: "Site Utilisation",
|
|
114
|
-
description: "Which clinical sites have the most placements",
|
|
115
|
-
defaultSpan: 1,
|
|
116
|
-
defaultChartType: "horizontal-bar",
|
|
117
|
-
chartTypes: [
|
|
118
|
-
{ type: "horizontal-bar", label: "Horizontal Bar", icon: "fa-light fa-chart-bar fa-rotate-90" },
|
|
119
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
120
|
-
{ type: "pie", label: "Donut", icon: "fa-light fa-chart-pie" },
|
|
121
|
-
],
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
id: "completion-outcomes",
|
|
125
|
-
title: "Completion Outcomes",
|
|
126
|
-
description: "Pass rate and average ratings for completed placements",
|
|
127
|
-
defaultSpan: 1,
|
|
128
|
-
defaultChartType: "radial",
|
|
129
|
-
chartTypes: [
|
|
130
|
-
{ type: "radial", label: "Radial", icon: "fa-light fa-circle-notch" },
|
|
131
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
id: "upcoming-timeline",
|
|
136
|
-
title: "Upcoming Start Dates",
|
|
137
|
-
description: "New placements starting in the next 8 weeks",
|
|
138
|
-
defaultSpan: 2,
|
|
139
|
-
defaultChartType: "area",
|
|
140
|
-
chartTypes: [
|
|
141
|
-
{ type: "area", label: "Area", icon: "fa-light fa-chart-area" },
|
|
142
|
-
{ type: "line", label: "Line", icon: "fa-light fa-chart-line" },
|
|
143
|
-
{ type: "bar", label: "Bar", icon: "fa-light fa-chart-bar" },
|
|
144
|
-
],
|
|
145
|
-
},
|
|
146
|
-
]
|
|
147
|
-
|
|
148
|
-
export const DEFAULT_VISIBLE_CARDS = ALL_DASHBOARD_CARDS.map(c => c.id)
|
|
149
|
-
export const DEFAULT_SPANS: Record<string, 1 | 2> = Object.fromEntries(
|
|
150
|
-
ALL_DASHBOARD_CARDS.map(c => [c.id, c.defaultSpan]),
|
|
151
|
-
)
|
|
152
|
-
export const DEFAULT_CHART_TYPES: Record<string, ChartType> = Object.fromEntries(
|
|
153
|
-
ALL_DASHBOARD_CARDS.map(c => [c.id, c.defaultChartType]),
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
export interface DashboardLayout {
|
|
157
|
-
visible: string[]
|
|
158
|
-
order: string[]
|
|
159
|
-
spans?: Record<string, 1 | 2>
|
|
160
|
-
chartTypes?: Record<string, ChartType>
|
|
161
|
-
keyMetricsKpiCount?: number
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function loadDashboardLayout(): DashboardLayout | null {
|
|
165
|
-
const v = loadStoredDataViewLayout("placements")
|
|
166
|
-
if (!v) return null
|
|
167
|
-
return {
|
|
168
|
-
visible: v.visible,
|
|
169
|
-
order: v.order,
|
|
170
|
-
spans: v.spans,
|
|
171
|
-
chartTypes: v.chartTypes as Record<string, ChartType> | undefined,
|
|
172
|
-
keyMetricsKpiCount: v.keyMetricsKpiCount,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function mergeDashboardLayout(saved: DashboardLayout | null): DashboardLayout {
|
|
177
|
-
const defaults = {
|
|
178
|
-
visible: [...DEFAULT_VISIBLE_CARDS],
|
|
179
|
-
order: ALL_DASHBOARD_CARDS.map(c => c.id),
|
|
180
|
-
spans: { ...DEFAULT_SPANS },
|
|
181
|
-
chartTypes: { ...DEFAULT_CHART_TYPES } as Record<string, string>,
|
|
182
|
-
keyMetricsKpiCount: KEY_METRICS_KPI_COUNT_DEFAULT,
|
|
183
|
-
}
|
|
184
|
-
const ids = ALL_DASHBOARD_CARDS.map(c => c.id)
|
|
185
|
-
const m = mergeDashboardLayoutGeneric(saved, defaults, ids)
|
|
186
|
-
return {
|
|
187
|
-
visible: m.visible,
|
|
188
|
-
order: m.order,
|
|
189
|
-
spans: m.spans as Record<string, 1 | 2>,
|
|
190
|
-
chartTypes: m.chartTypes as Record<string, ChartType>,
|
|
191
|
-
keyMetricsKpiCount: m.keyMetricsKpiCount,
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function saveDashboardLayout(layout: DashboardLayout) {
|
|
196
|
-
saveStoredDataViewLayout("placements", {
|
|
197
|
-
visible: layout.visible,
|
|
198
|
-
order: layout.order,
|
|
199
|
-
spans: layout.spans,
|
|
200
|
-
chartTypes: layout.chartTypes as Record<string, string> | undefined,
|
|
201
|
-
keyMetricsKpiCount: layout.keyMetricsKpiCount,
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function applyVisibleReorder(
|
|
206
|
-
fullOrder: string[],
|
|
207
|
-
visible: Set<string>,
|
|
208
|
-
newVisibleOrder: string[],
|
|
209
|
-
): string[] {
|
|
210
|
-
let vi = 0
|
|
211
|
-
return fullOrder.map(id => {
|
|
212
|
-
if (!visible.has(id)) return id
|
|
213
|
-
return newVisibleOrder[vi++]!
|
|
214
|
-
})
|
|
215
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
2
|
-
import type { ComplianceItem } from "@/lib/mock/compliance"
|
|
3
|
-
|
|
4
|
-
export function complianceKpiMetrics(rows: ComplianceItem[]): MetricItem[] {
|
|
5
|
-
const compliant = rows.filter(r => r.status === "compliant").length
|
|
6
|
-
const dueSoon = rows.filter(r => r.status === "due_soon").length
|
|
7
|
-
const overdue = rows.filter(r => r.status === "overdue").length
|
|
8
|
-
const pending = rows.filter(r => r.status === "pending").length
|
|
9
|
-
|
|
10
|
-
return [
|
|
11
|
-
{
|
|
12
|
-
id: "total",
|
|
13
|
-
label: "Total items",
|
|
14
|
-
value: rows.length,
|
|
15
|
-
delta: "—",
|
|
16
|
-
trend: "neutral",
|
|
17
|
-
href: "#",
|
|
18
|
-
metricVariant: "hero",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "compliant",
|
|
22
|
-
label: "Compliant",
|
|
23
|
-
value: compliant,
|
|
24
|
-
delta: "—",
|
|
25
|
-
trend: "neutral",
|
|
26
|
-
href: "#",
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "attention",
|
|
30
|
-
label: "Due soon",
|
|
31
|
-
value: dueSoon,
|
|
32
|
-
delta: dueSoon > 0 ? "!" : "—",
|
|
33
|
-
trend: dueSoon > 0 ? "up" : "neutral",
|
|
34
|
-
href: "#",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "risk",
|
|
38
|
-
label: "Overdue / pending",
|
|
39
|
-
value: overdue + pending,
|
|
40
|
-
delta: "—",
|
|
41
|
-
trend: overdue + pending > 0 ? "up" : "neutral",
|
|
42
|
-
href: "#",
|
|
43
|
-
},
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function complianceKpiInsight(rows: ComplianceItem[]): MetricInsight {
|
|
48
|
-
const overdue = rows.filter(r => r.status === "overdue").length
|
|
49
|
-
const dueSoon = rows.filter(r => r.status === "due_soon").length
|
|
50
|
-
return {
|
|
51
|
-
title: "Compliance queue",
|
|
52
|
-
description:
|
|
53
|
-
overdue > 0
|
|
54
|
-
? `${overdue} item(s) overdue. ${dueSoon} due within 30 days.`
|
|
55
|
-
: dueSoon > 0
|
|
56
|
-
? `${dueSoon} item(s) due soon — review owners and dates.`
|
|
57
|
-
: "No overdue items in this view.",
|
|
58
|
-
severity: overdue > 0 ? "warning" : dueSoon > 0 ? "warning" : "info",
|
|
59
|
-
actionLabel: "Ask Leo",
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock compliance obligations — replace with API data in production.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export type ComplianceStatus = "compliant" | "due_soon" | "overdue" | "pending"
|
|
6
|
-
|
|
7
|
-
export interface ComplianceItem extends Record<string, unknown> {
|
|
8
|
-
id: string
|
|
9
|
-
title: string
|
|
10
|
-
category: string
|
|
11
|
-
status: ComplianceStatus
|
|
12
|
-
/** MM/DD/YYYY */
|
|
13
|
-
dueDate: string
|
|
14
|
-
owner: string
|
|
15
|
-
/** MM/DD/YYYY */
|
|
16
|
-
lastReviewed: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const COMPLIANCE_ITEMS: ComplianceItem[] = [
|
|
20
|
-
{
|
|
21
|
-
id: "1",
|
|
22
|
-
title: "Annual HIPAA training attestation",
|
|
23
|
-
category: "HIPAA",
|
|
24
|
-
status: "compliant",
|
|
25
|
-
dueDate: "12/31/2026",
|
|
26
|
-
owner: "Alex Rivera",
|
|
27
|
-
lastReviewed: "03/01/2026",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "2",
|
|
31
|
-
title: "OSHA bloodborne pathogen review",
|
|
32
|
-
category: "OSHA",
|
|
33
|
-
status: "due_soon",
|
|
34
|
-
dueDate: "04/10/2026",
|
|
35
|
-
owner: "Jordan Chen",
|
|
36
|
-
lastReviewed: "10/15/2025",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "3",
|
|
40
|
-
title: "Clinical site affiliation agreements",
|
|
41
|
-
category: "Clinical",
|
|
42
|
-
status: "pending",
|
|
43
|
-
dueDate: "05/01/2026",
|
|
44
|
-
owner: "Sam Patel",
|
|
45
|
-
lastReviewed: "—",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: "4",
|
|
49
|
-
title: "FERPA student records policy",
|
|
50
|
-
category: "Privacy",
|
|
51
|
-
status: "compliant",
|
|
52
|
-
dueDate: "09/30/2026",
|
|
53
|
-
owner: "Taylor Brooks",
|
|
54
|
-
lastReviewed: "02/20/2026",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "5",
|
|
58
|
-
title: "Background check re-verification",
|
|
59
|
-
category: "Clinical",
|
|
60
|
-
status: "overdue",
|
|
61
|
-
dueDate: "03/01/2026",
|
|
62
|
-
owner: "Jordan Chen",
|
|
63
|
-
lastReviewed: "03/01/2025",
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: "6",
|
|
67
|
-
title: "Accreditation self-study draft",
|
|
68
|
-
category: "Accreditation",
|
|
69
|
-
status: "due_soon",
|
|
70
|
-
dueDate: "04/22/2026",
|
|
71
|
-
owner: "Alex Rivera",
|
|
72
|
-
lastReviewed: "01/10/2026",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: "7",
|
|
76
|
-
title: "Incident response tabletop exercise",
|
|
77
|
-
category: "HIPAA",
|
|
78
|
-
status: "pending",
|
|
79
|
-
dueDate: "06/15/2026",
|
|
80
|
-
owner: "Sam Patel",
|
|
81
|
-
lastReviewed: "—",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "8",
|
|
85
|
-
title: "Emergency preparedness plan",
|
|
86
|
-
category: "OSHA",
|
|
87
|
-
status: "compliant",
|
|
88
|
-
dueDate: "11/01/2026",
|
|
89
|
-
owner: "Taylor Brooks",
|
|
90
|
-
lastReviewed: "02/28/2026",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: "9",
|
|
94
|
-
title: "Medication safety competency checklist",
|
|
95
|
-
category: "Clinical",
|
|
96
|
-
status: "compliant",
|
|
97
|
-
dueDate: "08/01/2026",
|
|
98
|
-
owner: "Alex Rivera",
|
|
99
|
-
lastReviewed: "03/15/2026",
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "10",
|
|
103
|
-
title: "Data breach notification procedure",
|
|
104
|
-
category: "Privacy",
|
|
105
|
-
status: "due_soon",
|
|
106
|
-
dueDate: "04/05/2026",
|
|
107
|
-
owner: "Jordan Chen",
|
|
108
|
-
lastReviewed: "10/01/2025",
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
id: "11",
|
|
112
|
-
title: "LCME / programmatic review evidence",
|
|
113
|
-
category: "Accreditation",
|
|
114
|
-
status: "pending",
|
|
115
|
-
dueDate: "07/30/2026",
|
|
116
|
-
owner: "Sam Patel",
|
|
117
|
-
lastReviewed: "—",
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: "12",
|
|
121
|
-
title: "Sharps injury log audit",
|
|
122
|
-
category: "OSHA",
|
|
123
|
-
status: "overdue",
|
|
124
|
-
dueDate: "02/20/2026",
|
|
125
|
-
owner: "Taylor Brooks",
|
|
126
|
-
lastReviewed: "02/20/2025",
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: "13",
|
|
130
|
-
title: "Business associate agreement inventory",
|
|
131
|
-
category: "HIPAA",
|
|
132
|
-
status: "compliant",
|
|
133
|
-
dueDate: "10/15/2026",
|
|
134
|
-
owner: "Alex Rivera",
|
|
135
|
-
lastReviewed: "03/01/2026",
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
id: "14",
|
|
139
|
-
title: "Student immunization tracking",
|
|
140
|
-
category: "Clinical",
|
|
141
|
-
status: "due_soon",
|
|
142
|
-
dueDate: "04/18/2026",
|
|
143
|
-
owner: "Jordan Chen",
|
|
144
|
-
lastReviewed: "09/01/2025",
|
|
145
|
-
},
|
|
146
|
-
]
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// List hub — KPI strip + insight (data-list; dashboard view uses row-driven helpers)
|
|
3
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
6
|
-
import { ALL_PLACEMENTS, type Placement, type Status } from "./placements"
|
|
7
|
-
|
|
8
|
-
function statusCount(status: Status): number {
|
|
9
|
-
return ALL_PLACEMENTS.filter((p) => p.status === status).length
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* KPIs from the current filtered row set (table/list/board/dashboard shared state).
|
|
14
|
-
* Use for the dashboard view tab; optional for the template metrics strip when you want parity.
|
|
15
|
-
*/
|
|
16
|
-
export function placementKpiMetricsFromRows(rows: Placement[]): MetricItem[] {
|
|
17
|
-
const total = rows.length
|
|
18
|
-
const startingWeek = rows.filter(p => p.daysUntilStart >= 0 && p.daysUntilStart <= 7).length
|
|
19
|
-
const alerts = rows.filter(p => {
|
|
20
|
-
const r = p.readiness.toLowerCase()
|
|
21
|
-
return r.includes("risk") || r.includes("blocked")
|
|
22
|
-
}).length
|
|
23
|
-
const complete = rows.filter(p => p.compliance === "Complete").length
|
|
24
|
-
const avgPct = total > 0 ? Math.round((complete / total) * 100) : 0
|
|
25
|
-
|
|
26
|
-
return [
|
|
27
|
-
{
|
|
28
|
-
id: "total-placements",
|
|
29
|
-
label: "Total rows",
|
|
30
|
-
value: total,
|
|
31
|
-
delta: "—",
|
|
32
|
-
trend: "neutral",
|
|
33
|
-
href: "#",
|
|
34
|
-
metricVariant: "hero",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "starting-week",
|
|
38
|
-
label: "Due this week",
|
|
39
|
-
value: startingWeek,
|
|
40
|
-
delta: "—",
|
|
41
|
-
trend: startingWeek > 0 ? "up" : "neutral",
|
|
42
|
-
href: "#",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "compliance-alerts",
|
|
46
|
-
label: "Attention flags",
|
|
47
|
-
value: alerts,
|
|
48
|
-
delta: "—",
|
|
49
|
-
trend: alerts > 0 ? "up" : "neutral",
|
|
50
|
-
href: "#",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: "avg-compliance",
|
|
54
|
-
label: "Completeness",
|
|
55
|
-
value: `${avgPct}%`,
|
|
56
|
-
delta: "—",
|
|
57
|
-
trend: "neutral",
|
|
58
|
-
href: "#",
|
|
59
|
-
},
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function placementKpiInsightFromRows(rows: Placement[]): MetricInsight {
|
|
64
|
-
const pending = rows.filter(p => p.status === "pending").length
|
|
65
|
-
const inReview = rows.filter(p => p.status === "under-review").length
|
|
66
|
-
const n = rows.length
|
|
67
|
-
return {
|
|
68
|
-
title: "Queue snapshot",
|
|
69
|
-
description:
|
|
70
|
-
n > 0
|
|
71
|
-
? `${pending} pending, ${inReview} in review in this view. Clear the queue to keep work moving.`
|
|
72
|
-
: "No rows match the current filters.",
|
|
73
|
-
href: "/data-list",
|
|
74
|
-
severity: pending + inReview > 0 ? "warning" : "info",
|
|
75
|
-
actionLabel: "Ask Leo",
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* KPI row for the list hub metrics strip (demo numbers).
|
|
81
|
-
*/
|
|
82
|
-
export const PLACEMENT_KPI_METRICS: MetricItem[] = [
|
|
83
|
-
{
|
|
84
|
-
id: "total-placements",
|
|
85
|
-
label: "Total rows",
|
|
86
|
-
value: 50,
|
|
87
|
-
delta: "+12",
|
|
88
|
-
trend: "up",
|
|
89
|
-
href: "#all",
|
|
90
|
-
metricVariant: "hero",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: "starting-week",
|
|
94
|
-
label: "Due this week",
|
|
95
|
-
value: 0,
|
|
96
|
-
delta: "-5",
|
|
97
|
-
trend: "down",
|
|
98
|
-
href: "#starting",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: "compliance-alerts",
|
|
102
|
-
label: "Attention flags",
|
|
103
|
-
value: 23,
|
|
104
|
-
delta: "+13",
|
|
105
|
-
trend: "up",
|
|
106
|
-
href: "#alerts",
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: "avg-compliance",
|
|
110
|
-
label: "Completeness",
|
|
111
|
-
value: "87%",
|
|
112
|
-
delta: "+3",
|
|
113
|
-
trend: "up",
|
|
114
|
-
href: "#compliance",
|
|
115
|
-
},
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Insight copy derived from demo row status counts.
|
|
120
|
-
*/
|
|
121
|
-
export function getPlacementInsight(): MetricInsight {
|
|
122
|
-
const pending = statusCount("pending")
|
|
123
|
-
const inReview = statusCount("under-review")
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
title: "Queue snapshot",
|
|
127
|
-
description: `${pending} pending, ${inReview} in review. Clear the queue to keep work moving.`,
|
|
128
|
-
href: "/data-list",
|
|
129
|
-
severity: "warning",
|
|
130
|
-
actionLabel: "Ask Leo",
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export const PLACEMENT_KPI_INSIGHT = getPlacementInsight()
|