@exxatdesignux/ui 0.0.6 → 0.0.8

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,598 @@
1
+ "use client"
2
+
3
+ /**
4
+ * DashboardTabs — three-view dashboard switcher
5
+ *
6
+ * Report — full KPI + charts view (current dashboard)
7
+ * Simple — Promo · Greeting · Onboarding gallery · Tasks · Insights · Recent · Learn
8
+ * Mix — compact metrics + key chart + tasks & activity side-by-side
9
+ */
10
+
11
+ import * as React from "react"
12
+ import { Card, CardContent, CardHeader, CardDescription } from "@/components/ui/card"
13
+ import { TintedIconDisc, type TintedIconDiscTone } from "@/components/tinted-icon-disc"
14
+ import { Separator } from "@/components/ui/separator"
15
+ import { KeyMetrics, type MetricItem, type MetricInsight } from "@/components/key-metrics"
16
+ import type { ChartCardVariant } from "@/components/charts-overview"
17
+ import { DashboardReportCharts } from "@/components/dashboard-report-charts"
18
+ import { GettingStarted, GettingStartedProgressCard } from "@/components/getting-started"
19
+ import { TaskListPanel, type TaskListItem } from "@/components/task-list-panel"
20
+ import { PageHeader } from "@/components/page-header"
21
+ import { useDashboardView } from "@/contexts/dashboard-view-context"
22
+ import { useChartVariant } from "@/contexts/chart-variant-context"
23
+ import { useAskLeo, useAskLeoPageContext } from "@/components/ask-leo-sidebar"
24
+ import { cn } from "@/lib/utils"
25
+ import {
26
+ DashboardSectionTitle,
27
+ dashboardSectionDescriptionClassName,
28
+ } from "@/components/dashboard-section-heading"
29
+ import { DashboardPromoBanner } from "@/components/dashboard-promo-banner"
30
+ import { CoachMark } from "@/components/ui/coach-mark"
31
+ import { useCoachMark } from "@/hooks/use-coach-mark"
32
+
33
+ /* ── Types passed from the page ─────────────────────────────────────────── */
34
+ interface DashboardTabsProps {
35
+ metrics: MetricItem[]
36
+ insight: MetricInsight
37
+ title?: string
38
+ subtitle?: string
39
+ }
40
+
41
+ /* ════════════════════════════════════════════════════════════════════════════
42
+ SIMPLE TAB — widgets
43
+ ════════════════════════════════════════════════════════════════════════════ */
44
+
45
+ /* ── Greeting ─────────────────────────────────────────────────────────────── */
46
+ function GreetingWidget({ compact = false }: { compact?: boolean }) {
47
+ const [now, setNow] = React.useState<Date | null>(null)
48
+ React.useEffect(() => {
49
+ setNow(new Date())
50
+ }, [])
51
+
52
+ const hour = now?.getHours() ?? 9
53
+ const greeting =
54
+ hour < 12 ? "Good morning" : hour < 17 ? "Good afternoon" : "Good evening"
55
+
56
+ return (
57
+ <div className="flex flex-col gap-3">
58
+ <div className="flex items-start justify-between gap-4">
59
+ <div>
60
+ {!compact ? (
61
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider" suppressHydrationWarning>
62
+ {now?.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }) ?? ""}
63
+ </p>
64
+ ) : null}
65
+ {compact ? (
66
+ <p
67
+ className="text-2xl font-semibold tracking-tight leading-tight text-foreground"
68
+ style={{ fontFamily: "var(--font-heading)" }}
69
+ >
70
+ {greeting}, Himanshu 👋
71
+ </p>
72
+ ) : (
73
+ <h2
74
+ className="text-2xl font-semibold tracking-tight leading-tight text-foreground"
75
+ style={{ fontFamily: "var(--font-heading)" }}
76
+ >
77
+ {greeting}, Himanshu 👋
78
+ </h2>
79
+ )}
80
+ {!compact ? (
81
+ <p className="mt-1 text-sm text-muted-foreground max-w-sm">
82
+ You have <span className="font-medium text-foreground">8 reviews</span> pending and{" "}
83
+ <span className="font-medium text-foreground">23 requests</span> waiting today.
84
+ </p>
85
+ ) : null}
86
+ </div>
87
+ <TintedIconDisc icon="fa-sun-bright" tone="brand" size="lg" />
88
+ </div>
89
+ </div>
90
+ )
91
+ }
92
+
93
+ /* ── Tasks ────────────────────────────────────────────────────────────────── */
94
+ const TASK_ITEMS: TaskListItem[] = [
95
+ { id: 1, label: "Review pending evaluations", due: "Today", priority: "high", done: false },
96
+ { id: 2, label: "Approve site contract — City Med", due: "Today", priority: "high", done: false },
97
+ { id: 3, label: "Send onboarding docs to PT cohort", due: "Tomorrow", priority: "medium", done: false },
98
+ { id: 4, label: "Update compliance checklist", due: "Mar 25", priority: "medium", done: false },
99
+ { id: 5, label: "Schedule supervisor training", due: "Mar 28", priority: "low", done: true },
100
+ ]
101
+
102
+ /* ── Insights ─────────────────────────────────────────────────────────────── */
103
+ const INSIGHTS: {
104
+ id: number
105
+ icon: string
106
+ tone: TintedIconDiscTone
107
+ title: string
108
+ body: string
109
+ }[] = [
110
+ {
111
+ id: 1,
112
+ icon: "fa-arrow-trend-up",
113
+ tone: "chart-2",
114
+ title: "Placement rate up 12%",
115
+ body: "Nursing placements increased compared to last quarter. Site capacity utilisation at 94%.",
116
+ },
117
+ {
118
+ id: 2,
119
+ icon: "fa-triangle-exclamation",
120
+ tone: "chart-4",
121
+ title: "Review backlog growing",
122
+ body: "8 evaluations have been pending for more than 48 hrs. Clear them to unblock new requests.",
123
+ },
124
+ {
125
+ id: 3,
126
+ icon: "fa-certificate",
127
+ tone: "brand",
128
+ title: "Compliance milestone",
129
+ body: "Nursing program reached 98% compliance — highest in 12 months.",
130
+ },
131
+ ]
132
+
133
+ function InsightsWidget({ plain = false }: { plain?: boolean }) {
134
+ /* Glow applied — AI surface (rule 1). See GLOW GUIDELINE in key-metrics.tsx */
135
+ const glowStyle = {
136
+ background:
137
+ "radial-gradient(ellipse 120% 80% at 50% 100%, oklch(from var(--brand-color) l c h / 0.14) 0%, transparent 65%)",
138
+ } as const
139
+
140
+ const items = INSIGHTS.map((ins, idx) => (
141
+ <React.Fragment key={ins.id}>
142
+ <div className="flex gap-3 items-start py-2">
143
+ <TintedIconDisc
144
+ className="mt-0.5"
145
+ icon={ins.icon}
146
+ tone={ins.tone}
147
+ size="md"
148
+ />
149
+ <div className="flex-1 min-w-0">
150
+ <p className="text-xs font-semibold text-foreground leading-snug">{ins.title}</p>
151
+ <p className="text-xs text-muted-foreground mt-0.5 leading-snug">{ins.body}</p>
152
+ </div>
153
+ </div>
154
+ {plain && idx < INSIGHTS.length - 1 ? (
155
+ <Separator className="opacity-50" aria-hidden="true" />
156
+ ) : null}
157
+ </React.Fragment>
158
+ ))
159
+
160
+ if (plain) {
161
+ return (
162
+ <section aria-labelledby="dashboard-insights-heading" className="flex flex-col gap-3">
163
+ <div className="flex flex-col gap-0.5">
164
+ <DashboardSectionTitle as="h1" id="dashboard-insights-heading">
165
+ Insights
166
+ </DashboardSectionTitle>
167
+ <p className={cn(dashboardSectionDescriptionClassName, "mt-0.5")}>
168
+ AI-generated · Updated now
169
+ </p>
170
+ </div>
171
+ <div className="flex flex-col">{items}</div>
172
+ </section>
173
+ )
174
+ }
175
+
176
+ return (
177
+ <Card size="sm" className="overflow-hidden" style={glowStyle}>
178
+ <CardHeader>
179
+ <DashboardSectionTitle as="h2">Insights</DashboardSectionTitle>
180
+ <CardDescription>AI-generated · Updated now</CardDescription>
181
+ </CardHeader>
182
+ <CardContent className="flex flex-col gap-3">
183
+ {INSIGHTS.map((ins) => (
184
+ <div key={ins.id} className="flex gap-3 items-start">
185
+ <TintedIconDisc
186
+ className="mt-0.5"
187
+ icon={ins.icon}
188
+ tone={ins.tone}
189
+ size="md"
190
+ />
191
+ <div className="flex-1 min-w-0">
192
+ <p className="text-xs font-semibold text-foreground leading-snug">{ins.title}</p>
193
+ <p className="text-xs text-muted-foreground mt-0.5 leading-snug">{ins.body}</p>
194
+ </div>
195
+ </div>
196
+ ))}
197
+ </CardContent>
198
+ </Card>
199
+ )
200
+ }
201
+
202
+ /* ── Recent Activity ──────────────────────────────────────────────────────── */
203
+ const ACTIVITY: {
204
+ id: number
205
+ icon: string
206
+ tone: TintedIconDiscTone
207
+ actor: string
208
+ action: string
209
+ subject: string
210
+ time: string
211
+ }[] = [
212
+ { id: 1, icon: "fa-user-check", tone: "chart-2", actor: "Dr. Patel", action: "approved", subject: "City Med placement", time: "2m ago" },
213
+ { id: 2, icon: "fa-file-signature", tone: "brand", actor: "Sarah Kim", action: "submitted", subject: "OT evaluation form", time: "18m ago" },
214
+ { id: 3, icon: "fa-circle-xmark", tone: "destructive", actor: "North Clinic", action: "rejected", subject: "2 pending requests", time: "1h ago" },
215
+ { id: 4, icon: "fa-envelope-open", tone: "chart-4", actor: "System", action: "sent reminder", subject: "to 5 supervisors", time: "3h ago" },
216
+ { id: 5, icon: "fa-arrow-up-right", tone: "brand", actor: "You", action: "exported", subject: "Q1 compliance report", time: "5h ago" },
217
+ { id: 6, icon: "fa-user-plus", tone: "chart-2", actor: "Admissions", action: "added", subject: "14 new nursing students", time: "Yesterday"},
218
+ ]
219
+
220
+ function RecentActivityWidget({ plain = false }: { plain?: boolean }) {
221
+ const rows = ACTIVITY.map((ev, idx) => (
222
+ <React.Fragment key={ev.id}>
223
+ <div className="flex items-start gap-3 py-2">
224
+ <TintedIconDisc
225
+ className="mt-0.5"
226
+ icon={ev.icon}
227
+ tone={ev.tone}
228
+ size="sm"
229
+ />
230
+ <div className="flex-1 min-w-0">
231
+ <p className="text-xs leading-snug text-foreground">
232
+ <span className="font-medium">{ev.actor}</span>{" "}
233
+ <span className="text-muted-foreground">{ev.action}</span>{" "}
234
+ <span className="font-medium">{ev.subject}</span>
235
+ </p>
236
+ <p className="text-xs text-muted-foreground mt-0.5">{ev.time}</p>
237
+ </div>
238
+ </div>
239
+ {idx < ACTIVITY.length - 1 && (
240
+ <Separator className="opacity-50" aria-hidden="true" />
241
+ )}
242
+ </React.Fragment>
243
+ ))
244
+
245
+ if (plain) {
246
+ return (
247
+ <section aria-labelledby="dashboard-activity-heading" className="flex flex-col gap-3">
248
+ <DashboardSectionTitle as="h1" id="dashboard-activity-heading">
249
+ Recent Activity
250
+ </DashboardSectionTitle>
251
+ <div className="flex flex-col overflow-auto">{rows}</div>
252
+ </section>
253
+ )
254
+ }
255
+
256
+ return (
257
+ <Card size="sm">
258
+ <CardHeader>
259
+ <DashboardSectionTitle as="h2">Recent Activity</DashboardSectionTitle>
260
+ </CardHeader>
261
+ <CardContent className="flex flex-col overflow-auto">{rows}</CardContent>
262
+ </Card>
263
+ )
264
+ }
265
+
266
+ /* ── Learn / Guide ────────────────────────────────────────────────────────── */
267
+ const STEPS = [
268
+ { id: 1, label: "Create your organisation profile", done: true },
269
+ { id: 2, label: "Add your first placement site", done: true },
270
+ { id: 3, label: "Import student roster", done: true },
271
+ { id: 4, label: "Configure compliance requirements", done: false },
272
+ { id: 5, label: "Invite supervisors", done: false },
273
+ { id: 6, label: "Launch first placement cycle", done: false },
274
+ ]
275
+
276
+ const GUIDE_ARTICLES = [
277
+ {
278
+ id: "workflow",
279
+ icon: "fa-sitemap",
280
+ title: "How does a placement actually work?",
281
+ meta: "5m read",
282
+ gradientClass: "bg-gradient-to-br from-chart-3 to-chart-1",
283
+ },
284
+ {
285
+ id: "site",
286
+ icon: "fa-circle-play",
287
+ title: "Watch: Setting up your first clinical site",
288
+ meta: "3m watch",
289
+ gradientClass: "bg-gradient-to-br from-chart-1 to-chart-5",
290
+ },
291
+ {
292
+ id: "compliance",
293
+ icon: "fa-shield-check",
294
+ title: "Make compliance less of a headache",
295
+ meta: "8m read",
296
+ gradientClass: "bg-gradient-to-br from-chart-2 to-chart-4",
297
+ },
298
+ {
299
+ id: "reports",
300
+ icon: "fa-file-chart-column",
301
+ title: "Turn your data into a report in 2 minutes",
302
+ meta: "4m read",
303
+ gradientClass: "bg-gradient-to-br from-chart-4 to-chart-5",
304
+ },
305
+ {
306
+ id: "support",
307
+ icon: "fa-headset",
308
+ title: "Stuck? Our team is one message away",
309
+ meta: "Get help",
310
+ gradientClass: "bg-gradient-to-br from-chart-3 to-muted",
311
+ },
312
+ ]
313
+
314
+ function LearnSection({ layout = "scroll", plain = false }: { layout?: "scroll" | "stack"; plain?: boolean }) {
315
+ const done = STEPS.filter((s) => s.done).length
316
+ const total = STEPS.length
317
+ const pct = Math.round((done / total) * 100)
318
+ const nextStep = STEPS.find((s) => !s.done)
319
+ const stacked = layout === "stack"
320
+ const tileClass = cn(
321
+ "flex flex-col rounded-lg overflow-hidden border border-border bg-card hover:bg-interactive-hover-soft transition-colors focus-visible:outline-2 focus-visible:outline-ring",
322
+ stacked ? "w-full" : "shrink-0 w-[13rem]",
323
+ )
324
+
325
+ const tiles = (
326
+ <>
327
+ <a
328
+ href="#"
329
+ className={tileClass}
330
+ aria-label={`Continue setup — ${done} of ${total} steps done`}
331
+ >
332
+ <div
333
+ className="h-[6.5rem] relative flex items-center justify-center overflow-hidden bg-gradient-to-br from-chart-1 to-chart-3"
334
+ >
335
+ <i className="fa-light fa-rocket-launch text-[3rem] text-background/25" aria-hidden="true" />
336
+ <div className="absolute inset-x-0 bottom-0 px-3 pb-2.5">
337
+ <div className="h-[3px] rounded-full overflow-hidden bg-background/20">
338
+ <div
339
+ className="h-full rounded-full bg-background/80"
340
+ style={{ width: `${pct}%`, transition: "width 0.5s ease" }}
341
+ />
342
+ </div>
343
+ </div>
344
+ </div>
345
+ <div className="flex flex-1 flex-col gap-1 p-3">
346
+ <p className="text-sm font-medium leading-snug text-foreground">
347
+ Pick up where you left off
348
+ </p>
349
+ {nextStep && (
350
+ <p className="text-xs text-muted-foreground leading-snug">Next: {nextStep.label}</p>
351
+ )}
352
+ <div className="mt-auto pt-1.5">
353
+ <span className="inline-block text-xs font-medium px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
354
+ {done}/{total} steps
355
+ </span>
356
+ </div>
357
+ </div>
358
+ </a>
359
+
360
+ {GUIDE_ARTICLES.map((g) => (
361
+ <a
362
+ key={g.id}
363
+ href="#"
364
+ className={tileClass}
365
+ aria-label={g.title}
366
+ >
367
+ <div
368
+ className={cn("h-[6.5rem] flex items-center justify-center overflow-hidden", g.gradientClass)}
369
+ >
370
+ <i className={`fa-light ${g.icon} text-[3rem] text-background/25`} aria-hidden="true" />
371
+ </div>
372
+ <div className="flex flex-1 flex-col gap-1 p-3">
373
+ <h3 className="text-sm font-medium leading-snug text-foreground">{g.title}</h3>
374
+ <div className="flex items-center gap-1.5 mt-auto pt-1.5 text-muted-foreground">
375
+ <i className="fa-light fa-book-open text-xs" aria-hidden="true" />
376
+ <span className="text-xs">{g.meta}</span>
377
+ </div>
378
+ </div>
379
+ </a>
380
+ ))}
381
+ </>
382
+ )
383
+
384
+ const rail = (
385
+ <div
386
+ className={cn("flex gap-3", stacked ? "flex-col" : "overflow-x-auto")}
387
+ style={stacked ? undefined : { scrollbarWidth: "none" }}
388
+ >
389
+ {tiles}
390
+ </div>
391
+ )
392
+
393
+ if (plain) {
394
+ return (
395
+ <section aria-labelledby="dashboard-learn-heading" className="flex flex-col gap-3">
396
+ <div className="flex flex-col gap-0.5">
397
+ <DashboardSectionTitle as="h1" id="dashboard-learn-heading">
398
+ Learn
399
+ </DashboardSectionTitle>
400
+ <p className={cn(dashboardSectionDescriptionClassName, "mt-0.5")}>
401
+ Guides, videos &amp; resources
402
+ </p>
403
+ </div>
404
+ {rail}
405
+ </section>
406
+ )
407
+ }
408
+
409
+ return (
410
+ <Card size="sm" className="overflow-hidden">
411
+ <CardHeader>
412
+ <DashboardSectionTitle as="h2">Learn</DashboardSectionTitle>
413
+ <CardDescription>Guides, videos &amp; resources</CardDescription>
414
+ </CardHeader>
415
+ <CardContent>{rail}</CardContent>
416
+ </Card>
417
+ )
418
+ }
419
+
420
+ /* ════════════════════════════════════════════════════════════════════════════
421
+ TAB VIEWS
422
+ ════════════════════════════════════════════════════════════════════════════ */
423
+
424
+ function ReportView({ metrics, insight, chartVariant }: DashboardTabsProps & { chartVariant: ChartCardVariant }) {
425
+ return (
426
+ <DashboardReportCharts
427
+ metrics={metrics}
428
+ insight={insight}
429
+ chartVariant={chartVariant}
430
+ />
431
+ )
432
+ }
433
+
434
+ function SimpleView() {
435
+ return (
436
+ <div className="mx-auto flex w-full max-w-4xl flex-col gap-10 px-4 py-2 pb-8 sm:gap-11 sm:px-6 sm:pb-9 lg:gap-12 lg:px-8 lg:pb-10">
437
+ <DashboardPromoBanner />
438
+ <GreetingWidget compact />
439
+ <GettingStarted inset={false} titleAs="h1" />
440
+ <TaskListPanel
441
+ title="Tasks"
442
+ headingId="dashboard-tasks-heading"
443
+ headingLevel="h1"
444
+ plain
445
+ defaultTasks={TASK_ITEMS}
446
+ />
447
+ <InsightsWidget plain />
448
+ <RecentActivityWidget plain />
449
+ <LearnSection layout="scroll" plain />
450
+ </div>
451
+ )
452
+ }
453
+
454
+ function MixView({ metrics, insight }: DashboardTabsProps) {
455
+ return (
456
+ <div className="flex flex-col gap-4 pb-6">
457
+ <div className="px-4 lg:px-6">
458
+ <DashboardPromoBanner />
459
+ </div>
460
+ {/* Key metrics flat band */}
461
+ <KeyMetrics variant="flat" metrics={metrics} insight={insight} />
462
+
463
+ <div className="px-4 lg:px-6 flex flex-col gap-4">
464
+ {/* Row: Greeting + Onboarding */}
465
+ <div className="grid grid-cols-1 gap-4 lg:grid-cols-3 items-stretch">
466
+ <div className="lg:col-span-2">
467
+ <GreetingWidget />
468
+ </div>
469
+ <GettingStartedProgressCard steps={STEPS} title="Guided onboarding" />
470
+ </div>
471
+
472
+ {/* Row: Tasks + Recent Activity */}
473
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2 items-stretch">
474
+ <TaskListPanel title="Tasks" defaultTasks={TASK_ITEMS} />
475
+ <RecentActivityWidget />
476
+ </div>
477
+
478
+ {/* Row: Insights */}
479
+ <InsightsWidget />
480
+
481
+ <GettingStarted />
482
+
483
+ {/* Learn panel — full width */}
484
+ <LearnSection />
485
+ </div>
486
+ </div>
487
+ )
488
+ }
489
+
490
+ /* ════════════════════════════════════════════════════════════════════════════
491
+ COACH MARK FLOW — dashboard onboarding tour
492
+ ════════════════════════════════════════════════════════════════════════════ */
493
+
494
+ const DASHBOARD_TOUR_STEPS = [
495
+ {
496
+ id: "tour-welcome",
497
+ target: "h1",
498
+ side: "bottom" as const,
499
+ align: "start" as const,
500
+ title: "Welcome to your Dashboard",
501
+ description:
502
+ "This is your command centre. See key metrics, charts, tasks and AI insights — all in one place.",
503
+ image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=640&h=320&fit=crop&q=80",
504
+ imageAlt: "Dashboard analytics overview",
505
+ },
506
+ {
507
+ id: "tour-metrics",
508
+ target: "[aria-label='Key Metrics']",
509
+ side: "bottom" as const,
510
+ align: "start" as const,
511
+ title: "Key Metrics at a Glance",
512
+ description:
513
+ "Track pending requests, confirmed placements, compliance rate and more. Trends show how numbers changed since last period.",
514
+ },
515
+ {
516
+ id: "tour-insights",
517
+ target: "[aria-label='Insight']",
518
+ side: "left" as const,
519
+ align: "start" as const,
520
+ title: "AI-Powered Insights",
521
+ description:
522
+ "Leo analyses your data and surfaces actionable insights automatically — no need to dig through reports.",
523
+ },
524
+ {
525
+ id: "tour-askleo",
526
+ target: "[aria-label='Ask Leo']",
527
+ side: "bottom" as const,
528
+ align: "end" as const,
529
+ title: "Ask Leo Anything",
530
+ description:
531
+ "Click here or press ⌘⌥K to open Leo, your AI assistant. Ask questions about your data, get recommendations, or automate tasks.",
532
+ },
533
+ ]
534
+
535
+ /* ════════════════════════════════════════════════════════════════════════════
536
+ Main export
537
+ ════════════════════════════════════════════════════════════════════════════ */
538
+
539
+ export function DashboardTabs({
540
+ metrics,
541
+ insight: insightProp,
542
+ title = "Dashboard",
543
+ subtitle,
544
+ }: DashboardTabsProps) {
545
+ const { chartVariant } = useChartVariant()
546
+ const { activeView } = useDashboardView()
547
+ const { openWithPrompt } = useAskLeo()
548
+
549
+ const insight = React.useMemo<MetricInsight>(() => {
550
+ if (insightProp.onAction) return insightProp
551
+ return {
552
+ ...insightProp,
553
+ onAction: () =>
554
+ openWithPrompt(
555
+ insightProp.description ?? insightProp.statement ?? insightProp.title,
556
+ ),
557
+ }
558
+ }, [insightProp, openWithPrompt])
559
+
560
+ const tour = useCoachMark({
561
+ flowId: "dashboard-tour",
562
+ steps: DASHBOARD_TOUR_STEPS,
563
+ delay: 800,
564
+ })
565
+
566
+ const viewLabel =
567
+ activeView === "report" ? "Report" : activeView === "simple" ? "Simple" : "Mix"
568
+
569
+ useAskLeoPageContext(
570
+ React.useMemo(
571
+ () => ({
572
+ title: "Dashboard",
573
+ description: `${viewLabel} layout · ${metrics.length} KPI tiles on the strip.`,
574
+ suggestions: [
575
+ "What changed in my key metrics this week?",
576
+ "Summarize the insight card for my stand-up",
577
+ "Which metric should I watch for placement risk?",
578
+ ],
579
+ }),
580
+ [viewLabel, metrics.length],
581
+ ),
582
+ )
583
+
584
+ return (
585
+ <div className="flex flex-col">
586
+ {/* Coach mark tour — targets elements by CSS selector, no wrapping needed */}
587
+ <CoachMark state={tour} />
588
+
589
+ {/* Simple view: greeting IS the page header — no separate PageHeader */}
590
+ {activeView !== "simple" && (
591
+ <PageHeader title={title} subtitle={subtitle} className="pt-4 md:pt-6" />
592
+ )}
593
+ {activeView === "report" && <ReportView metrics={metrics} insight={insight} chartVariant={chartVariant} />}
594
+ {activeView === "simple" && <SimpleView />}
595
+ {activeView === "mix" && <MixView metrics={metrics} insight={insight} />}
596
+ </div>
597
+ )
598
+ }