@exxatdesignux/ui 0.2.17 → 0.2.19

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 (162) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/consumer-extras/AGENTS.md +76 -0
  3. package/consumer-extras/README.md +5 -1
  4. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
  5. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
  6. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +22 -7
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
  9. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
  10. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  11. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  12. package/consumer-extras/patterns/data-views-pattern.md +42 -3
  13. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  14. package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
  15. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
  16. package/package.json +2 -1
  17. package/src/components/ui/button-group.tsx +81 -0
  18. package/src/components/ui/button.tsx +4 -4
  19. package/src/components/ui/sidebar.tsx +2 -2
  20. package/src/globals.css +7 -1807
  21. package/src/theme.css +10 -1126
  22. package/src/tokens/README.md +15 -0
  23. package/src/tokens/base.css +337 -0
  24. package/src/tokens/high-contrast.css +1195 -0
  25. package/src/tokens/layers.css +224 -0
  26. package/src/tokens/tailwind-bridge.css +118 -0
  27. package/src/tokens/themes.css +201 -0
  28. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
  29. package/template/AGENTS.md +66 -21
  30. package/template/app/(app)/dashboard/loading.tsx +3 -15
  31. package/template/app/(app)/dashboard/page.tsx +2 -14
  32. package/template/app/(app)/data-list/layout.tsx +43 -0
  33. package/template/app/(app)/data-list/page.tsx +2 -2
  34. package/template/app/(app)/error.tsx +22 -6
  35. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  36. package/template/app/(app)/examples/page.tsx +1 -0
  37. package/template/app/(app)/layout.tsx +13 -6
  38. package/template/app/(app)/loading.tsx +1 -18
  39. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  40. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  41. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  42. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  43. package/template/app/(app)/question-bank/page.tsx +2 -1
  44. package/template/app/(app)/settings/page.tsx +4 -5
  45. package/template/app/global-error.tsx +63 -0
  46. package/template/app/globals.css +7 -1934
  47. package/template/app/layout.tsx +2 -0
  48. package/template/components/app-route-loading.tsx +14 -0
  49. package/template/components/app-sidebar.tsx +71 -55
  50. package/template/components/data-table/index.tsx +31 -67
  51. package/template/components/data-table/use-table-state.ts +33 -6
  52. package/template/components/data-views/index.ts +37 -9
  53. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  54. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  55. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  56. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  57. package/template/components/dev-chunk-load-recovery.tsx +41 -0
  58. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  59. package/template/components/exxat-product-logo.tsx +2 -6
  60. package/template/components/key-metrics.tsx +54 -22
  61. package/template/components/list-hub-board-view.tsx +68 -0
  62. package/template/components/list-hub-client.tsx +186 -0
  63. package/template/components/list-hub-list-view.tsx +36 -0
  64. package/template/components/list-hub-panel-activator.tsx +8 -0
  65. package/template/components/list-hub-secondary-nav.tsx +121 -0
  66. package/template/components/list-hub-table.tsx +336 -0
  67. package/template/components/new-question-composer.tsx +6 -24
  68. package/template/components/product-switcher.tsx +5 -5
  69. package/template/components/product-wordmark.tsx +4 -7
  70. package/template/components/question-bank-client.tsx +4 -1
  71. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  72. package/template/components/question-bank-hub-client.tsx +2 -5
  73. package/template/components/question-bank-table.tsx +155 -509
  74. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  75. package/template/components/secondary-panel.tsx +4 -44
  76. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  77. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  78. package/template/components/secondary-panels/registry.tsx +15 -0
  79. package/template/components/settings-appearance-card.tsx +3 -2
  80. package/template/components/settings-client.tsx +59 -15
  81. package/template/components/settings-form-row.tsx +9 -4
  82. package/template/components/sidebar-shell.tsx +2 -1
  83. package/template/components/table-properties/drawer-button.tsx +51 -20
  84. package/template/components/table-properties/drawer.tsx +81 -17
  85. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  86. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  87. package/template/components/templates/list-page.tsx +40 -13
  88. package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
  89. package/template/components/templates/page-loading-shell.tsx +262 -0
  90. package/template/components/ui/button-group.tsx +1 -0
  91. package/template/contexts/product-context.tsx +21 -2
  92. package/template/docs/consumer-app-pattern.md +39 -0
  93. package/template/docs/data-views-pattern.md +42 -3
  94. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  95. package/template/docs/focused-workflow-page-pattern.md +84 -0
  96. package/template/docs/kpi-flat-band-pattern.md +57 -0
  97. package/template/docs/kpi-strip-max-four-pattern.md +1 -0
  98. package/template/docs/shell-surface-elevation-pattern.md +54 -0
  99. package/template/lib/chunk-load-error.ts +13 -0
  100. package/template/lib/command-menu-search-data.ts +11 -27
  101. package/template/lib/conditional-rule-match.ts +87 -22
  102. package/template/lib/data-list-display-options.ts +16 -2
  103. package/template/lib/data-list-view-registry.ts +104 -0
  104. package/template/lib/data-list-view-surface.ts +15 -1
  105. package/template/lib/data-list-view.ts +16 -1
  106. package/template/lib/data-view-dashboard-storage.ts +38 -35
  107. package/template/lib/hub-connected-view-renderers.ts +58 -0
  108. package/template/lib/list-hub-nav.ts +121 -0
  109. package/template/lib/list-hub-supported-views.ts +10 -0
  110. package/template/lib/list-page-table-properties.ts +3 -7
  111. package/template/lib/list-status-badges.ts +4 -97
  112. package/template/lib/mock/list-hub-directory.ts +27 -0
  113. package/template/lib/mock/list-hub-kpi.ts +27 -0
  114. package/template/lib/mock/navigation.tsx +1 -0
  115. package/template/lib/page-loading-variant.ts +40 -0
  116. package/template/lib/question-bank-supported-views.ts +13 -0
  117. package/template/lib/sidebar-state-cookie.ts +9 -0
  118. package/template/lib/table-state-lifecycle.ts +60 -13
  119. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  120. package/template/app/(app)/data-list/new/page.tsx +0 -34
  121. package/template/components/compliance-board-view.tsx +0 -142
  122. package/template/components/compliance-client.tsx +0 -92
  123. package/template/components/compliance-list-view.tsx +0 -54
  124. package/template/components/compliance-page-header.tsx +0 -89
  125. package/template/components/compliance-table.tsx +0 -632
  126. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  127. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  128. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  129. package/template/components/new-placement-back-btn.tsx +0 -28
  130. package/template/components/new-placement-form.tsx +0 -1068
  131. package/template/components/placement-board-card.tsx +0 -262
  132. package/template/components/placement-detail.tsx +0 -438
  133. package/template/components/placements-board-view.tsx +0 -404
  134. package/template/components/placements-client.tsx +0 -252
  135. package/template/components/placements-list-view.tsx +0 -171
  136. package/template/components/placements-page-header.tsx +0 -166
  137. package/template/components/placements-table-cells.test.tsx +0 -22
  138. package/template/components/placements-table-cells.tsx +0 -173
  139. package/template/components/placements-table-columns.tsx +0 -640
  140. package/template/components/placements-table.tsx +0 -1675
  141. package/template/components/rotations-empty-state.tsx +0 -50
  142. package/template/components/rotations-panel-activator.tsx +0 -8
  143. package/template/components/sites-all-client.tsx +0 -154
  144. package/template/components/sites-board-view.tsx +0 -67
  145. package/template/components/sites-list-view.tsx +0 -42
  146. package/template/components/sites-table.tsx +0 -402
  147. package/template/components/team-board-view.tsx +0 -122
  148. package/template/components/team-client.tsx +0 -100
  149. package/template/components/team-list-view.tsx +0 -59
  150. package/template/components/team-page-header.tsx +0 -92
  151. package/template/components/team-table.tsx +0 -714
  152. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  153. package/template/lib/mock/compliance-kpi.ts +0 -61
  154. package/template/lib/mock/compliance.ts +0 -146
  155. package/template/lib/mock/placements-kpi.ts +0 -134
  156. package/template/lib/mock/placements.ts +0 -183
  157. package/template/lib/mock/sites-directory.ts +0 -16
  158. package/template/lib/mock/sites-kpi.ts +0 -25
  159. package/template/lib/mock/team-kpi.ts +0 -60
  160. package/template/lib/mock/team.ts +0 -118
  161. package/template/lib/placement-board-card-layout.ts +0 -79
  162. package/template/lib/placement-lifecycle.ts +0 -5
@@ -1,640 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Placements lifecycle columns, empty states, and Properties drawer labels.
5
- * Owned by the placements feature (`PlacementsClient` / `/data-list`); consumed by `PlacementsTable`.
6
- */
7
-
8
- import { Badge } from "@/components/ui/badge"
9
- import type { FilterFieldDef, FilterOperator } from "@/components/table-properties/types"
10
- import type { ColumnDef } from "@/components/data-table/types"
11
- import {
12
- AvatarCircle,
13
- HireBadge,
14
- PLACEMENT_ROW_ACTIONS,
15
- ReadinessBadge,
16
- RowActions,
17
- StatusBadge,
18
- WeeksProgressCell,
19
- } from "@/components/placements-table-cells"
20
- import { uniquePlacementFieldOptions, type Placement } from "@/lib/mock/placements"
21
- import { formatDateUS } from "@/lib/date-filter"
22
- import type { PlacementLifecycleTabId } from "@/lib/placement-lifecycle"
23
-
24
- const COLUMN_SELECT: ColumnDef<Placement> = {
25
- key: "select",
26
- label: "",
27
- width: 40,
28
- minWidth: 40,
29
- defaultPin: "left",
30
- lockPin: true,
31
- }
32
-
33
- const COLUMN_ACTIONS: ColumnDef<Placement> = {
34
- key: "actions",
35
- label: "",
36
- width: 48,
37
- minWidth: 48,
38
- defaultPin: "right",
39
- lockPin: true,
40
- cell: (row) => (
41
- <div className="flex items-center justify-center">
42
- <RowActions row={row} actions={PLACEMENT_ROW_ACTIONS} />
43
- </div>
44
- ),
45
- }
46
-
47
- const CELL_STUDENT: ColumnDef<Placement>["cell"] = (row) => (
48
- <div className="flex items-center gap-2.5 min-w-0">
49
- <AvatarCircle initials={row.initials} />
50
- <div className="flex flex-col min-w-0">
51
- <span className="font-medium text-foreground text-sm leading-tight truncate">
52
- {row.student}
53
- </span>
54
- <span className="text-xs text-muted-foreground leading-tight mt-0.5 truncate">
55
- {row.email}
56
- </span>
57
- </div>
58
- </div>
59
- )
60
-
61
- const CELL_SITE_LOCATION: ColumnDef<Placement>["cell"] = (row) => (
62
- <div className="min-w-0" title={`${row.site} · ${row.siteAddress}`}>
63
- <span className="block truncate text-sm font-medium text-foreground leading-tight">{row.site}</span>
64
- <span className="block truncate text-xs text-muted-foreground mt-0.5 leading-tight">{row.siteAddress}</span>
65
- </div>
66
- )
67
-
68
- function placementColumnToFilterFieldDef(c: ColumnDef<Placement>): FilterFieldDef | null {
69
- if (!c.filter) return null
70
- const f = c.filter
71
- const defaultOps =
72
- f.type === "select" || f.type === "date"
73
- ? (["is", "is_not"] as FilterOperator[])
74
- : (["contains", "not_contains"] as FilterOperator[])
75
- return {
76
- key: c.key,
77
- label: c.label,
78
- icon: f.icon ?? "fa-filter",
79
- type: f.type,
80
- operators: (f.operators ?? defaultOps) as FilterOperator[],
81
- options:
82
- f.type === "date"
83
- ? uniquePlacementFieldOptions(c.key as keyof Placement)
84
- : f.options,
85
- ...(f.textMask ? { textMask: f.textMask } : {}),
86
- }
87
- }
88
-
89
- export function columnsToFilterFields(cols: ColumnDef<Placement>[]): FilterFieldDef[] {
90
- return cols.map(placementColumnToFilterFieldDef).filter((x): x is FilterFieldDef => x !== null)
91
- }
92
-
93
- /** All columns — original placements overview */
94
- const PLACEMENT_COLUMNS_ALL: ColumnDef<Placement>[] = [
95
- COLUMN_SELECT,
96
- {
97
- key: "student",
98
- label: "Student",
99
- width: 210,
100
- minWidth: 180,
101
- sortable: true,
102
- sortKey: "student",
103
- defaultPin: "left",
104
- filter: {
105
- type: "select",
106
- icon: "fa-user",
107
- operators: ["is", "is_not"],
108
- options: uniquePlacementFieldOptions("student"),
109
- },
110
- cell: CELL_STUDENT,
111
- },
112
- {
113
- key: "specialization",
114
- label: "Specialization",
115
- width: 160,
116
- minWidth: 100,
117
- sortable: true,
118
- sortKey: "specialization",
119
- filter: {
120
- type: "select",
121
- icon: "fa-stethoscope",
122
- operators: ["is", "is_not"],
123
- options: uniquePlacementFieldOptions("specialization"),
124
- },
125
- cell: (row) => (
126
- <span className="block truncate text-sm text-foreground/80">{row.specialization}</span>
127
- ),
128
- },
129
- {
130
- key: "site",
131
- label: "Site",
132
- width: 180,
133
- minWidth: 100,
134
- sortable: true,
135
- sortKey: "site",
136
- filter: {
137
- type: "select",
138
- icon: "fa-hospital",
139
- operators: ["is", "is_not"],
140
- options: uniquePlacementFieldOptions("site"),
141
- },
142
- cell: (row) => (
143
- <div className="min-w-0" title={`${row.site} · ${row.siteAddress}`}>
144
- <span className="block truncate text-sm font-medium text-foreground leading-tight">{row.site}</span>
145
- <span className="block truncate text-xs text-muted-foreground mt-0.5 leading-tight">{row.siteAddress}</span>
146
- </div>
147
- ),
148
- },
149
- {
150
- key: "status",
151
- label: "Status",
152
- width: 130,
153
- minWidth: 110,
154
- sortable: true,
155
- sortKey: "status",
156
- filter: {
157
- type: "select",
158
- icon: "fa-circle-dot",
159
- operators: ["is", "is_not"],
160
- options: [
161
- { value: "confirmed", label: "Confirmed" },
162
- { value: "pending", label: "Pending" },
163
- { value: "under-review", label: "Under Review" },
164
- { value: "rejected", label: "Rejected" },
165
- { value: "completed", label: "Completed" },
166
- ],
167
- },
168
- cell: (row) => <StatusBadge status={row.status} />,
169
- },
170
- {
171
- key: "start",
172
- label: "Start Date",
173
- width: 130,
174
- minWidth: 110,
175
- sortable: true,
176
- sortKey: "start",
177
- filter: {
178
- type: "date",
179
- icon: "fa-calendar-days",
180
- operators: ["is", "is_not"],
181
- },
182
- cell: (row) => (
183
- <span className="text-sm text-foreground/80 tabular-nums whitespace-nowrap">{row.start}</span>
184
- ),
185
- },
186
- {
187
- key: "duration",
188
- label: "Duration",
189
- width: 96,
190
- minWidth: 80,
191
- cell: (row) => (
192
- <span className="text-sm text-foreground/80 whitespace-nowrap">{row.duration}</span>
193
- ),
194
- },
195
- {
196
- key: "supervisor",
197
- label: "Supervisor",
198
- width: 152,
199
- minWidth: 100,
200
- filter: {
201
- type: "select",
202
- icon: "fa-user-tie",
203
- operators: ["is", "is_not"],
204
- options: uniquePlacementFieldOptions("supervisor"),
205
- },
206
- cell: (row) => (
207
- <span className="block truncate text-sm text-foreground/80">{row.supervisor}</span>
208
- ),
209
- },
210
- COLUMN_ACTIONS,
211
- ]
212
-
213
- /** Upcoming lifecycle */
214
- const PLACEMENT_COLUMNS_UPCOMING: ColumnDef<Placement>[] = [
215
- COLUMN_SELECT,
216
- {
217
- key: "student",
218
- label: "Student",
219
- width: 200,
220
- minWidth: 170,
221
- sortable: true,
222
- sortKey: "student",
223
- defaultPin: "left",
224
- filter: {
225
- type: "select",
226
- icon: "fa-user",
227
- operators: ["is", "is_not"],
228
- options: uniquePlacementFieldOptions("student"),
229
- },
230
- cell: CELL_STUDENT,
231
- },
232
- {
233
- key: "site",
234
- label: "Site & Location",
235
- width: 200,
236
- minWidth: 140,
237
- sortable: true,
238
- sortKey: "site",
239
- filter: {
240
- type: "select",
241
- icon: "fa-hospital",
242
- operators: ["is", "is_not"],
243
- options: uniquePlacementFieldOptions("site"),
244
- },
245
- cell: CELL_SITE_LOCATION,
246
- },
247
- {
248
- key: "internship",
249
- label: "Internship",
250
- width: 180,
251
- minWidth: 120,
252
- sortable: true,
253
- sortKey: "internship",
254
- filter: {
255
- type: "select",
256
- icon: "fa-briefcase",
257
- operators: ["is", "is_not"],
258
- options: uniquePlacementFieldOptions("internship"),
259
- },
260
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.internship}</span>,
261
- },
262
- {
263
- key: "supervisor",
264
- label: "Preceptor",
265
- width: 140,
266
- minWidth: 100,
267
- sortable: true,
268
- sortKey: "supervisor",
269
- filter: {
270
- type: "select",
271
- icon: "fa-user-tie",
272
- operators: ["is", "is_not"],
273
- options: uniquePlacementFieldOptions("supervisor"),
274
- },
275
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.supervisor}</span>,
276
- },
277
- {
278
- key: "specialization",
279
- label: "Specialization",
280
- width: 140,
281
- minWidth: 100,
282
- sortable: true,
283
- sortKey: "specialization",
284
- filter: {
285
- type: "select",
286
- icon: "fa-stethoscope",
287
- operators: ["is", "is_not"],
288
- options: uniquePlacementFieldOptions("specialization"),
289
- },
290
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.specialization}</span>,
291
- },
292
- {
293
- key: "start",
294
- label: "Start Date",
295
- width: 110,
296
- minWidth: 100,
297
- sortable: true,
298
- sortKey: "start",
299
- filter: { type: "date", icon: "fa-calendar-days", operators: ["is", "is_not"] },
300
- cell: (row) => (
301
- <span className="text-sm text-foreground/80 tabular-nums whitespace-nowrap">{row.start}</span>
302
- ),
303
- },
304
- {
305
- key: "compliance",
306
- label: "Compliance",
307
- width: 120,
308
- minWidth: 100,
309
- sortable: true,
310
- sortKey: "compliance",
311
- filter: {
312
- type: "select",
313
- icon: "fa-shield-check",
314
- operators: ["is", "is_not"],
315
- options: uniquePlacementFieldOptions("compliance"),
316
- },
317
- cell: (row) => <span className="text-sm text-foreground/80">{row.compliance}</span>,
318
- },
319
- {
320
- key: "daysUntilStart",
321
- label: "Days Until Start",
322
- width: 120,
323
- minWidth: 100,
324
- sortable: true,
325
- sortKey: "daysUntilStart",
326
- cell: (row) => (
327
- <span className="text-sm tabular-nums text-foreground/80">
328
- {row.daysUntilStart > 0 ? `${row.daysUntilStart} days` : "—"}
329
- </span>
330
- ),
331
- },
332
- {
333
- key: "readiness",
334
- label: "Readiness",
335
- width: 120,
336
- minWidth: 100,
337
- sortable: true,
338
- sortKey: "readiness",
339
- filter: {
340
- type: "select",
341
- icon: "fa-flag",
342
- operators: ["is", "is_not"],
343
- options: uniquePlacementFieldOptions("readiness"),
344
- },
345
- cell: (row) => <ReadinessBadge value={row.readiness} />,
346
- },
347
- COLUMN_ACTIONS,
348
- ]
349
-
350
- /** Ongoing lifecycle */
351
- const PLACEMENT_COLUMNS_ONGOING: ColumnDef<Placement>[] = [
352
- COLUMN_SELECT,
353
- {
354
- key: "student",
355
- label: "Student",
356
- width: 200,
357
- minWidth: 170,
358
- sortable: true,
359
- sortKey: "student",
360
- defaultPin: "left",
361
- filter: {
362
- type: "select",
363
- icon: "fa-user",
364
- operators: ["is", "is_not"],
365
- options: uniquePlacementFieldOptions("student"),
366
- },
367
- cell: CELL_STUDENT,
368
- },
369
- {
370
- key: "site",
371
- label: "Site & Location",
372
- width: 200,
373
- minWidth: 140,
374
- sortable: true,
375
- sortKey: "site",
376
- filter: {
377
- type: "select",
378
- icon: "fa-hospital",
379
- operators: ["is", "is_not"],
380
- options: uniquePlacementFieldOptions("site"),
381
- },
382
- cell: CELL_SITE_LOCATION,
383
- },
384
- {
385
- key: "internship",
386
- label: "Internship",
387
- width: 180,
388
- minWidth: 120,
389
- sortable: true,
390
- sortKey: "internship",
391
- filter: {
392
- type: "select",
393
- icon: "fa-briefcase",
394
- operators: ["is", "is_not"],
395
- options: uniquePlacementFieldOptions("internship"),
396
- },
397
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.internship}</span>,
398
- },
399
- {
400
- key: "supervisor",
401
- label: "Preceptor",
402
- width: 140,
403
- minWidth: 100,
404
- sortable: true,
405
- sortKey: "supervisor",
406
- filter: {
407
- type: "select",
408
- icon: "fa-user-tie",
409
- operators: ["is", "is_not"],
410
- options: uniquePlacementFieldOptions("supervisor"),
411
- },
412
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.supervisor}</span>,
413
- },
414
- {
415
- key: "specialization",
416
- label: "Specialization",
417
- width: 140,
418
- minWidth: 100,
419
- sortable: true,
420
- sortKey: "specialization",
421
- filter: {
422
- type: "select",
423
- icon: "fa-stethoscope",
424
- operators: ["is", "is_not"],
425
- options: uniquePlacementFieldOptions("specialization"),
426
- },
427
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.specialization}</span>,
428
- },
429
- {
430
- key: "progressWeeksDone",
431
- label: "Progress",
432
- width: 160,
433
- minWidth: 140,
434
- sortable: true,
435
- sortKey: "progressWeeksDone",
436
- cell: (row) => <WeeksProgressCell row={row} />,
437
- },
438
- {
439
- key: "endDate",
440
- label: "End Date",
441
- width: 110,
442
- minWidth: 100,
443
- sortable: true,
444
- sortKey: "endDate",
445
- filter: { type: "date", icon: "fa-calendar-days", operators: ["is", "is_not"] },
446
- cell: (row) => (
447
- <span className="text-sm text-foreground/80 tabular-nums whitespace-nowrap">{formatDateUS(row.endDate)}</span>
448
- ),
449
- },
450
- {
451
- key: "lastCheckin",
452
- label: "Last Check-In",
453
- width: 120,
454
- minWidth: 100,
455
- sortable: true,
456
- sortKey: "lastCheckin",
457
- cell: (row) => (
458
- <span className="text-sm text-foreground/80 whitespace-nowrap">{formatDateUS(row.lastCheckin)}</span>
459
- ),
460
- },
461
- COLUMN_ACTIONS,
462
- ]
463
-
464
- /** Completed lifecycle */
465
- const PLACEMENT_COLUMNS_COMPLETED: ColumnDef<Placement>[] = [
466
- COLUMN_SELECT,
467
- {
468
- key: "student",
469
- label: "Student",
470
- width: 200,
471
- minWidth: 170,
472
- sortable: true,
473
- sortKey: "student",
474
- defaultPin: "left",
475
- filter: {
476
- type: "select",
477
- icon: "fa-user",
478
- operators: ["is", "is_not"],
479
- options: uniquePlacementFieldOptions("student"),
480
- },
481
- cell: CELL_STUDENT,
482
- },
483
- {
484
- key: "site",
485
- label: "Site & Location",
486
- width: 200,
487
- minWidth: 140,
488
- sortable: true,
489
- sortKey: "site",
490
- filter: {
491
- type: "select",
492
- icon: "fa-hospital",
493
- operators: ["is", "is_not"],
494
- options: uniquePlacementFieldOptions("site"),
495
- },
496
- cell: CELL_SITE_LOCATION,
497
- },
498
- {
499
- key: "internship",
500
- label: "Internship",
501
- width: 180,
502
- minWidth: 120,
503
- sortable: true,
504
- sortKey: "internship",
505
- filter: {
506
- type: "select",
507
- icon: "fa-briefcase",
508
- operators: ["is", "is_not"],
509
- options: uniquePlacementFieldOptions("internship"),
510
- },
511
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.internship}</span>,
512
- },
513
- {
514
- key: "supervisor",
515
- label: "Preceptor",
516
- width: 140,
517
- minWidth: 100,
518
- sortable: true,
519
- sortKey: "supervisor",
520
- filter: {
521
- type: "select",
522
- icon: "fa-user-tie",
523
- operators: ["is", "is_not"],
524
- options: uniquePlacementFieldOptions("supervisor"),
525
- },
526
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.supervisor}</span>,
527
- },
528
- {
529
- key: "specialization",
530
- label: "Specialization",
531
- width: 140,
532
- minWidth: 100,
533
- sortable: true,
534
- sortKey: "specialization",
535
- filter: {
536
- type: "select",
537
- icon: "fa-stethoscope",
538
- operators: ["is", "is_not"],
539
- options: uniquePlacementFieldOptions("specialization"),
540
- },
541
- cell: (row) => <span className="block truncate text-sm text-foreground/80">{row.specialization}</span>,
542
- },
543
- {
544
- key: "completionDate",
545
- label: "Completion Date",
546
- width: 120,
547
- minWidth: 100,
548
- sortable: true,
549
- sortKey: "completionDate",
550
- filter: { type: "date", icon: "fa-calendar-days", operators: ["is", "is_not"] },
551
- cell: (row) => (
552
- <span className="text-sm text-foreground/80 tabular-nums whitespace-nowrap">{row.completionDate}</span>
553
- ),
554
- },
555
- {
556
- key: "finalStatus",
557
- label: "Final Status",
558
- width: 120,
559
- minWidth: 100,
560
- sortable: true,
561
- sortKey: "finalStatus",
562
- filter: {
563
- type: "select",
564
- icon: "fa-circle-check",
565
- operators: ["is", "is_not"],
566
- options: uniquePlacementFieldOptions("finalStatus"),
567
- },
568
- cell: (row) => (
569
- <Badge variant="outline" className="h-6 px-2 py-1 text-xs font-medium leading-none">
570
- {row.finalStatus}
571
- </Badge>
572
- ),
573
- },
574
- {
575
- key: "rating",
576
- label: "Rating",
577
- width: 100,
578
- minWidth: 88,
579
- sortable: true,
580
- sortKey: "rating",
581
- cell: (row) =>
582
- row.rating > 0 ? (
583
- <span className="inline-flex items-center gap-1 text-sm font-medium tabular-nums">
584
- {row.rating.toFixed(1)}
585
- <i className="fa-solid fa-star text-xs text-chart-4" aria-hidden="true" />
586
- </span>
587
- ) : (
588
- <span className="text-sm text-muted-foreground">—</span>
589
- ),
590
- },
591
- {
592
- key: "suggestedToHire",
593
- label: "Suggested To Hire",
594
- width: 130,
595
- minWidth: 110,
596
- sortable: true,
597
- sortKey: "suggestedToHire",
598
- filter: {
599
- type: "select",
600
- icon: "fa-user-plus",
601
- operators: ["is", "is_not"],
602
- options: uniquePlacementFieldOptions("suggestedToHire"),
603
- },
604
- cell: (row) => <HireBadge value={row.suggestedToHire} />,
605
- },
606
- COLUMN_ACTIONS,
607
- ]
608
-
609
- export function getPlacementColumnsForLifecycle(tab: PlacementLifecycleTabId): ColumnDef<Placement>[] {
610
- switch (tab) {
611
- case "upcoming":
612
- return PLACEMENT_COLUMNS_UPCOMING
613
- case "ongoing":
614
- return PLACEMENT_COLUMNS_ONGOING
615
- case "completed":
616
- return PLACEMENT_COLUMNS_COMPLETED
617
- default:
618
- return PLACEMENT_COLUMNS_ALL
619
- }
620
- }
621
-
622
- export function emptyCopyForPlacementLifecycleTab(tab: PlacementLifecycleTabId): string {
623
- switch (tab) {
624
- case "upcoming":
625
- return "No rows in this segment match your filters."
626
- case "ongoing":
627
- return "No rows in this segment match your filters."
628
- case "completed":
629
- return "No rows in this segment match your filters."
630
- default:
631
- return "No rows match your filters."
632
- }
633
- }
634
-
635
- export const placementLifecycleDrawerLabels: Record<PlacementLifecycleTabId, string> = {
636
- all: "Segment: All rows",
637
- upcoming: "Segment: Due soon",
638
- ongoing: "Segment: In progress",
639
- completed: "Segment: Done",
640
- }