@health-samurai/react-components 0.0.0-alpha.6 → 0.0.0-alpha.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 (122) hide show
  1. package/dist/bundle.css +88 -451
  2. package/dist/src/components/button-dropdown.d.ts +10 -0
  3. package/dist/src/components/button-dropdown.d.ts.map +1 -0
  4. package/dist/src/components/button-dropdown.js +70 -0
  5. package/dist/src/components/button-dropdown.js.map +1 -0
  6. package/dist/src/components/button-dropdown.stories.d.ts +11 -0
  7. package/dist/src/components/button-dropdown.stories.d.ts.map +1 -0
  8. package/dist/src/components/button-dropdown.stories.js +48 -0
  9. package/dist/src/components/button-dropdown.stories.js.map +1 -0
  10. package/dist/src/components/data-table.d.ts +4 -3
  11. package/dist/src/components/data-table.d.ts.map +1 -1
  12. package/dist/src/components/data-table.js +5 -4
  13. package/dist/src/components/data-table.js.map +1 -1
  14. package/dist/src/components/data-table.stories.d.ts.map +1 -1
  15. package/dist/src/components/data-table.stories.js +196 -0
  16. package/dist/src/components/data-table.stories.js.map +1 -1
  17. package/dist/src/components/fhir-structure-view.d.ts +8 -8
  18. package/dist/src/components/fhir-structure-view.d.ts.map +1 -1
  19. package/dist/src/components/fhir-structure-view.js +21 -22
  20. package/dist/src/components/fhir-structure-view.js.map +1 -1
  21. package/dist/src/components/segment-control.d.ts +3 -3
  22. package/dist/src/components/segment-control.d.ts.map +1 -1
  23. package/dist/src/components/segment-control.js +5 -5
  24. package/dist/src/components/segment-control.js.map +1 -1
  25. package/dist/src/components/segment-control.stories.d.ts +2 -3
  26. package/dist/src/components/segment-control.stories.d.ts.map +1 -1
  27. package/dist/src/components/split-button.d.ts +2 -2
  28. package/dist/src/components/split-button.d.ts.map +1 -1
  29. package/dist/src/components/split-button.js +2 -2
  30. package/dist/src/components/split-button.js.map +1 -1
  31. package/dist/src/components/tree-view.d.ts +28 -5
  32. package/dist/src/components/tree-view.d.ts.map +1 -1
  33. package/dist/src/components/tree-view.js +75 -60
  34. package/dist/src/components/tree-view.js.map +1 -1
  35. package/dist/src/components/tree-view.stories.d.ts.map +1 -1
  36. package/dist/src/components/tree-view.stories.js +20 -11
  37. package/dist/src/components/tree-view.stories.js.map +1 -1
  38. package/dist/src/icons.d.ts +8 -8
  39. package/dist/src/icons.d.ts.map +1 -1
  40. package/dist/src/icons.js +78 -78
  41. package/dist/src/icons.js.map +1 -1
  42. package/dist/src/index.d.ts +3 -1
  43. package/dist/src/index.d.ts.map +1 -1
  44. package/dist/src/index.js +2 -0
  45. package/dist/src/index.js.map +1 -1
  46. package/dist/src/shadcn/components/ui/badge.d.ts +1 -1
  47. package/dist/src/shadcn/components/ui/card.d.ts +5 -1
  48. package/dist/src/shadcn/components/ui/card.d.ts.map +1 -1
  49. package/dist/src/shadcn/components/ui/card.js +21 -8
  50. package/dist/src/shadcn/components/ui/card.js.map +1 -1
  51. package/dist/src/shadcn/components/ui/card.stories.d.ts +302 -1
  52. package/dist/src/shadcn/components/ui/card.stories.d.ts.map +1 -1
  53. package/dist/src/shadcn/components/ui/card.stories.js +23 -2
  54. package/dist/src/shadcn/components/ui/card.stories.js.map +1 -1
  55. package/dist/src/shadcn/components/ui/chart.d.ts +2 -2
  56. package/dist/src/shadcn/components/ui/chart.d.ts.map +1 -1
  57. package/dist/src/shadcn/components/ui/chart.js +2 -2
  58. package/dist/src/shadcn/components/ui/chart.js.map +1 -1
  59. package/dist/src/shadcn/components/ui/combobox.d.ts +13 -0
  60. package/dist/src/shadcn/components/ui/combobox.d.ts.map +1 -1
  61. package/dist/src/shadcn/components/ui/combobox.js +105 -12
  62. package/dist/src/shadcn/components/ui/combobox.js.map +1 -1
  63. package/dist/src/shadcn/components/ui/form.d.ts +2 -2
  64. package/dist/src/shadcn/components/ui/form.d.ts.map +1 -1
  65. package/dist/src/shadcn/components/ui/form.js +4 -4
  66. package/dist/src/shadcn/components/ui/form.js.map +1 -1
  67. package/dist/src/shadcn/components/ui/pagination.d.ts +8 -1
  68. package/dist/src/shadcn/components/ui/pagination.d.ts.map +1 -1
  69. package/dist/src/shadcn/components/ui/pagination.js +36 -19
  70. package/dist/src/shadcn/components/ui/pagination.js.map +1 -1
  71. package/dist/src/shadcn/components/ui/pagination.stories.d.ts.map +1 -1
  72. package/dist/src/shadcn/components/ui/pagination.stories.js +44 -37
  73. package/dist/src/shadcn/components/ui/pagination.stories.js.map +1 -1
  74. package/dist/src/shadcn/components/ui/sonner.d.ts +3 -3
  75. package/dist/src/shadcn/components/ui/sonner.d.ts.map +1 -1
  76. package/dist/src/shadcn/components/ui/sonner.js +4 -4
  77. package/dist/src/shadcn/components/ui/sonner.js.map +1 -1
  78. package/dist/src/shadcn/components/ui/table.d.ts.map +1 -1
  79. package/dist/src/shadcn/components/ui/table.js +1 -1
  80. package/dist/src/shadcn/components/ui/table.js.map +1 -1
  81. package/dist/src/shadcn/components/ui/tabs.d.ts +6 -1
  82. package/dist/src/shadcn/components/ui/tabs.d.ts.map +1 -1
  83. package/dist/src/shadcn/components/ui/tabs.js +12 -8
  84. package/dist/src/shadcn/components/ui/tabs.js.map +1 -1
  85. package/dist/src/shadcn/components/ui/tree.d.ts.map +1 -1
  86. package/dist/src/shadcn/components/ui/tree.js +21 -9
  87. package/dist/src/shadcn/components/ui/tree.js.map +1 -1
  88. package/dist/src/typography.css +24 -8
  89. package/package.json +1 -1
  90. package/src/components/button-dropdown.stories.tsx +41 -0
  91. package/src/components/button-dropdown.tsx +95 -0
  92. package/src/components/data-table.stories.tsx +52 -1
  93. package/src/components/data-table.tsx +8 -4
  94. package/src/components/fhir-structure-view.tsx +29 -29
  95. package/src/components/segment-control.tsx +6 -5
  96. package/src/components/split-button.tsx +2 -2
  97. package/src/components/tree-view.stories.tsx +20 -14
  98. package/src/components/tree-view.tsx +122 -56
  99. package/src/icons.tsx +78 -78
  100. package/src/index.tsx +3 -2
  101. package/src/shadcn/components/ui/card.stories.tsx +17 -3
  102. package/src/shadcn/components/ui/card.tsx +52 -8
  103. package/src/shadcn/components/ui/chart.tsx +2 -2
  104. package/src/shadcn/components/ui/combobox.tsx +132 -10
  105. package/src/shadcn/components/ui/form.tsx +5 -7
  106. package/src/shadcn/components/ui/pagination.stories.tsx +8 -2
  107. package/src/shadcn/components/ui/pagination.tsx +54 -3
  108. package/src/shadcn/components/ui/sonner.tsx +5 -5
  109. package/src/shadcn/components/ui/table.tsx +6 -1
  110. package/src/shadcn/components/ui/tabs.tsx +26 -17
  111. package/src/shadcn/components/ui/tree.tsx +27 -9
  112. package/src/typography.css +24 -8
  113. package/dist/src/components/patient-table.d.ts +0 -73
  114. package/dist/src/components/patient-table.d.ts.map +0 -1
  115. package/dist/src/components/patient-table.js +0 -921
  116. package/dist/src/components/patient-table.js.map +0 -1
  117. package/dist/src/components/patient-table.stories.d.ts +0 -111
  118. package/dist/src/components/patient-table.stories.d.ts.map +0 -1
  119. package/dist/src/components/patient-table.stories.js +0 -172
  120. package/dist/src/components/patient-table.stories.js.map +0 -1
  121. package/src/components/patient-table.stories.tsx +0 -111
  122. package/src/components/patient-table.tsx +0 -1301
@@ -1,1301 +0,0 @@
1
- import type {
2
- Column,
3
- ColumnDef,
4
- ColumnPinningState,
5
- Header,
6
- } from "@tanstack/react-table";
7
- import {
8
- createColumnHelper,
9
- flexRender,
10
- getCoreRowModel,
11
- useReactTable,
12
- } from "@tanstack/react-table";
13
- import {
14
- ArrowLeftToLine,
15
- ArrowRightToLine,
16
- ChevronDown,
17
- ChevronUp,
18
- GripVertical,
19
- MoreHorizontal,
20
- PinOff,
21
- Search,
22
- } from "lucide-react";
23
- import type { CSSProperties } from "react";
24
- import { useEffect, useMemo, useState } from "react";
25
- import { Button } from "#shadcn/components/ui/button";
26
- import {
27
- DropdownMenu,
28
- DropdownMenuContent,
29
- DropdownMenuItem,
30
- DropdownMenuTrigger,
31
- } from "#shadcn/components/ui/dropdown-menu";
32
- import { Input } from "#shadcn/components/ui/input";
33
- import { cn } from "#shadcn/lib/utils";
34
-
35
- // Unstyled table components to preserve custom styling
36
- const Table = ({ className, ...props }: React.ComponentProps<"table">) => (
37
- <table className={className} {...props} />
38
- );
39
- const TableHeader = ({
40
- className,
41
- ...props
42
- }: React.ComponentProps<"thead">) => <thead className={className} {...props} />;
43
- const TableBody = ({ className, ...props }: React.ComponentProps<"tbody">) => (
44
- <tbody className={className} {...props} />
45
- );
46
- const TableRow = ({ className, ...props }: React.ComponentProps<"tr">) => (
47
- <tr className={className} {...props} />
48
- );
49
- const TableHead = ({ className, ...props }: React.ComponentProps<"th">) => (
50
- <th className={className} {...props} />
51
- );
52
- const TableCell = ({ className, ...props }: React.ComponentProps<"td">) => (
53
- <td className={className} {...props} />
54
- );
55
-
56
- const getPinningStyles = <T,>(column: Column<T>): CSSProperties => {
57
- const isPinned = column.getIsPinned();
58
- return {
59
- left: isPinned === "left" ? `${column.getStart("left")}px` : undefined,
60
- right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined,
61
- position: isPinned ? "sticky" : "relative",
62
- width: column.getSize(),
63
- zIndex: isPinned ? 1 : 0,
64
- };
65
- };
66
-
67
- // Column configuration types
68
- export type ColumnType = "text" | "code" | "link" | "button";
69
-
70
- export interface ActionConfig {
71
- label: string;
72
- onClick: (rowId: string) => void;
73
- variant?: "primary" | "secondary" | "link" | "ghost";
74
- danger?: boolean;
75
- }
76
-
77
- export interface ColumnConfig {
78
- key: string;
79
- label: string;
80
- width?: string;
81
- fixed?: boolean;
82
- rightAlign?: boolean;
83
- type?: ColumnType;
84
- sortable?: boolean;
85
- filterable?: boolean;
86
- // For action columns
87
- actions?: ActionConfig[];
88
- }
89
-
90
- // Styles
91
- const styles = {
92
- // Table container
93
- tableContainer: cn(
94
- "w-full",
95
- "overflow-x-auto",
96
- "border",
97
- "border-border-secondary",
98
- "rounded-md",
99
- ),
100
- table: cn(
101
- "w-full",
102
- "table-fixed",
103
- "border-collapse",
104
- "text-sm",
105
- "[&_td]:border-border",
106
- "[&_th]:border-border",
107
- "border-separate",
108
- "border-spacing-0",
109
- "[&_tfoot_td]:border-t",
110
- "[&_th]:border-b",
111
- "[&_tr]:border-none",
112
- ),
113
-
114
- // Global cell padding
115
- cellPadding: cn("px-4"),
116
-
117
- pinnedColumn: cn(
118
- "data-pinned:bg-background/90",
119
- "data-pinned:backdrop-blur-xs",
120
- "[&[data-pinned=left][data-last-col=left]]:border-r",
121
- "[&[data-pinned=left][data-last-col=left]]:border-r-border-primary",
122
- "[&[data-pinned=right][data-last-col=right]]:border-l",
123
- "[&[data-pinned=right][data-last-col=right]]:border-l-border-primary",
124
- ),
125
- pinnedHeader: cn(
126
- "data-pinned:bg-muted/90",
127
- "data-pinned:backdrop-blur-xs",
128
- "[&[data-pinned=left][data-last-col=left]]:border-r",
129
- "[&[data-pinned=left][data-last-col=left]]:border-r-border-primary",
130
- "[&[data-pinned=right][data-last-col=right]]:border-l",
131
- "[&[data-pinned=right][data-last-col=right]]:border-l-border-primary",
132
- ),
133
-
134
- // Header styles
135
- thead: cn("bg-bg-secondary"),
136
- th: cn("h-8", "text-left", "font-medium", "text-text-primary", "select-none"),
137
- thSortable: cn("cursor-pointer", "hover:bg-bg-tertiary"),
138
- thSorted: cn("bg-bg-link/3", "border-b", "border-border-link"),
139
- thRightAlign: cn("text-right"),
140
-
141
- // Cell styles for sorted columns
142
- cellSorted: cn("bg-bg-link/3"),
143
-
144
- // Header content
145
- headerContent: cn(
146
- "h-8",
147
- "flex",
148
- "items-center",
149
- "justify-between",
150
- "typo-body",
151
- "text-text-secondary",
152
- "relative",
153
- "w-full",
154
- ),
155
- headerText: cn("truncate", "pr-3"),
156
- headerIcons: cn("flex", "flex-col", "items-center", "justify-center"),
157
- headerIcon: cn("w-3", "h-3"),
158
- headerIconInactive: cn("w-3", "h-3", "opacity-30"),
159
-
160
- // Cell content
161
- cellContent: cn("h-8", "flex", "items-center", "text-text-primary"),
162
- cellText: cn("truncate"),
163
- cellCode: cn("h-8", "flex", "items-center", "text-text-primary", "typo-code"),
164
- cellRightAlign: cn("justify-end"),
165
- cellTextRightAlign: cn("text-right"),
166
-
167
- // Filter row
168
- filterRow: cn("bg-white", "border-b", "border-border-secondary"),
169
- filterCell: cn("px-1", "h-8"),
170
- filterCellPinned: cn("px-1", "h-8", "bg-white/90", "backdrop-blur-xs"),
171
- filterActions: cn("text-text-tertiary", "text-sm"),
172
- filterInput: cn("border-0", "h-8"),
173
- filterIcon: cn("w-4", "h-4", "text-text-tertiary"),
174
-
175
- dataRow: cn("hover:bg-bg-link/10"),
176
- dataRowZebra: cn("bg-bg-secondary", "hover:bg-bg-link/10"),
177
- dataCell: cn("h-8"),
178
-
179
- // Action button
180
- actionButton: cn(
181
- "text-text-link",
182
- "hover:text-text-link_hover",
183
- "transition-colors",
184
- "cursor-pointer",
185
- ),
186
-
187
- // Action link
188
- actionLink: cn(
189
- "text-text-link",
190
- "hover:text-text-link_hover",
191
- "transition-colors",
192
- "cursor-pointer",
193
- ),
194
-
195
- // Column resize handle
196
- resizeHandle: cn(
197
- "absolute",
198
- "right-0",
199
- "top-0",
200
- "h-full",
201
- "w-1",
202
- "bg-border-primary",
203
- "cursor-col-resize",
204
- "user-select-none",
205
- "touch-action-none",
206
- "opacity-0",
207
- "hover:opacity-100",
208
- "active:opacity-100",
209
- "transition-opacity",
210
- "duration-150",
211
- "before:absolute",
212
- "before:right-[-4px]",
213
- "before:top-0",
214
- "before:w-2",
215
- "before:h-full",
216
- "before:content-['']",
217
- ),
218
- resizeHandleActive: cn("opacity-100", "bg-border-link"),
219
-
220
- // Resizable header indicator
221
- resizableHeader: cn(
222
- "group-hover:[&:not(:last-child)]:after:opacity-30",
223
- "after:absolute",
224
- "after:right-0",
225
- "after:top-1/2",
226
- "after:-translate-y-1/2",
227
- "after:h-4",
228
- "after:w-px",
229
- "after:bg-border-primary",
230
- "after:opacity-0",
231
- "after:transition-opacity",
232
- "after:duration-150",
233
- "hover:after:opacity-50",
234
- "last:after:w-0",
235
- ),
236
-
237
- // Draggable header styles
238
- draggableHeader: cn("transition-all", "duration-150", "group/header"),
239
- dragZone: cn(
240
- "absolute",
241
- "left-0",
242
- "top-0",
243
- "h-full",
244
- "cursor-grab",
245
- "active:cursor-grabbing",
246
- "flex",
247
- "items-center",
248
- "justify-start",
249
- "pl-0",
250
- "opacity-0",
251
- "hover:opacity-100",
252
- "group-hover/header:opacity-60",
253
- "hover:!opacity-100",
254
- "transition-opacity",
255
- "duration-150",
256
- "bg-transparent",
257
- "border-none",
258
- "text-text-tertiary",
259
- "hover:text-text-secondary",
260
- "right-5",
261
- ),
262
- draggingHeader: cn(
263
- "bg-bg-primary_inverse/10",
264
- "scale-105",
265
- "shadow-lg",
266
- "z-50",
267
- ),
268
- draggingColumn: cn("bg-bg-primary_inverse/10", "shadow-inner"),
269
- dropZone: cn(
270
- "relative",
271
- "before:absolute",
272
- "before:left-0",
273
- "before:top-0",
274
- "before:w-1",
275
- "before:h-full",
276
- "before:bg-border-link",
277
- "before:opacity-0",
278
- "before:transition-opacity",
279
- "before:duration-150",
280
- ),
281
- dropZoneActive: cn("before:opacity-100"),
282
- } as const;
283
-
284
- // Types
285
- export interface PatientRow {
286
- id: string;
287
- firstName: string;
288
- lastName: string;
289
- phoneNumber: string;
290
- email: string;
291
- birthDate: string;
292
- gender: "MALE" | "FEMALE" | "OTHER";
293
- street: string;
294
- city: string;
295
- state: string;
296
- zip: string;
297
- country?: string;
298
- encounters: number;
299
- }
300
-
301
- export interface ColumnSearchConfig {
302
- [key: string]: {
303
- enabled: boolean;
304
- type: "text" | "date";
305
- placeholder: string;
306
- };
307
- }
308
-
309
- export interface PaginationValues {
310
- pageIndex: number;
311
- pageSize: number;
312
- }
313
-
314
- export interface TableHeaderContentProps {
315
- content: React.ReactNode;
316
- isSortable?: boolean;
317
- sortDirection?: "asc" | "desc" | null;
318
- isPinnable?: boolean;
319
- pinnedDirection?: "left" | "right" | false;
320
- onPin?: (direction: "left" | "right" | false) => void;
321
- }
322
-
323
- export interface TableCellContentProps {
324
- content: React.ReactNode;
325
- type?: ColumnType;
326
- rightAlign?: boolean;
327
- actions?: ActionConfig[];
328
- rowId?: string;
329
- }
330
-
331
- // Mock components
332
- function TableHeaderContent({
333
- content,
334
- isSortable = false,
335
- sortDirection = null,
336
- isPinnable = false,
337
- pinnedDirection = false,
338
- onPin,
339
- }: TableHeaderContentProps) {
340
- return (
341
- <div className={styles.headerContent}>
342
- <span className={styles.headerText}>{content}</span>
343
- <div className="flex items-center">
344
- {isSortable && (
345
- <div className={styles.headerIcons}>
346
- {sortDirection === "asc" ? (
347
- <ChevronUp className={styles.headerIcon} />
348
- ) : sortDirection === "desc" ? (
349
- <ChevronDown className={styles.headerIcon} />
350
- ) : (
351
- <div className="flex flex-col">
352
- <ChevronUp className={styles.headerIconInactive} />
353
- <ChevronDown className={styles.headerIconInactive} />
354
- </div>
355
- )}
356
- </div>
357
- )}
358
- {isPinnable &&
359
- onPin &&
360
- (pinnedDirection ? (
361
- <Button
362
- size="small"
363
- variant="ghost"
364
- className="h-6 w-6 p-0 ml-1 opacity-60 hover:opacity-100"
365
- onClick={() => onPin(false)}
366
- title="Unpin column"
367
- >
368
- <PinOff className="h-3 w-3" />
369
- </Button>
370
- ) : (
371
- <DropdownMenu>
372
- <DropdownMenuTrigger asChild>
373
- <Button
374
- size="small"
375
- variant="ghost"
376
- className="h-6 w-6 p-0 ml-1 opacity-60 hover:opacity-100"
377
- title="Pin column"
378
- >
379
- <MoreHorizontal className="h-3 w-3" />
380
- </Button>
381
- </DropdownMenuTrigger>
382
- <DropdownMenuContent align="end">
383
- <DropdownMenuItem onClick={() => onPin("left")}>
384
- <ArrowLeftToLine className="mr-2 h-4 w-4" />
385
- Stick to left
386
- </DropdownMenuItem>
387
- <DropdownMenuItem onClick={() => onPin("right")}>
388
- <ArrowRightToLine className="mr-2 h-4 w-4" />
389
- Stick to right
390
- </DropdownMenuItem>
391
- </DropdownMenuContent>
392
- </DropdownMenu>
393
- ))}
394
- </div>
395
- </div>
396
- );
397
- }
398
-
399
- function TableCellContent({
400
- content,
401
- type = "text",
402
- rightAlign = false,
403
- actions = [],
404
- rowId,
405
- }: TableCellContentProps) {
406
- // For action types, render actions instead of content
407
- if (type === "link" || type === "button") {
408
- return (
409
- <div
410
- className={cn(styles.cellContent, rightAlign && styles.cellRightAlign)}
411
- >
412
- <div className="flex gap-2">
413
- {actions.map((action, index) => {
414
- const handleClick = (e: React.MouseEvent) => {
415
- e.stopPropagation();
416
- action.onClick(rowId || "");
417
- };
418
-
419
- const actionKey = `${rowId}-${action.label}-${index}`;
420
-
421
- if (type === "button") {
422
- return (
423
- <Button
424
- key={actionKey}
425
- variant={action.variant || "primary"}
426
- size="small"
427
- {...(action.danger !== undefined && {
428
- danger: action.danger,
429
- })}
430
- onClick={handleClick}
431
- >
432
- {action.label}
433
- </Button>
434
- );
435
- }
436
-
437
- // Default to link type
438
- return (
439
- <button
440
- key={actionKey}
441
- type="button"
442
- className={styles.actionLink}
443
- onClick={handleClick}
444
- >
445
- {action.label}
446
- </button>
447
- );
448
- })}
449
- </div>
450
- </div>
451
- );
452
- }
453
-
454
- // For regular content
455
- const cellClass = cn(
456
- type === "code" ? styles.cellCode : styles.cellContent,
457
- rightAlign && styles.cellRightAlign,
458
- );
459
-
460
- const textClass = cn(
461
- styles.cellText,
462
- rightAlign && styles.cellTextRightAlign,
463
- );
464
-
465
- return (
466
- <div className={cellClass}>
467
- <span className={textClass}>{content}</span>
468
- </div>
469
- );
470
- }
471
-
472
- // Filter row component
473
- function FilterRow<TData, TValue>({
474
- headers,
475
- draggedColumn,
476
- dropTarget,
477
- }: {
478
- headers: Header<TData, TValue>[]; // TanStack Table headers
479
- draggedColumn?: string | null;
480
- dropTarget?: string | null;
481
- }) {
482
- return (
483
- <TableRow className={styles.filterRow}>
484
- {headers.map((header) => {
485
- const tableColumn = header.column;
486
- const columnKey = header.id;
487
-
488
- const isPinned = tableColumn.getIsPinned();
489
- const isBeingDragged = draggedColumn === columnKey;
490
- const isDropTarget = dropTarget === columnKey;
491
- const isLastLeftPinned =
492
- isPinned === "left" && tableColumn.getIsLastColumn("left");
493
- const isFirstRightPinned =
494
- isPinned === "right" && tableColumn.getIsFirstColumn("right");
495
-
496
- return (
497
- <TableCell
498
- key={`filter-${columnKey}`}
499
- className={cn(
500
- isPinned ? styles.filterCellPinned : styles.filterCell,
501
- styles.pinnedColumn,
502
- isBeingDragged && styles.draggingColumn,
503
- isDropTarget && styles.dropZone,
504
- isDropTarget && styles.dropZoneActive,
505
- )}
506
- style={{
507
- ...getPinningStyles(tableColumn),
508
- }}
509
- data-pinned={isPinned || undefined}
510
- data-last-col={
511
- isLastLeftPinned
512
- ? "left"
513
- : isFirstRightPinned
514
- ? "right"
515
- : undefined
516
- }
517
- >
518
- {columnKey === "actions" ? (
519
- <div className={styles.filterActions}></div>
520
- ) : (
521
- <Input
522
- placeholder="Search"
523
- leftSlot={<Search className={styles.filterIcon} />}
524
- onChange={(e) =>
525
- console.log(`Filter ${columnKey}:`, e.target.value)
526
- }
527
- className={styles.filterInput}
528
- />
529
- )}
530
- </TableCell>
531
- );
532
- })}
533
- </TableRow>
534
- );
535
- }
536
-
537
- // Mock DataTable component
538
- interface DataTableProps<T> {
539
- columns: ColumnDef<T>[];
540
- data: T[];
541
- showZebraStripes: boolean;
542
- showFilters?: boolean;
543
- showSorting?: boolean;
544
- enableColumnResizing?: boolean;
545
- enableColumnReordering?: boolean;
546
- enableColumnPinning?: boolean;
547
- onSort?: (columnKey: string) => void;
548
- columnWidths?: Record<string, string>;
549
- sortConfig?: { key: string; direction: "asc" | "desc" } | null;
550
- columnOrder?: string[];
551
- onColumnOrderChange?: (newOrder: string[]) => void;
552
- columnPinning?: ColumnPinningState;
553
- onColumnPinningChange?: (pinning: ColumnPinningState) => void;
554
- }
555
-
556
- function DataTable<T>({
557
- columns,
558
- data,
559
- showZebraStripes,
560
- showFilters = true,
561
- showSorting = true,
562
- enableColumnResizing = false,
563
- enableColumnReordering = false,
564
- enableColumnPinning = false,
565
- onSort,
566
- columnWidths = {},
567
- sortConfig,
568
- columnOrder = [],
569
- onColumnOrderChange,
570
- columnPinning = {},
571
- onColumnPinningChange,
572
- columnConfigs,
573
- }: DataTableProps<T> & { columnConfigs?: ColumnConfig[] }) {
574
- const [draggedColumn, setDraggedColumn] = useState<string | null>(null);
575
- const [dropTarget, setDropTarget] = useState<string | null>(null);
576
-
577
- const initialColumnSizing = useMemo(() => {
578
- if (!enableColumnResizing || !columnConfigs) return {};
579
-
580
- const sizing: Record<string, number> = {};
581
- columnConfigs.forEach((config) => {
582
- if (config.width) {
583
- const width = parseInt(config.width.replace("px", ""), 10);
584
- if (!Number.isNaN(width)) {
585
- sizing[config.key] = width;
586
- }
587
- }
588
- });
589
- return sizing;
590
- }, [enableColumnResizing, columnConfigs]);
591
-
592
- const table = useReactTable({
593
- data,
594
- columns,
595
- getCoreRowModel: getCoreRowModel(),
596
- enableColumnResizing,
597
- enableColumnPinning,
598
- columnResizeMode: "onChange",
599
- state: {
600
- ...(columnOrder.length > 0 && { columnOrder }),
601
- ...(enableColumnPinning && { columnPinning }),
602
- },
603
- ...(onColumnOrderChange && {
604
- onColumnOrderChange: (updater) => {
605
- const newOrder =
606
- typeof updater === "function" ? updater(columnOrder) : updater;
607
- onColumnOrderChange(newOrder);
608
- },
609
- }),
610
- ...(onColumnPinningChange &&
611
- enableColumnPinning && {
612
- onColumnPinningChange: (updater) => {
613
- const newPinning =
614
- typeof updater === "function" ? updater(columnPinning) : updater;
615
- onColumnPinningChange(newPinning);
616
- },
617
- }),
618
- initialState: {
619
- columnSizing: initialColumnSizing,
620
- },
621
- });
622
-
623
- return (
624
- <div className={styles.tableContainer}>
625
- <Table className={styles.table}>
626
- <TableHeader className={styles.thead}>
627
- <TableRow className={enableColumnResizing ? "group" : ""}>
628
- {table.getHeaderGroups()[0]?.headers.map((header) => {
629
- const columnKey = header.id;
630
- const isSortable = columnKey !== "actions";
631
-
632
- const isPinned = header.column.getIsPinned();
633
- const isSorted = sortConfig?.key === columnKey;
634
- const isDraggable = enableColumnReordering && !isPinned;
635
- const isDragging = draggedColumn === columnKey;
636
- const isDropTarget = dropTarget === columnKey;
637
- const isLastLeftPinned =
638
- isPinned === "left" && header.column.getIsLastColumn("left");
639
- const isFirstRightPinned =
640
- isPinned === "right" && header.column.getIsFirstColumn("right");
641
-
642
- const columnConfig = columnConfigs?.find(
643
- (config) => config.key === columnKey,
644
- );
645
- const isRightAlign = columnConfig?.rightAlign || false;
646
-
647
- return (
648
- <TableHead
649
- key={`header-${columnKey}`}
650
- className={cn(
651
- styles.cellPadding,
652
- styles.th,
653
- isSortable && showSorting && styles.thSortable,
654
- styles.pinnedHeader,
655
- isSorted && styles.thSorted,
656
- isRightAlign && styles.thRightAlign,
657
- "relative",
658
- enableColumnResizing &&
659
- header.column.getCanResize() &&
660
- styles.resizableHeader,
661
- isDraggable && styles.draggableHeader,
662
- isDragging && styles.draggingHeader,
663
- isDragging && styles.draggingColumn,
664
- isDropTarget && styles.dropZone,
665
- isDropTarget && styles.dropZoneActive,
666
- )}
667
- style={{
668
- ...getPinningStyles(header.column),
669
- ...(enableColumnResizing
670
- ? {}
671
- : { width: columnWidths[columnKey] || "200px" }),
672
- }}
673
- data-pinned={isPinned || undefined}
674
- data-last-col={
675
- isLastLeftPinned
676
- ? "left"
677
- : isFirstRightPinned
678
- ? "right"
679
- : undefined
680
- }
681
- onClick={(e) => {
682
- const target = e.currentTarget;
683
- if (
684
- target.closest("[data-resize-handle]") ||
685
- target.closest("[data-drag-zone]")
686
- ) {
687
- return;
688
- }
689
- if (isSortable && showSorting && onSort) {
690
- onSort(String(columnKey));
691
- }
692
- }}
693
- onDragOver={(e) => {
694
- if (
695
- !isDraggable ||
696
- !draggedColumn ||
697
- draggedColumn === columnKey
698
- )
699
- return;
700
- e.preventDefault();
701
- setDropTarget(columnKey);
702
- }}
703
- onDragLeave={(e) => {
704
- const rect = e.currentTarget.getBoundingClientRect();
705
- const x = e.clientX;
706
- const y = e.clientY;
707
- if (
708
- x < rect.left ||
709
- x > rect.right ||
710
- y < rect.top ||
711
- y > rect.bottom
712
- ) {
713
- setDropTarget(null);
714
- }
715
- }}
716
- onDrop={(e) => {
717
- e.preventDefault();
718
- if (!isDraggable || !draggedColumn || !onColumnOrderChange)
719
- return;
720
-
721
- const currentOrder = table
722
- .getAllLeafColumns()
723
- .map((col) => col.id);
724
- const draggedIndex = currentOrder.indexOf(draggedColumn);
725
- const targetIndex = currentOrder.indexOf(columnKey);
726
-
727
- if (draggedIndex !== -1 && targetIndex !== -1) {
728
- const newOrder = [...currentOrder];
729
- newOrder.splice(draggedIndex, 1);
730
- newOrder.splice(targetIndex, 0, draggedColumn);
731
- onColumnOrderChange(newOrder);
732
- }
733
-
734
- setDraggedColumn(null);
735
- setDropTarget(null);
736
- }}
737
- >
738
- {isDraggable && (
739
- <button
740
- type="button"
741
- data-drag-zone
742
- draggable
743
- className={styles.dragZone}
744
- onDragStart={(e) => {
745
- setDraggedColumn(columnKey);
746
- e.dataTransfer.effectAllowed = "move";
747
- }}
748
- onDragEnd={() => {
749
- setDraggedColumn(null);
750
- setDropTarget(null);
751
- }}
752
- >
753
- <GripVertical className="w-4 h-4" />
754
- </button>
755
- )}
756
-
757
- {flexRender(
758
- header.column.columnDef.header,
759
- header.getContext(),
760
- )}
761
- {enableColumnResizing && header.column.getCanResize() && (
762
- <div
763
- data-resize-handle
764
- {...{
765
- onMouseDown: header.getResizeHandler(),
766
- onTouchStart: header.getResizeHandler(),
767
- onClick: (e) => e.stopPropagation(),
768
- className: cn(
769
- styles.resizeHandle,
770
- header.column.getIsResizing() &&
771
- styles.resizeHandleActive,
772
- ),
773
- }}
774
- />
775
- )}
776
- </TableHead>
777
- );
778
- })}
779
- </TableRow>
780
- </TableHeader>
781
- <TableBody>
782
- {/* Filter row */}
783
- {showFilters && (
784
- <FilterRow
785
- headers={table.getHeaderGroups()[0]?.headers || []}
786
- draggedColumn={draggedColumn}
787
- dropTarget={dropTarget}
788
- />
789
- )}
790
-
791
- {/* Data rows */}
792
- {table.getRowModel().rows.map((row, rowIndex) => (
793
- <TableRow
794
- key={`row-${row.id}`}
795
- className={cn(
796
- styles.dataRow,
797
- showZebraStripes &&
798
- rowIndex % 2 === (showFilters ? 0 : 1) &&
799
- styles.dataRowZebra,
800
- )}
801
- >
802
- {row.getVisibleCells().map((cell) => {
803
- const columnKey = cell.column.id;
804
- const isPinned = cell.column.getIsPinned();
805
- const isSorted = sortConfig?.key === columnKey;
806
- const isBeingDragged = draggedColumn === columnKey;
807
- const isDropTarget = dropTarget === columnKey;
808
- const isLastLeftPinned =
809
- isPinned === "left" && cell.column.getIsLastColumn("left");
810
- const isFirstRightPinned =
811
- isPinned === "right" && cell.column.getIsFirstColumn("right");
812
-
813
- return (
814
- <TableCell
815
- key={cell.id}
816
- className={cn(
817
- styles.cellPadding,
818
- styles.dataCell,
819
- styles.pinnedColumn,
820
- isSorted && styles.cellSorted,
821
- isBeingDragged && styles.draggingColumn,
822
- isDropTarget && styles.dropZone,
823
- isDropTarget && styles.dropZoneActive,
824
- )}
825
- style={{
826
- ...getPinningStyles(cell.column),
827
- ...(enableColumnResizing
828
- ? {}
829
- : { width: columnWidths[columnKey] || "200px" }),
830
- }}
831
- data-pinned={isPinned || undefined}
832
- data-last-col={
833
- isLastLeftPinned
834
- ? "left"
835
- : isFirstRightPinned
836
- ? "right"
837
- : undefined
838
- }
839
- >
840
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
841
- </TableCell>
842
- );
843
- })}
844
- </TableRow>
845
- ))}
846
- </TableBody>
847
- </Table>
848
- </div>
849
- );
850
- }
851
-
852
- const columnHelper = createColumnHelper<PatientRow>();
853
-
854
- const columns = [
855
- columnHelper.accessor("firstName", {
856
- header: () => <TableHeaderContent content="First name" />,
857
- cell: (props) => <TableCellContent content={props.getValue()} />,
858
- }),
859
- columnHelper.accessor("lastName", {
860
- header: () => <TableHeaderContent content="Last name" />,
861
- cell: (props) => <TableCellContent content={props.getValue()} />,
862
- }),
863
- columnHelper.accessor("id", {
864
- header: () => <TableHeaderContent content="ID" />,
865
- cell: (props) => (
866
- <TableCellContent content={props.getValue()} type="code" />
867
- ),
868
- }),
869
- columnHelper.accessor("birthDate", {
870
- header: () => <TableHeaderContent content="Birth" />,
871
- cell: (props) => (
872
- <TableCellContent content={props.getValue()} type="code" />
873
- ),
874
- }),
875
- columnHelper.accessor("phoneNumber", {
876
- header: () => <TableHeaderContent content="Phone number" />,
877
- cell: (props) => <TableCellContent content={props.getValue()} />,
878
- }),
879
- columnHelper.accessor("email", {
880
- header: () => <TableHeaderContent content="Email" />,
881
- cell: (props) => <TableCellContent content={props.getValue()} />,
882
- }),
883
- columnHelper.accessor("gender", {
884
- header: () => <TableHeaderContent content="Gender" />,
885
- cell: (props) => <TableCellContent content={props.getValue()} />,
886
- }),
887
- columnHelper.accessor("street", {
888
- header: () => <TableHeaderContent content="Street" />,
889
- cell: (props) => <TableCellContent content={props.getValue()} />,
890
- }),
891
- columnHelper.accessor("city", {
892
- header: () => <TableHeaderContent content="City" />,
893
- cell: (props) => <TableCellContent content={props.getValue()} />,
894
- }),
895
- columnHelper.accessor("state", {
896
- header: () => <TableHeaderContent content="State" />,
897
- cell: (props) => <TableCellContent content={props.getValue()} />,
898
- }),
899
- columnHelper.accessor("zip", {
900
- header: () => <TableHeaderContent content="ZIP" />,
901
- cell: (props) => <TableCellContent content={props.getValue()} />,
902
- }),
903
- columnHelper.accessor("country", {
904
- header: () => <TableHeaderContent content="Country" />,
905
- cell: (props) => <TableCellContent content={props.getValue()} />,
906
- }),
907
- columnHelper.accessor("encounters", {
908
- header: () => <TableHeaderContent content="Encounters" />,
909
- cell: (props) => <TableCellContent content={props.getValue()} />,
910
- }),
911
- columnHelper.display({
912
- id: "actions",
913
- header: () => <TableHeaderContent content={"Actions"} />,
914
- cell: ({ row }) => (
915
- <TableCellContent
916
- content={
917
- <button
918
- type="button"
919
- className={styles.actionButton}
920
- onClick={(e) => {
921
- e.stopPropagation();
922
- console.log("Match clicked for:", row.original.id);
923
- }}
924
- >
925
- Match
926
- </button>
927
- }
928
- />
929
- ),
930
- meta: {
931
- fixed: "right",
932
- },
933
- }),
934
- ];
935
-
936
- export type PatientTableProps = {
937
- // data: PatientRow[],
938
- page: number;
939
- count: number;
940
- showFilters?: boolean;
941
- showSorting?: boolean;
942
- enableColumnResizing?: boolean;
943
- enableColumnReordering?: boolean;
944
- enableColumnPinning?: boolean;
945
- showPinningMenu?: boolean;
946
- columnWidths?: Record<string, string>;
947
- columnConfigs?: ColumnConfig[];
948
- };
949
-
950
- export function PatientTable(props: PatientTableProps) {
951
- const {
952
- showFilters = true,
953
- showSorting = true,
954
- enableColumnResizing = false,
955
- enableColumnReordering = false,
956
- enableColumnPinning = false,
957
- showPinningMenu = true,
958
- columnWidths = {},
959
- columnConfigs,
960
- } = props;
961
-
962
- const [sortConfig, setSortConfig] = useState<{
963
- key: string;
964
- direction: "asc" | "desc";
965
- } | null>(null);
966
-
967
- const [columnOrder, setColumnOrder] = useState<string[]>([]);
968
-
969
- const [columnPinning, setColumnPinning] = useState<ColumnPinningState>({});
970
-
971
- useEffect(() => {
972
- if (!enableColumnPinning || !columnConfigs) return;
973
-
974
- const fixedColumns = columnConfigs.filter((config) => config.fixed);
975
- if (fixedColumns.length > 0) {
976
- const initialPinning: ColumnPinningState = {
977
- right: fixedColumns.map((config) => config.key),
978
- };
979
- setColumnPinning(initialPinning);
980
- }
981
- }, [enableColumnPinning, columnConfigs]);
982
-
983
- const handleSort = (columnKey: string) => {
984
- setSortConfig((current) => {
985
- if (current?.key === columnKey) {
986
- return current.direction === "asc"
987
- ? { key: columnKey, direction: "desc" }
988
- : null;
989
- }
990
- return { key: columnKey, direction: "asc" };
991
- });
992
- };
993
-
994
- const data: PatientRow[] = [
995
- {
996
- id: "PAT-000001",
997
- firstName: "John",
998
- lastName: "Smith",
999
- phoneNumber: "(555) 123-4567",
1000
- email: "john.smith@gmail.com",
1001
- birthDate: "1985-03-15",
1002
- gender: "MALE",
1003
- street: "123 Main St",
1004
- city: "New York",
1005
- state: "NY",
1006
- zip: "10001",
1007
- encounters: 5,
1008
- },
1009
- {
1010
- id: "PAT-000002",
1011
- firstName: "Jane",
1012
- lastName: "Johnson",
1013
- phoneNumber: "(555) 234-5678",
1014
- email: "jane.johnson@yahoo.com",
1015
- birthDate: "1990-07-22",
1016
- gender: "FEMALE",
1017
- street: "456 Oak Ave",
1018
- city: "Los Angeles",
1019
- state: "CA",
1020
- zip: "90210",
1021
- encounters: 12,
1022
- },
1023
- {
1024
- id: "PAT-000003",
1025
- firstName: "Michael",
1026
- lastName: "Williams",
1027
- phoneNumber: "(555) 345-6789",
1028
- email: "michael.williams@hotmail.com",
1029
- birthDate: "1978-11-08",
1030
- gender: "MALE",
1031
- street: "789 Pine St",
1032
- city: "Chicago",
1033
- state: "IL",
1034
- zip: "60601",
1035
- encounters: 8,
1036
- },
1037
- {
1038
- id: "PAT-000004",
1039
- firstName: "Sarah",
1040
- lastName: "Brown",
1041
- phoneNumber: "(555) 456-7890",
1042
- email: "sarah.brown@outlook.com",
1043
- birthDate: "1992-01-30",
1044
- gender: "FEMALE",
1045
- street: "321 Elm Ave",
1046
- city: "Houston",
1047
- state: "TX",
1048
- zip: "77001",
1049
- encounters: 3,
1050
- },
1051
- {
1052
- id: "PAT-000005",
1053
- firstName: "David",
1054
- lastName: "Garcia",
1055
- phoneNumber: "(555) 567-8901",
1056
- email: "david.garcia@gmail.com",
1057
- birthDate: "1980-09-12",
1058
- gender: "MALE",
1059
- street: "654 Cedar St",
1060
- city: "Phoenix",
1061
- state: "AZ",
1062
- zip: "85001",
1063
- encounters: 15,
1064
- },
1065
- {
1066
- id: "PAT-000006",
1067
- firstName: "Emily",
1068
- lastName: "Martinez",
1069
- phoneNumber: "(555) 678-9012",
1070
- email: "emily.martinez@icloud.com",
1071
- birthDate: "1995-05-18",
1072
- gender: "FEMALE",
1073
- street: "987 Washington St",
1074
- city: "Philadelphia",
1075
- state: "PA",
1076
- zip: "19101",
1077
- encounters: 7,
1078
- },
1079
- {
1080
- id: "PAT-000007",
1081
- firstName: "Robert",
1082
- lastName: "Davis",
1083
- phoneNumber: "(555) 789-0123",
1084
- email: "robert.davis@aol.com",
1085
- birthDate: "1973-12-03",
1086
- gender: "MALE",
1087
- street: "147 Park Ave",
1088
- city: "San Antonio",
1089
- state: "TX",
1090
- zip: "78201",
1091
- encounters: 22,
1092
- },
1093
- {
1094
- id: "PAT-000008",
1095
- firstName: "Jessica",
1096
- lastName: "Rodriguez",
1097
- phoneNumber: "(555) 890-1234",
1098
- email: "jessica.rodriguez@protonmail.com",
1099
- birthDate: "1988-04-25",
1100
- gender: "FEMALE",
1101
- street: "258 Lincoln St",
1102
- city: "San Diego",
1103
- state: "CA",
1104
- zip: "92101",
1105
- encounters: 9,
1106
- },
1107
- {
1108
- id: "PAT-000009",
1109
- firstName: "William",
1110
- lastName: "Wilson",
1111
- phoneNumber: "(555) 901-2345",
1112
- email: "william.wilson@gmail.com",
1113
- birthDate: "1965-08-14",
1114
- gender: "MALE",
1115
- street: "369 Jefferson Ave",
1116
- city: "Dallas",
1117
- state: "TX",
1118
- zip: "75201",
1119
- encounters: 18,
1120
- },
1121
- {
1122
- id: "PAT-000010",
1123
- firstName: "Ashley",
1124
- lastName: "Anderson",
1125
- phoneNumber: "(555) 012-3456",
1126
- email: "ashley.anderson@yahoo.com",
1127
- birthDate: "1993-10-07",
1128
- gender: "FEMALE",
1129
- street: "741 Madison St",
1130
- city: "San Jose",
1131
- state: "CA",
1132
- zip: "95101",
1133
- encounters: 4,
1134
- },
1135
- {
1136
- id: "PAT-000011",
1137
- firstName: "Alex",
1138
- lastName: "Taylor",
1139
- phoneNumber: "(555) 111-2222",
1140
- email: "alex.taylor@outlook.com",
1141
- birthDate: "1987-06-19",
1142
- gender: "OTHER",
1143
- street: "852 Adams Ave",
1144
- city: "Austin",
1145
- state: "TX",
1146
- zip: "73301",
1147
- encounters: 11,
1148
- },
1149
- ];
1150
-
1151
- const sortedData = useMemo(() => {
1152
- if (!sortConfig) return data;
1153
-
1154
- return [...data].sort((a, b) => {
1155
- const aValue = a[sortConfig.key as keyof PatientRow] ?? "";
1156
- const bValue = b[sortConfig.key as keyof PatientRow] ?? "";
1157
-
1158
- if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
1159
- if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
1160
- return 0;
1161
- });
1162
- }, [sortConfig]);
1163
-
1164
- const generatedColumnWidths = useMemo(() => {
1165
- if (!columnConfigs) {
1166
- return columnWidths;
1167
- }
1168
-
1169
- const configWidths: Record<string, string> = {};
1170
- columnConfigs.forEach((config) => {
1171
- if (config.width) {
1172
- configWidths[config.key] = config.width;
1173
- }
1174
- });
1175
-
1176
- return { ...columnWidths, ...configWidths };
1177
- }, [columnConfigs, columnWidths]);
1178
-
1179
- const generatedColumns = useMemo(() => {
1180
- if (!columnConfigs) {
1181
- return columns;
1182
- }
1183
-
1184
- return columnConfigs.map((config): ColumnDef<PatientRow> => {
1185
- const columnSize = config.width
1186
- ? parseInt(config.width.replace("px", ""), 10)
1187
- : undefined;
1188
-
1189
- if (config.type === "link" || config.type === "button") {
1190
- return {
1191
- id: config.key,
1192
- accessorFn: () => null,
1193
- header: () => <TableHeaderContent content={config.label} />,
1194
- cell: ({ row }: { row: { original: PatientRow } }) => (
1195
- <TableCellContent
1196
- content=""
1197
- {...(config.type && { type: config.type })}
1198
- {...(config.rightAlign !== undefined && {
1199
- rightAlign: config.rightAlign,
1200
- })}
1201
- actions={config.actions || []}
1202
- rowId={row.original.id}
1203
- />
1204
- ),
1205
- ...(config.fixed && { meta: { fixed: "right" } }),
1206
- ...(columnSize && { size: columnSize }),
1207
- };
1208
- }
1209
-
1210
- return {
1211
- accessorKey: config.key,
1212
- header: () => <TableHeaderContent content={config.label} />,
1213
- cell: ({ cell }: { cell: { getValue: () => any } }) => (
1214
- <TableCellContent
1215
- content={cell.getValue()}
1216
- type={config.type || "text"}
1217
- {...(config.rightAlign !== undefined && {
1218
- rightAlign: config.rightAlign,
1219
- })}
1220
- />
1221
- ),
1222
- ...(config.fixed && { meta: { fixed: "right" } }),
1223
- ...(columnSize && { size: columnSize }),
1224
- };
1225
- });
1226
- }, [columnConfigs]);
1227
-
1228
- const columnsWithSorting = generatedColumns.map((column) => ({
1229
- ...column,
1230
- header: () => {
1231
- const columnKey = (column as any).id || (column as any).accessorKey;
1232
- const isSortable = columnKey !== "actions";
1233
- const isPinnedLeft = columnPinning.left?.includes(columnKey);
1234
- const isPinnedRight = columnPinning.right?.includes(columnKey);
1235
- const pinnedDirection = isPinnedLeft
1236
- ? "left"
1237
- : isPinnedRight
1238
- ? "right"
1239
- : false;
1240
-
1241
- const handlePin = (direction: "left" | "right" | false) => {
1242
- const newPinning: ColumnPinningState = { ...columnPinning };
1243
-
1244
- if (newPinning.left) {
1245
- newPinning.left = newPinning.left.filter((id) => id !== columnKey);
1246
- }
1247
- if (newPinning.right) {
1248
- newPinning.right = newPinning.right.filter((id) => id !== columnKey);
1249
- }
1250
-
1251
- if (direction === "left") {
1252
- newPinning.left = [...(newPinning.left || []), columnKey];
1253
- } else if (direction === "right") {
1254
- newPinning.right = [...(newPinning.right || []), columnKey];
1255
- }
1256
-
1257
- setColumnPinning(newPinning);
1258
- };
1259
-
1260
- return (
1261
- <TableHeaderContent
1262
- content={
1263
- typeof column.header === "function"
1264
- ? column.header({} as any)
1265
- : column.header
1266
- }
1267
- isSortable={isSortable && showSorting}
1268
- sortDirection={
1269
- sortConfig?.key === columnKey && sortConfig
1270
- ? sortConfig.direction
1271
- : null
1272
- }
1273
- isPinnable={enableColumnPinning && showPinningMenu}
1274
- pinnedDirection={pinnedDirection}
1275
- onPin={handlePin}
1276
- />
1277
- );
1278
- },
1279
- })) as ColumnDef<PatientRow>[];
1280
-
1281
- return (
1282
- <DataTable
1283
- columns={columnsWithSorting}
1284
- data={sortedData}
1285
- showZebraStripes={true}
1286
- showFilters={showFilters}
1287
- showSorting={showSorting}
1288
- enableColumnResizing={enableColumnResizing}
1289
- enableColumnReordering={enableColumnReordering}
1290
- enableColumnPinning={enableColumnPinning}
1291
- onSort={handleSort}
1292
- columnWidths={generatedColumnWidths}
1293
- sortConfig={sortConfig}
1294
- columnOrder={columnOrder}
1295
- onColumnOrderChange={setColumnOrder}
1296
- columnPinning={columnPinning}
1297
- onColumnPinningChange={setColumnPinning}
1298
- {...(columnConfigs && { columnConfigs })}
1299
- />
1300
- );
1301
- }