@exxatdesignux/ui 0.0.5 → 0.0.7

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.
Files changed (264) hide show
  1. package/bin/init.mjs +29 -0
  2. package/package.json +7 -2
  3. package/template/.nvmrc +1 -0
  4. package/template/.prettierignore +7 -0
  5. package/template/.prettierrc +11 -0
  6. package/template/AGENTS.md +485 -0
  7. package/template/Logo/Exxat_Prism.svg +39 -0
  8. package/template/Logo/Exxat_one.svg +36 -0
  9. package/template/README.md +58 -0
  10. package/template/app/(app)/compliance/page.tsx +10 -0
  11. package/template/app/(app)/dashboard/loading.tsx +18 -0
  12. package/template/app/(app)/dashboard/page.tsx +36 -0
  13. package/template/app/(app)/data-list/[id]/page.tsx +28 -0
  14. package/template/app/(app)/data-list/new/page.tsx +31 -0
  15. package/template/app/(app)/data-list/page.tsx +10 -0
  16. package/template/app/(app)/error.tsx +43 -0
  17. package/template/app/(app)/help/page.tsx +34 -0
  18. package/template/app/(app)/layout.tsx +54 -0
  19. package/template/app/(app)/loading.tsx +18 -0
  20. package/template/app/(app)/question-bank/page.tsx +10 -0
  21. package/template/app/(app)/rotations/page.tsx +15 -0
  22. package/template/app/(app)/settings/page.tsx +17 -0
  23. package/template/app/(app)/sites/all/page.tsx +13 -0
  24. package/template/app/(app)/team/page.tsx +10 -0
  25. package/template/app/favicon.ico +0 -0
  26. package/template/app/globals.css +1811 -0
  27. package/template/app/layout.tsx +95 -0
  28. package/template/app/page.tsx +9 -0
  29. package/template/components/.gitkeep +0 -0
  30. package/template/components/app-sidebar-dynamic.tsx +15 -0
  31. package/template/components/app-sidebar.tsx +901 -0
  32. package/template/components/ask-leo-composer.tsx +216 -0
  33. package/template/components/ask-leo-sidebar.tsx +509 -0
  34. package/template/components/chart-area-interactive.tsx +293 -0
  35. package/template/components/charts-overview.tsx +2321 -0
  36. package/template/components/command-menu-01.tsx +133 -0
  37. package/template/components/command-menu-02.tsx +386 -0
  38. package/template/components/command-menu.tsx +182 -0
  39. package/template/components/compliance-board-view.tsx +134 -0
  40. package/template/components/compliance-client.tsx +92 -0
  41. package/template/components/compliance-list-view.tsx +59 -0
  42. package/template/components/compliance-page-header.tsx +89 -0
  43. package/template/components/compliance-table.tsx +525 -0
  44. package/template/components/dashboard-onboarding-gallery.tsx +13 -0
  45. package/template/components/dashboard-onboarding.tsx +21 -0
  46. package/template/components/dashboard-promo-banner.tsx +67 -0
  47. package/template/components/dashboard-quota-progress-card.tsx +369 -0
  48. package/template/components/dashboard-report-charts.tsx +69 -0
  49. package/template/components/dashboard-section-heading.tsx +68 -0
  50. package/template/components/dashboard-tabs.tsx +598 -0
  51. package/template/components/data-list-client.tsx +239 -0
  52. package/template/components/data-list-table-cells.test.tsx +22 -0
  53. package/template/components/data-list-table-cells.tsx +173 -0
  54. package/template/components/data-list-table.tsx +879 -0
  55. package/template/components/data-table/filter-date-calendar.tsx +38 -0
  56. package/template/components/data-table/filter-text-value-input.tsx +77 -0
  57. package/template/components/data-table/index.tsx +1612 -0
  58. package/template/components/data-table/pagination.tsx +256 -0
  59. package/template/components/data-table/types.ts +91 -0
  60. package/template/components/data-table/use-table-state.ts +566 -0
  61. package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
  62. package/template/components/data-view-dashboard-charts-team.tsx +968 -0
  63. package/template/components/data-view-dashboard-charts.tsx +1668 -0
  64. package/template/components/data-views/board-card-primitives.tsx +93 -0
  65. package/template/components/data-views/index.ts +41 -0
  66. package/template/components/data-views/list-page-board-card.tsx +192 -0
  67. package/template/components/data-views/list-page-board-template.tsx +122 -0
  68. package/template/components/data-views/placement-board-card.tsx +262 -0
  69. package/template/components/export-drawer.tsx +375 -0
  70. package/template/components/exxat-product-logo.tsx +453 -0
  71. package/template/components/form-layout-01.tsx +131 -0
  72. package/template/components/getting-started.tsx +625 -0
  73. package/template/components/key-metrics.tsx +920 -0
  74. package/template/components/leo-insight-indicator.tsx +364 -0
  75. package/template/components/leo-typing-dots.tsx +121 -0
  76. package/template/components/list-hub-status-badge.tsx +51 -0
  77. package/template/components/list-page-dashboard-charts.tsx +18 -0
  78. package/template/components/nav-documents.tsx +89 -0
  79. package/template/components/nav-main.tsx +58 -0
  80. package/template/components/nav-secondary.tsx +64 -0
  81. package/template/components/nav-user.tsx +190 -0
  82. package/template/components/new-placement-back-btn.tsx +28 -0
  83. package/template/components/new-placement-form.tsx +1066 -0
  84. package/template/components/onboarding/index.ts +4 -0
  85. package/template/components/onboarding/onboarding-01.tsx +7 -0
  86. package/template/components/onboarding/onboarding-02.tsx +7 -0
  87. package/template/components/onboarding/onboarding-03.tsx +7 -0
  88. package/template/components/onboarding/onboarding-04.tsx +7 -0
  89. package/template/components/page-header.tsx +57 -0
  90. package/template/components/placement-detail.tsx +438 -0
  91. package/template/components/placements-board-view.tsx +404 -0
  92. package/template/components/placements-list-view.tsx +285 -0
  93. package/template/components/placements-page-header.tsx +160 -0
  94. package/template/components/placements-table-columns.tsx +639 -0
  95. package/template/components/product-switcher.tsx +116 -0
  96. package/template/components/question-bank-board-view.tsx +205 -0
  97. package/template/components/question-bank-client.tsx +77 -0
  98. package/template/components/question-bank-list-view.tsx +59 -0
  99. package/template/components/question-bank-page-header.tsx +89 -0
  100. package/template/components/question-bank-table.tsx +586 -0
  101. package/template/components/rotations-empty-state.tsx +47 -0
  102. package/template/components/rotations-panel-activator.tsx +8 -0
  103. package/template/components/secondary-nav.tsx +394 -0
  104. package/template/components/secondary-panel.tsx +239 -0
  105. package/template/components/section-cards.tsx +106 -0
  106. package/template/components/settings-appearance-card.tsx +424 -0
  107. package/template/components/settings-client.tsx +537 -0
  108. package/template/components/settings-form-row.tsx +42 -0
  109. package/template/components/sidebar-auto-collapse.tsx +23 -0
  110. package/template/components/sidebar-auto-open.tsx +18 -0
  111. package/template/components/sidebar-shell.tsx +37 -0
  112. package/template/components/site-header.tsx +93 -0
  113. package/template/components/sites-all-client.tsx +154 -0
  114. package/template/components/sites-board-view.tsx +67 -0
  115. package/template/components/sites-list-view.tsx +47 -0
  116. package/template/components/sites-table.tsx +312 -0
  117. package/template/components/system-banner-slot.tsx +66 -0
  118. package/template/components/table-properties/column-row.tsx +90 -0
  119. package/template/components/table-properties/draggable-list.ts +49 -0
  120. package/template/components/table-properties/drawer-button.tsx +231 -0
  121. package/template/components/table-properties/drawer.tsx +1102 -0
  122. package/template/components/table-properties/filter-card.tsx +251 -0
  123. package/template/components/table-properties/index.ts +22 -0
  124. package/template/components/table-properties/sort-card.tsx +59 -0
  125. package/template/components/table-properties/types.ts +124 -0
  126. package/template/components/task-list-panel.tsx +98 -0
  127. package/template/components/task-priority-badge.tsx +28 -0
  128. package/template/components/team-board-view.tsx +114 -0
  129. package/template/components/team-client.tsx +93 -0
  130. package/template/components/team-list-view.tsx +62 -0
  131. package/template/components/team-page-header.tsx +92 -0
  132. package/template/components/team-table.tsx +525 -0
  133. package/template/components/templates/list-page.tsx +576 -0
  134. package/template/components/templates/primary-page-template.tsx +56 -0
  135. package/template/components/theme-color-sync.tsx +32 -0
  136. package/template/components/theme-provider.tsx +71 -0
  137. package/template/components/tinted-icon-disc.tsx +53 -0
  138. package/template/components/ui/ai-thinking-surface.tsx +121 -0
  139. package/template/components/ui/avatar.tsx +1 -0
  140. package/template/components/ui/badge.tsx +1 -0
  141. package/template/components/ui/banner.tsx +1 -0
  142. package/template/components/ui/breadcrumb.tsx +1 -0
  143. package/template/components/ui/button.tsx +1 -0
  144. package/template/components/ui/calendar.tsx +1 -0
  145. package/template/components/ui/card.tsx +1 -0
  146. package/template/components/ui/chart.tsx +1 -0
  147. package/template/components/ui/checkbox.tsx +1 -0
  148. package/template/components/ui/coach-mark.tsx +1 -0
  149. package/template/components/ui/collapsible.tsx +1 -0
  150. package/template/components/ui/command.tsx +1 -0
  151. package/template/components/ui/date-picker-field.tsx +1 -0
  152. package/template/components/ui/dialog.tsx +1 -0
  153. package/template/components/ui/dot-pattern.tsx +159 -0
  154. package/template/components/ui/drag-handle-grip.tsx +1 -0
  155. package/template/components/ui/drawer.tsx +1 -0
  156. package/template/components/ui/dropdown-menu.tsx +1 -0
  157. package/template/components/ui/field.tsx +1 -0
  158. package/template/components/ui/form.tsx +1 -0
  159. package/template/components/ui/input-group.tsx +1 -0
  160. package/template/components/ui/input-mask.tsx +1 -0
  161. package/template/components/ui/input.tsx +1 -0
  162. package/template/components/ui/kbd.tsx +1 -0
  163. package/template/components/ui/label.tsx +1 -0
  164. package/template/components/ui/leo-icon.tsx +726 -0
  165. package/template/components/ui/payment-card-fields.tsx +1 -0
  166. package/template/components/ui/popover.tsx +1 -0
  167. package/template/components/ui/radio-group.tsx +1 -0
  168. package/template/components/ui/select.tsx +1 -0
  169. package/template/components/ui/selection-tile-grid.tsx +1 -0
  170. package/template/components/ui/separator.tsx +1 -0
  171. package/template/components/ui/sheet.tsx +1 -0
  172. package/template/components/ui/sidebar.tsx +1 -0
  173. package/template/components/ui/skeleton.tsx +1 -0
  174. package/template/components/ui/sonner.tsx +1 -0
  175. package/template/components/ui/status-badge.tsx +1 -0
  176. package/template/components/ui/table.tsx +1 -0
  177. package/template/components/ui/tabs.tsx +1 -0
  178. package/template/components/ui/textarea.tsx +1 -0
  179. package/template/components/ui/tip.tsx +1 -0
  180. package/template/components/ui/toggle-group.tsx +1 -0
  181. package/template/components/ui/toggle-switch.tsx +1 -0
  182. package/template/components/ui/toggle.tsx +1 -0
  183. package/template/components/ui/tooltip.tsx +1 -0
  184. package/template/components/ui/view-segmented-control.tsx +1 -0
  185. package/template/components.json +27 -0
  186. package/template/contexts/chart-variant-context.tsx +35 -0
  187. package/template/contexts/command-menu-context.tsx +28 -0
  188. package/template/contexts/dashboard-view-context.tsx +35 -0
  189. package/template/contexts/product-context.tsx +38 -0
  190. package/template/contexts/system-banner-context.tsx +127 -0
  191. package/template/docs/command-menu-pattern.md +45 -0
  192. package/template/docs/data-views-pattern.md +160 -0
  193. package/template/ecosystem.config.cjs +20 -0
  194. package/template/eslint.config.mjs +18 -0
  195. package/template/fontawesome-subset.manifest.json +190 -0
  196. package/template/hooks/.gitkeep +0 -0
  197. package/template/hooks/use-app-theme.ts +1 -0
  198. package/template/hooks/use-coach-mark.ts +1 -0
  199. package/template/hooks/use-mobile.ts +1 -0
  200. package/template/hooks/use-mod-key-label.ts +1 -0
  201. package/template/lib/.gitkeep +0 -0
  202. package/template/lib/ask-leo-route-context.ts +133 -0
  203. package/template/lib/chart-keyboard-selection.test.ts +20 -0
  204. package/template/lib/chart-keyboard-selection.ts +17 -0
  205. package/template/lib/chart-line-dash.ts +16 -0
  206. package/template/lib/coach-mark-registry.ts +68 -0
  207. package/template/lib/command-menu-config.ts +127 -0
  208. package/template/lib/command-menu-search-data.ts +44 -0
  209. package/template/lib/conditional-rule-match.ts +32 -0
  210. package/template/lib/dashboard-customize-coach-mark.ts +18 -0
  211. package/template/lib/dashboard-layout-merge.ts +63 -0
  212. package/template/lib/data-list-display-options.ts +35 -0
  213. package/template/lib/data-list-persistence.ts +280 -0
  214. package/template/lib/data-list-view-surface.ts +58 -0
  215. package/template/lib/data-list-view.ts +29 -0
  216. package/template/lib/data-view-dashboard-storage.ts +101 -0
  217. package/template/lib/date-filter.ts +8 -0
  218. package/template/lib/dev-log.test.ts +28 -0
  219. package/template/lib/dev-log.ts +8 -0
  220. package/template/lib/editable-target.ts +10 -0
  221. package/template/lib/floating-sheet-panel.ts +72 -0
  222. package/template/lib/initials-from-name.ts +7 -0
  223. package/template/lib/list-page-table-properties.ts +52 -0
  224. package/template/lib/list-status-badges.ts +168 -0
  225. package/template/lib/logo-dev.ts +12 -0
  226. package/template/lib/mock/compliance-kpi.ts +61 -0
  227. package/template/lib/mock/compliance.ts +146 -0
  228. package/template/lib/mock/dashboard.ts +105 -0
  229. package/template/lib/mock/navigation.tsx +231 -0
  230. package/template/lib/mock/placements-kpi.ts +134 -0
  231. package/template/lib/mock/placements.ts +183 -0
  232. package/template/lib/mock/question-bank-kpi.ts +61 -0
  233. package/template/lib/mock/question-bank.ts +142 -0
  234. package/template/lib/mock/sites-directory.ts +16 -0
  235. package/template/lib/mock/sites-kpi.ts +25 -0
  236. package/template/lib/mock/team-kpi.ts +60 -0
  237. package/template/lib/mock/team.ts +118 -0
  238. package/template/lib/motion-ui.ts +17 -0
  239. package/template/lib/placement-board-card-layout.ts +79 -0
  240. package/template/lib/placement-lifecycle.ts +5 -0
  241. package/template/lib/row-height.ts +10 -0
  242. package/template/lib/stock-portrait.ts +11 -0
  243. package/template/lib/utils.test.ts +13 -0
  244. package/template/lib/utils.ts +1 -0
  245. package/template/next.config.mjs +15 -0
  246. package/template/package.json +83 -0
  247. package/template/postcss.config.mjs +8 -0
  248. package/template/public/.gitkeep +0 -0
  249. package/template/public/Illustration/Rotation.svg +74 -0
  250. package/template/public/avatars/user.svg +11 -0
  251. package/template/public/favicon/favicon.ico +0 -0
  252. package/template/public/favicon.ico +0 -0
  253. package/template/public/logos/exxat-one.svg +36 -0
  254. package/template/public/logos/exxat-prism.svg +39 -0
  255. package/template/public/mock-schools/emory.svg +4 -0
  256. package/template/public/mock-schools/rush.svg +4 -0
  257. package/template/scripts/fontawesome-subset-audit.mjs +190 -0
  258. package/template/scripts/pm2-startup-macos.sh +13 -0
  259. package/template/skills-lock.json +10 -0
  260. package/template/stores/app-store.ts +33 -0
  261. package/template/tests/setup.ts +1 -0
  262. package/template/tsconfig.json +35 -0
  263. package/template/types/react-payment-inputs.d.ts +19 -0
  264. package/template/vitest.config.ts +18 -0
@@ -0,0 +1,231 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Mock data — Navigation
3
+ // Uses .tsx because icons are JSX (<i> elements).
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+
6
+ import type * as React from "react"
7
+
8
+ import { logoDevUrl } from "@/lib/logo-dev"
9
+ import { stockPortraitUrl } from "@/lib/stock-portrait"
10
+
11
+ // ── Types ─────────────────────────────────────────────────────────────────────
12
+
13
+ /** Flat sidebar link (screenshot: primary + Documents + utilities) */
14
+ export interface NavLinkItem {
15
+ key: string
16
+ title: string
17
+ url: string
18
+ icon: React.ReactNode
19
+ iconActive?: React.ReactNode
20
+ /** Optional child links — renders as a collapsible sub-menu */
21
+ children?: NavLinkItem[]
22
+ /** Optional inline badge — count (number) or label text (string).
23
+ * Special string values get distinct colors:
24
+ * "New" → green, "Beta" → amber, other strings → brand/primary */
25
+ badge?: number | string
26
+ /**
27
+ * When this collapsible section uses `SecondaryPanel`, set the panel id so
28
+ * child links can reopen the panel on click while already on the same route
29
+ * (Next.js Link does not navigate on same href).
30
+ */
31
+ secondaryPanel?: string
32
+ /**
33
+ * When several children share the same `url` (hub route), only this child’s
34
+ * key receives `data-active` for that pathname — otherwise every sub-row
35
+ * looks selected (same issue as duplicate `href` in any nav).
36
+ */
37
+ primaryHubChildKey?: string
38
+ }
39
+
40
+ // ── Team / school switcher mock data ─────────────────────────────────────────
41
+
42
+ export interface NavProgram { id: string; name: string }
43
+ export interface NavSchool {
44
+ id: string
45
+ name: string
46
+ /** URL of school logo — used as the avatar image */
47
+ logo: string
48
+ /** Two-letter abbreviation shown as AvatarFallback */
49
+ initials: string
50
+ programs: NavProgram[]
51
+ }
52
+
53
+ export const NAV_SCHOOLS: NavSchool[] = [
54
+ {
55
+ id: "jhu",
56
+ name: "Johns Hopkins University",
57
+ logo: logoDevUrl("jhu.edu"),
58
+ initials: "JH",
59
+ programs: [
60
+ { id: "som", name: "School of Medicine" },
61
+ { id: "son", name: "School of Nursing" },
62
+ { id: "sph", name: "Bloomberg School of Public Health" },
63
+ ],
64
+ },
65
+ {
66
+ id: "mayo",
67
+ name: "Mayo Clinic Alix School of Medicine",
68
+ logo: logoDevUrl("mayoclinic.org"),
69
+ initials: "MC",
70
+ programs: [
71
+ { id: "md", name: "Doctor of Medicine" },
72
+ { id: "bms", name: "Biomedical Sciences" },
73
+ ],
74
+ },
75
+ ]
76
+
77
+ export const NAV_SCHOOL_DEFAULT = NAV_SCHOOLS[0]
78
+ export const NAV_PROGRAM_DEFAULT = NAV_SCHOOLS[0].programs[0]
79
+
80
+ // ── Primary navigation ────────────────────────────────────────────────────────
81
+
82
+ export const NAV_PRIMARY: NavLinkItem[] = [
83
+ {
84
+ key: "dashboard",
85
+ title: "Dashboard",
86
+ url: "/dashboard",
87
+ icon: <i className="fa-light fa-grid-2" aria-hidden="true" />,
88
+ iconActive: <i className="fa-solid fa-grid-2" aria-hidden="true" />,
89
+ },
90
+ {
91
+ key: "question-bank",
92
+ title: "Question bank",
93
+ url: "/question-bank",
94
+ icon: <i className="fa-light fa-books" aria-hidden="true" />,
95
+ iconActive: <i className="fa-solid fa-books" aria-hidden="true" />,
96
+ },
97
+ {
98
+ key: "data-list",
99
+ title: "Placements",
100
+ url: "/data-list",
101
+ icon: <i className="fa-light fa-user-graduate" aria-hidden="true" />,
102
+ iconActive: <i className="fa-solid fa-user-graduate" aria-hidden="true" />,
103
+ badge: 24,
104
+ },
105
+ {
106
+ key: "rotations",
107
+ title: "Rotations",
108
+ url: "/rotations",
109
+ icon: <i className="fa-light fa-arrows-rotate" aria-hidden="true" />,
110
+ iconActive: <i className="fa-solid fa-arrows-rotate" aria-hidden="true" />,
111
+ secondaryPanel: "rotations",
112
+ primaryHubChildKey: "view-all-rotations",
113
+ children: [
114
+ {
115
+ key: "rotation-1",
116
+ title: "Clinical Nursing — Fall 2026",
117
+ url: "/rotations",
118
+ icon: <i className="fa-light fa-folder" aria-hidden="true" />,
119
+ },
120
+ {
121
+ key: "rotation-2",
122
+ title: "PT Fieldwork — Spring 2026",
123
+ url: "/rotations",
124
+ icon: <i className="fa-light fa-folder" aria-hidden="true" />,
125
+ },
126
+ {
127
+ key: "rotation-3",
128
+ title: "OT Level II — Summer 2026",
129
+ url: "/rotations",
130
+ icon: <i className="fa-light fa-folder" aria-hidden="true" />,
131
+ },
132
+ {
133
+ key: "view-all-rotations",
134
+ title: "View all",
135
+ url: "/rotations",
136
+ icon: <i className="fa-light fa-arrow-right" aria-hidden="true" />,
137
+ },
138
+ ],
139
+ },
140
+ {
141
+ key: "sites",
142
+ title: "Sites",
143
+ url: "/sites/all",
144
+ icon: <i className="fa-light fa-hospital" aria-hidden="true" />,
145
+ iconActive: <i className="fa-solid fa-hospital" aria-hidden="true" />,
146
+ children: Array.from({ length: 45 }, (_, i) => ({
147
+ key: `site-${i + 1}`,
148
+ title: `Site ${String(i + 1).padStart(2, "0")}`,
149
+ url: `/sites/all#site-${i + 1}`,
150
+ icon: <i className="fa-light fa-hospital" aria-hidden="true" />,
151
+ })),
152
+ },
153
+ ]
154
+
155
+ // ── Documents section ───────────────────────────────────────────────────────
156
+
157
+ export const NAV_DOCUMENTS_LABEL = "Documents"
158
+
159
+ export const NAV_DOCUMENTS: NavLinkItem[] = [
160
+ {
161
+ key: "word-assistant",
162
+ title: "Word Assistant",
163
+ url: "#",
164
+ icon: <i className="fa-light fa-file-pen" aria-hidden="true" />,
165
+ iconActive: <i className="fa-solid fa-file-pen" aria-hidden="true" />,
166
+ badge: "Beta",
167
+ },
168
+ {
169
+ key: "more",
170
+ title: "More",
171
+ url: "#",
172
+ icon: <i className="fa-light fa-ellipsis" aria-hidden="true" />,
173
+ iconActive: <i className="fa-solid fa-ellipsis" aria-hidden="true" />,
174
+ },
175
+ ]
176
+
177
+ // ── Quick actions (above primary nav) + bottom utilities ───────────────────────
178
+
179
+ /** `opensCommandMenu` wires ⌘K instead of `url`. */
180
+ export interface NavSecondaryItem {
181
+ key: string
182
+ title: string
183
+ url: string
184
+ icon: React.ReactNode
185
+ /** Solid / filled variant when the route is active — matches primary `NavLinkItem` rows. */
186
+ iconActive?: React.ReactNode
187
+ opensCommandMenu?: boolean
188
+ }
189
+
190
+ /** Search + Notifications — rendered above Dashboard in the sidebar. */
191
+ export const NAV_QUICK_ACTIONS: NavSecondaryItem[] = [
192
+ {
193
+ key: "command-menu",
194
+ title: "Search or ask Leo",
195
+ url: "#",
196
+ icon: <i className="fa-light fa-magnifying-glass" aria-hidden="true" />,
197
+ opensCommandMenu: true,
198
+ },
199
+ {
200
+ key: "notifications",
201
+ title: "Notifications",
202
+ url: "#",
203
+ icon: <i className="fa-light fa-bell" aria-hidden="true" />,
204
+ },
205
+ ]
206
+
207
+ export const NAV_SECONDARY: NavSecondaryItem[] = [
208
+ {
209
+ key: "settings",
210
+ title: "Settings",
211
+ url: "/settings",
212
+ icon: <i className="fa-light fa-gear" aria-hidden="true" />,
213
+ iconActive: <i className="fa-solid fa-gear" aria-hidden="true" />,
214
+ },
215
+ {
216
+ key: "help",
217
+ title: "Get Help",
218
+ url: "/help",
219
+ icon: <i className="fa-light fa-circle-question" aria-hidden="true" />,
220
+ iconActive: <i className="fa-solid fa-circle-question" aria-hidden="true" />,
221
+ },
222
+ ]
223
+
224
+ // ── User ──────────────────────────────────────────────────────────────────────
225
+
226
+ export const NAV_USER = {
227
+ name: "Jordan Rivera",
228
+ email: "jordan.rivera@jhmi.edu",
229
+ /** Stock portrait (randomuser.me); stable for this seed */
230
+ avatar: stockPortraitUrl("exxat-nav-user-jordan-rivera"),
231
+ }
@@ -0,0 +1,134 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Placements page — 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 placement 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 Placements",
30
+ value: total,
31
+ delta: "—",
32
+ trend: "neutral",
33
+ href: "#",
34
+ metricVariant: "hero",
35
+ },
36
+ {
37
+ id: "starting-week",
38
+ label: "Starting This Week",
39
+ value: startingWeek,
40
+ delta: "—",
41
+ trend: startingWeek > 0 ? "up" : "neutral",
42
+ href: "#",
43
+ },
44
+ {
45
+ id: "compliance-alerts",
46
+ label: "Readiness alerts",
47
+ value: alerts,
48
+ delta: "—",
49
+ trend: alerts > 0 ? "up" : "neutral",
50
+ href: "#",
51
+ },
52
+ {
53
+ id: "avg-compliance",
54
+ label: "Compliance complete",
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: "Pending Reviews",
69
+ description:
70
+ n > 0
71
+ ? `${pending} pending, ${inReview} in review in this view. Clear the queue to keep placements moving.`
72
+ : "No placements match the current filters.",
73
+ href: "/placements/reviews",
74
+ severity: pending + inReview > 0 ? "warning" : "info",
75
+ actionLabel: "Ask Leo",
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Placements KPI row — matches design reference (totals + operational metrics).
81
+ */
82
+ export const PLACEMENT_KPI_METRICS: MetricItem[] = [
83
+ {
84
+ id: "total-placements",
85
+ label: "Total Placements",
86
+ value: 50,
87
+ delta: "+12",
88
+ trend: "up",
89
+ href: "#all",
90
+ metricVariant: "hero",
91
+ },
92
+ {
93
+ id: "starting-week",
94
+ label: "Starting This Week",
95
+ value: 0,
96
+ delta: "-5",
97
+ trend: "down",
98
+ href: "#starting",
99
+ },
100
+ {
101
+ id: "compliance-alerts",
102
+ label: "Compliance Alerts",
103
+ value: 23,
104
+ delta: "+13",
105
+ trend: "up",
106
+ href: "#alerts",
107
+ },
108
+ {
109
+ id: "avg-compliance",
110
+ label: "Avg Compliance",
111
+ value: "87%",
112
+ delta: "+3",
113
+ trend: "up",
114
+ href: "#compliance",
115
+ },
116
+ ]
117
+
118
+ /**
119
+ * Insight copy still derived from live placement rows for the queue narrative.
120
+ */
121
+ export function getPlacementInsight(): MetricInsight {
122
+ const pending = statusCount("pending")
123
+ const inReview = statusCount("under-review")
124
+
125
+ return {
126
+ title: "Pending Reviews",
127
+ description: `${pending} pending, ${inReview} in review. Clear the queue to keep placements moving.`,
128
+ href: "/placements/reviews",
129
+ severity: "warning",
130
+ actionLabel: "Ask Leo",
131
+ }
132
+ }
133
+
134
+ export const PLACEMENT_KPI_INSIGHT = getPlacementInsight()
@@ -0,0 +1,183 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Mock data — Placements
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ export type Status = "confirmed" | "pending" | "under-review" | "rejected" | "completed"
6
+
7
+ /** Which lifecycle tab owns this row (All shows every row). */
8
+ export type PlacementPhase = "upcoming" | "ongoing" | "completed"
9
+
10
+ export interface Placement extends Record<string, unknown> {
11
+ id: number
12
+ student: string
13
+ email: string
14
+ initials: string
15
+ program: string
16
+ site: string
17
+ siteAddress: string
18
+ status: Status
19
+ start: string
20
+ duration: string
21
+ supervisor: string
22
+ placementPhase: PlacementPhase
23
+ /** Display title for internship column */
24
+ internship: string
25
+ specialization: string
26
+ /** Upcoming */
27
+ compliance: string
28
+ daysUntilStart: number
29
+ readiness: string
30
+ /** Ongoing */
31
+ progressWeeksDone: number
32
+ progressWeeksTotal: number
33
+ endDate: string
34
+ lastCheckin: string
35
+ /** Completed */
36
+ completionDate: string
37
+ finalStatus: string
38
+ rating: number
39
+ suggestedToHire: string
40
+ /** Visual indicator — row has new activity or was recently updated */
41
+ isNew?: boolean
42
+ }
43
+
44
+ export const ALL_PLACEMENTS: Placement[] = [
45
+ {
46
+ id: 1, student: "Sarah Johnson", email: "s.johnson@college.edu", initials: "SJ", program: "Nursing", site: "City Medical Center",
47
+ siteAddress: "1400 N Lake Shore Dr, Chicago, IL", status: "confirmed", start: "03/15/2026", duration: "12 wks", supervisor: "Dr. Patel",
48
+ placementPhase: "ongoing", internship: "Med-Surg Clinical I", specialization: "Adult Health", compliance: "Complete", daysUntilStart: 0,
49
+ readiness: "Ready", progressWeeksDone: 5, progressWeeksTotal: 12, endDate: "06/07/2026", lastCheckin: "Mar 22, 2026",
50
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
51
+ },
52
+ {
53
+ id: 2, student: "Mike Chen", email: "m.chen@university.edu", initials: "MC", program: "Physical Therapy", site: "Metro Rehab",
54
+ siteAddress: "250 E Superior St, Chicago, IL", status: "pending", start: "04/01/2026", duration: "8 wks", supervisor: "Dr. Kim",
55
+ placementPhase: "upcoming", internship: "Outpatient PT Rotation", specialization: "Orthopedics", compliance: "In progress", daysUntilStart: 8,
56
+ readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "05/27/2026", lastCheckin: "—",
57
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
58
+ },
59
+ {
60
+ id: 3, student: "Amy Park", email: "a.park@school.edu", initials: "AP", program: "Occupational Therapy", site: "Bay Area Health",
61
+ siteAddress: "3100 Telegraph Ave, Oakland, CA", status: "under-review", start: "03/28/2026", duration: "10 wks", supervisor: "Unassigned",
62
+ placementPhase: "upcoming", internship: "Acute Care OT", specialization: "Hand Therapy", compliance: "Pending documents", daysUntilStart: 4,
63
+ readiness: "At risk", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "06/06/2026", lastCheckin: "—",
64
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
65
+ },
66
+ {
67
+ id: 4, student: "James Williams", email: "j.williams@college.edu", initials: "JW", program: "Nursing", site: "Sunrise Hospital",
68
+ siteAddress: "5775 Wayzata Blvd, Minneapolis, MN", status: "confirmed", start: "04/07/2026", duration: "12 wks", supervisor: "Dr. Torres",
69
+ placementPhase: "ongoing", internship: "ICU Practicum", specialization: "Critical Care", compliance: "Complete", daysUntilStart: 0,
70
+ readiness: "Ready", progressWeeksDone: 2, progressWeeksTotal: 12, endDate: "06/30/2026", lastCheckin: "Mar 21, 2026",
71
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
72
+ },
73
+ {
74
+ id: 5, student: "Emily Davis", email: "e.davis@university.edu", initials: "ED", program: "Social Work", site: "Community Care",
75
+ siteAddress: "220 S State St, Chicago, IL", status: "rejected", start: "—", duration: "—", supervisor: "—",
76
+ placementPhase: "upcoming", internship: "Community SW Field", specialization: "Behavioral Health", compliance: "Incomplete", daysUntilStart: 0,
77
+ readiness: "Blocked", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "—", lastCheckin: "—",
78
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
79
+ },
80
+ {
81
+ id: 6, student: "Carlos Rivera", email: "c.rivera@school.edu", initials: "CR", program: "Physical Therapy", site: "Summit Sports Med",
82
+ siteAddress: "900 N Michigan Ave, Chicago, IL", status: "pending", start: "04/14/2026", duration: "8 wks", supervisor: "Dr. Lee",
83
+ placementPhase: "upcoming", internship: "Sports Medicine PT", specialization: "Sports Rehab", compliance: "Complete", daysUntilStart: 21,
84
+ readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "06/09/2026", lastCheckin: "—",
85
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
86
+ },
87
+ {
88
+ id: 7, student: "Priya Sharma", email: "p.sharma@college.edu", initials: "PS", program: "Nursing", site: "Harbor Medical",
89
+ siteAddress: "1200 W Harrison St, Chicago, IL", status: "confirmed", start: "03/22/2026", duration: "12 wks", supervisor: "Dr. Patel",
90
+ placementPhase: "ongoing", internship: "Pediatric Nursing", specialization: "Pediatrics", compliance: "Complete", daysUntilStart: 0,
91
+ readiness: "Ready", progressWeeksDone: 7, progressWeeksTotal: 12, endDate: "06/14/2026", lastCheckin: "Mar 23, 2026",
92
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
93
+ },
94
+ {
95
+ id: 8, student: "Tom Bradley", email: "t.bradley@university.edu", initials: "TB", program: "Occupational Therapy", site: "Valley Health",
96
+ siteAddress: "4646 N Marine Dr, Chicago, IL", status: "under-review", start: "04/03/2026", duration: "10 wks", supervisor: "Dr. Wong",
97
+ placementPhase: "upcoming", internship: "Rehabilitation OT", specialization: "Neuro", compliance: "Review", daysUntilStart: 10,
98
+ readiness: "In review", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "06/12/2026", lastCheckin: "—",
99
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
100
+ },
101
+ {
102
+ id: 9, student: "Lena Fischer", email: "l.fischer@school.edu", initials: "LF", program: "Nursing", site: "Westside Clinic",
103
+ siteAddress: "2525 S Michigan Ave, Chicago, IL", status: "confirmed", start: "03/18/2026", duration: "12 wks", supervisor: "Dr. Santos",
104
+ placementPhase: "ongoing", internship: "Primary Care RN", specialization: "Family Practice", compliance: "Complete", daysUntilStart: 0,
105
+ readiness: "Ready", progressWeeksDone: 8, progressWeeksTotal: 12, endDate: "06/10/2026", lastCheckin: "Mar 24, 2026",
106
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
107
+ },
108
+ {
109
+ id: 10, student: "Omar Hassan", email: "o.hassan@college.edu", initials: "OH", program: "Physical Therapy", site: "Metro Rehab",
110
+ siteAddress: "250 E Superior St, Chicago, IL", status: "pending", start: "04/21/2026", duration: "8 wks", supervisor: "Dr. Kim",
111
+ placementPhase: "upcoming", internship: "Neurologic PT", specialization: "Neuro Rehab", compliance: "Pending", daysUntilStart: 28,
112
+ readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "06/16/2026", lastCheckin: "—",
113
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
114
+ },
115
+ {
116
+ id: 11, student: "Nina Patel", email: "n.patel@university.edu", initials: "NP", program: "Social Work", site: "Hope Community Ctr",
117
+ siteAddress: "3517 W Arthington St, Chicago, IL", status: "confirmed", start: "03/25/2026", duration: "10 wks", supervisor: "Ms. Torres",
118
+ placementPhase: "ongoing", internship: "School Social Work", specialization: "Youth Services", compliance: "Complete", daysUntilStart: 0,
119
+ readiness: "Ready", progressWeeksDone: 4, progressWeeksTotal: 10, endDate: "06/03/2026", lastCheckin: "Mar 19, 2026",
120
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
121
+ },
122
+ {
123
+ id: 12, student: "David Okonkwo", email: "d.okonkwo@school.edu", initials: "DO", program: "Nursing", site: "Lakeside Hospital",
124
+ siteAddress: "4440 N Clark St, Chicago, IL", status: "under-review", start: "04/10/2026", duration: "12 wks", supervisor: "Unassigned",
125
+ placementPhase: "upcoming", internship: "Emergency Dept RN", specialization: "Emergency", compliance: "Pending", daysUntilStart: 17,
126
+ readiness: "At risk", progressWeeksDone: 0, progressWeeksTotal: 12, endDate: "07/03/2026", lastCheckin: "—",
127
+ completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
128
+ },
129
+ {
130
+ id: 13, student: "Alex Morgan", email: "a.morgan@college.edu", initials: "AM", program: "Nursing", site: "City Medical Center",
131
+ siteAddress: "1400 N Lake Shore Dr, Chicago, IL", status: "completed", start: "01/06/2026", duration: "12 wks", supervisor: "Dr. Patel",
132
+ placementPhase: "completed", internship: "Med-Surg Clinical II", specialization: "Adult Health", compliance: "Complete", daysUntilStart: 0,
133
+ readiness: "Ready", progressWeeksDone: 12, progressWeeksTotal: 12, endDate: "03/30/2026", lastCheckin: "Mar 28, 2026",
134
+ completionDate: "03/30/2026", finalStatus: "Passed", rating: 4.8, suggestedToHire: "Yes",
135
+ },
136
+ {
137
+ id: 14, student: "Jordan Lee", email: "j.lee@university.edu", initials: "JL", program: "Physical Therapy", site: "Metro Rehab",
138
+ siteAddress: "250 E Superior St, Chicago, IL", status: "completed", start: "11/04/2025", duration: "8 wks", supervisor: "Dr. Kim",
139
+ placementPhase: "completed", internship: "Inpatient PT", specialization: "Acute Care", compliance: "Complete", daysUntilStart: 0,
140
+ readiness: "Ready", progressWeeksDone: 8, progressWeeksTotal: 8, endDate: "12/30/2025", lastCheckin: "Dec 28, 2025",
141
+ completionDate: "12/30/2025", finalStatus: "Passed", rating: 4.2, suggestedToHire: "Yes",
142
+ },
143
+ {
144
+ id: 15, student: "Sam Rivera", email: "s.rivera@school.edu", initials: "SR", program: "Occupational Therapy", site: "Bay Area Health",
145
+ siteAddress: "3100 Telegraph Ave, Oakland, CA", status: "completed", start: "10/01/2025", duration: "10 wks", supervisor: "Dr. Nguyen",
146
+ placementPhase: "completed", internship: "Hand Therapy OT", specialization: "Hand Therapy", compliance: "Complete", daysUntilStart: 0,
147
+ readiness: "Ready", progressWeeksDone: 10, progressWeeksTotal: 10, endDate: "12/10/2025", lastCheckin: "Dec 08, 2025",
148
+ completionDate: "12/10/2025", finalStatus: "Incomplete", rating: 3.5, suggestedToHire: "No",
149
+ },
150
+ {
151
+ id: 16, student: "Taylor Brooks", email: "t.brooks@college.edu", initials: "TB", program: "Nursing", site: "Sunrise Hospital",
152
+ siteAddress: "5775 Wayzata Blvd, Minneapolis, MN", status: "completed", start: "09/02/2025", duration: "12 wks", supervisor: "Dr. Torres",
153
+ placementPhase: "completed", internship: "Labor & Delivery", specialization: "Women's Health", compliance: "Complete", daysUntilStart: 0,
154
+ readiness: "Ready", progressWeeksDone: 12, progressWeeksTotal: 12, endDate: "11/25/2025", lastCheckin: "Nov 22, 2025",
155
+ completionDate: "11/25/2025", finalStatus: "Passed", rating: 5, suggestedToHire: "Yes",
156
+ },
157
+ ]
158
+
159
+ /** Distinct sorted values for filter dropdowns (list UI) on text-like columns. */
160
+ export function uniquePlacementFieldOptions(
161
+ key: keyof Placement,
162
+ ): { value: string; label: string }[] {
163
+ const set = new Set<string>()
164
+ for (const r of ALL_PLACEMENTS) {
165
+ const raw = r[key]
166
+ if (raw === undefined || raw === null) continue
167
+ const v = typeof raw === "number" ? String(raw) : String(raw).trim()
168
+ if (!v || v === "—") continue
169
+ set.add(v)
170
+ }
171
+ return [...set].sort((a, b) => a.localeCompare(b)).map(v => ({ value: v, label: v }))
172
+ }
173
+
174
+ export function placementsForPhase(
175
+ phase: "all" | PlacementPhase,
176
+ ): Placement[] {
177
+ if (phase === "all") return ALL_PLACEMENTS
178
+ return ALL_PLACEMENTS.filter((p) => p.placementPhase === phase)
179
+ }
180
+
181
+ export function getPlacementById(id: number): Placement | undefined {
182
+ return ALL_PLACEMENTS.find(p => p.id === id)
183
+ }
@@ -0,0 +1,61 @@
1
+ import type { MetricInsight, MetricItem } from "@/components/key-metrics"
2
+ import type { QuestionBankItem } from "@/lib/mock/question-bank"
3
+
4
+ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
5
+ const published = rows.filter(r => r.status === "published").length
6
+ const draft = rows.filter(r => r.status === "draft").length
7
+ const review = rows.filter(r => r.status === "in_review").length
8
+
9
+ return [
10
+ {
11
+ id: "total",
12
+ label: "Total questions",
13
+ value: rows.length,
14
+ delta: "—",
15
+ trend: "neutral",
16
+ href: "#",
17
+ metricVariant: "hero",
18
+ },
19
+ {
20
+ id: "published",
21
+ label: "Published",
22
+ value: published,
23
+ delta: "—",
24
+ trend: "neutral",
25
+ href: "#",
26
+ },
27
+ {
28
+ id: "review",
29
+ label: "In review",
30
+ value: review,
31
+ delta: review > 0 ? "!" : "—",
32
+ trend: review > 0 ? "up" : "neutral",
33
+ href: "#",
34
+ },
35
+ {
36
+ id: "draft",
37
+ label: "Drafts",
38
+ value: draft,
39
+ delta: "—",
40
+ trend: "neutral",
41
+ href: "#",
42
+ },
43
+ ]
44
+ }
45
+
46
+ export function questionBankKpiInsight(rows: QuestionBankItem[]): MetricInsight {
47
+ const review = rows.filter(r => r.status === "in_review").length
48
+ const draft = rows.filter(r => r.status === "draft").length
49
+ return {
50
+ title: "Question bank",
51
+ description:
52
+ review > 0
53
+ ? `${review} item(s) in review. ${draft} draft(s) not yet published.`
54
+ : draft > 0
55
+ ? `${draft} draft(s) ready to finalize or send for review.`
56
+ : "All items are published or in review with no backlog.",
57
+ href: "/question-bank",
58
+ severity: review > 2 ? "warning" : "info",
59
+ actionLabel: "Ask Leo",
60
+ }
61
+ }