@exxatdesignux/ui 0.2.18 → 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 (140) hide show
  1. package/CHANGELOG.md +15 -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 +21 -6
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +4 -2
  9. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  10. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  11. package/consumer-extras/patterns/data-views-pattern.md +40 -3
  12. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  13. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +5 -3
  14. package/package.json +2 -1
  15. package/src/components/ui/button-group.tsx +81 -0
  16. package/src/components/ui/button.tsx +4 -4
  17. package/src/globals.css +7 -1858
  18. package/src/theme.css +10 -1126
  19. package/src/tokens/README.md +15 -0
  20. package/src/tokens/base.css +337 -0
  21. package/src/tokens/high-contrast.css +1195 -0
  22. package/src/tokens/layers.css +224 -0
  23. package/src/tokens/tailwind-bridge.css +118 -0
  24. package/src/tokens/themes.css +201 -0
  25. package/template/AGENTS.md +60 -22
  26. package/template/app/(app)/dashboard/loading.tsx +3 -15
  27. package/template/app/(app)/dashboard/page.tsx +2 -14
  28. package/template/app/(app)/data-list/layout.tsx +43 -0
  29. package/template/app/(app)/data-list/page.tsx +2 -2
  30. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  31. package/template/app/(app)/examples/page.tsx +1 -0
  32. package/template/app/(app)/loading.tsx +1 -18
  33. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  34. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  35. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  36. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  37. package/template/app/(app)/question-bank/page.tsx +2 -1
  38. package/template/app/(app)/settings/page.tsx +4 -5
  39. package/template/app/globals.css +7 -1964
  40. package/template/components/app-route-loading.tsx +14 -0
  41. package/template/components/app-sidebar.tsx +70 -55
  42. package/template/components/data-views/index.ts +37 -9
  43. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  44. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  45. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  46. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  47. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  48. package/template/components/list-hub-board-view.tsx +68 -0
  49. package/template/components/list-hub-client.tsx +186 -0
  50. package/template/components/list-hub-list-view.tsx +36 -0
  51. package/template/components/list-hub-panel-activator.tsx +8 -0
  52. package/template/components/list-hub-secondary-nav.tsx +121 -0
  53. package/template/components/list-hub-table.tsx +336 -0
  54. package/template/components/new-question-composer.tsx +6 -24
  55. package/template/components/product-switcher.tsx +3 -2
  56. package/template/components/question-bank-client.tsx +4 -1
  57. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  58. package/template/components/question-bank-table.tsx +143 -485
  59. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  60. package/template/components/secondary-panel.tsx +4 -44
  61. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  62. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  63. package/template/components/secondary-panels/registry.tsx +15 -0
  64. package/template/components/settings-appearance-card.tsx +3 -2
  65. package/template/components/settings-client.tsx +59 -15
  66. package/template/components/settings-form-row.tsx +9 -4
  67. package/template/components/table-properties/drawer-button.tsx +13 -0
  68. package/template/components/table-properties/drawer.tsx +65 -4
  69. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  70. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  71. package/template/components/templates/list-page.tsx +29 -5
  72. package/template/components/templates/nested-secondary-panel-shell.tsx +2 -1
  73. package/template/components/templates/page-loading-shell.tsx +262 -0
  74. package/template/components/ui/button-group.tsx +1 -0
  75. package/template/docs/consumer-app-pattern.md +39 -0
  76. package/template/docs/data-views-pattern.md +40 -3
  77. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  78. package/template/docs/focused-workflow-page-pattern.md +84 -0
  79. package/template/docs/shell-surface-elevation-pattern.md +5 -3
  80. package/template/lib/command-menu-search-data.ts +11 -27
  81. package/template/lib/data-list-display-options.ts +16 -2
  82. package/template/lib/data-list-view-registry.ts +104 -0
  83. package/template/lib/data-list-view-surface.ts +15 -1
  84. package/template/lib/data-list-view.ts +10 -1
  85. package/template/lib/data-view-dashboard-storage.ts +38 -35
  86. package/template/lib/hub-connected-view-renderers.ts +58 -0
  87. package/template/lib/list-hub-nav.ts +121 -0
  88. package/template/lib/list-hub-supported-views.ts +10 -0
  89. package/template/lib/list-page-table-properties.ts +3 -7
  90. package/template/lib/list-status-badges.ts +4 -97
  91. package/template/lib/mock/list-hub-directory.ts +27 -0
  92. package/template/lib/mock/list-hub-kpi.ts +27 -0
  93. package/template/lib/mock/navigation.tsx +1 -0
  94. package/template/lib/page-loading-variant.ts +40 -0
  95. package/template/lib/question-bank-supported-views.ts +13 -0
  96. package/template/lib/table-state-lifecycle.ts +2 -2
  97. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  98. package/template/app/(app)/data-list/new/page.tsx +0 -34
  99. package/template/components/compliance-board-view.tsx +0 -142
  100. package/template/components/compliance-client.tsx +0 -92
  101. package/template/components/compliance-list-view.tsx +0 -54
  102. package/template/components/compliance-page-header.tsx +0 -89
  103. package/template/components/compliance-table.tsx +0 -612
  104. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  105. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  106. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  107. package/template/components/new-placement-back-btn.tsx +0 -28
  108. package/template/components/new-placement-form.tsx +0 -1068
  109. package/template/components/placement-board-card.tsx +0 -262
  110. package/template/components/placement-detail.tsx +0 -438
  111. package/template/components/placements-board-view.tsx +0 -404
  112. package/template/components/placements-client.tsx +0 -252
  113. package/template/components/placements-list-view.tsx +0 -171
  114. package/template/components/placements-page-header.tsx +0 -166
  115. package/template/components/placements-table-cells.test.tsx +0 -22
  116. package/template/components/placements-table-cells.tsx +0 -173
  117. package/template/components/placements-table-columns.tsx +0 -640
  118. package/template/components/placements-table.tsx +0 -1642
  119. package/template/components/rotations-empty-state.tsx +0 -50
  120. package/template/components/rotations-panel-activator.tsx +0 -8
  121. package/template/components/sites-all-client.tsx +0 -154
  122. package/template/components/sites-board-view.tsx +0 -67
  123. package/template/components/sites-list-view.tsx +0 -42
  124. package/template/components/sites-table.tsx +0 -382
  125. package/template/components/team-board-view.tsx +0 -122
  126. package/template/components/team-client.tsx +0 -100
  127. package/template/components/team-list-view.tsx +0 -59
  128. package/template/components/team-page-header.tsx +0 -92
  129. package/template/components/team-table.tsx +0 -693
  130. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  131. package/template/lib/mock/compliance-kpi.ts +0 -61
  132. package/template/lib/mock/compliance.ts +0 -146
  133. package/template/lib/mock/placements-kpi.ts +0 -134
  134. package/template/lib/mock/placements.ts +0 -183
  135. package/template/lib/mock/sites-directory.ts +0 -16
  136. package/template/lib/mock/sites-kpi.ts +0 -25
  137. package/template/lib/mock/team-kpi.ts +0 -60
  138. package/template/lib/mock/team.ts +0 -118
  139. package/template/lib/placement-board-card-layout.ts +0 -79
  140. 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
- }