pf-common-components 1.0.0

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 (215) hide show
  1. package/components.json +17 -0
  2. package/dist/assets/Arrow.d.ts +7 -0
  3. package/dist/assets/Arrow.js +5 -0
  4. package/dist/assets/CalenderIcon.d.ts +2 -0
  5. package/dist/assets/CalenderIcon.js +5 -0
  6. package/dist/assets/ClearAll.d.ts +2 -0
  7. package/dist/assets/ClearAll.js +5 -0
  8. package/dist/assets/CrossIcon.d.ts +8 -0
  9. package/dist/assets/CrossIcon.js +7 -0
  10. package/dist/assets/DropDown.d.ts +4 -0
  11. package/dist/assets/DropDown.js +5 -0
  12. package/dist/global.css +3213 -0
  13. package/dist/index.d.ts +25 -0
  14. package/dist/index.js +25 -0
  15. package/dist/lib/CommonComponentsUtil.d.ts +7 -0
  16. package/dist/lib/CommonComponentsUtil.js +22 -0
  17. package/dist/lib/utils.d.ts +2 -0
  18. package/dist/lib/utils.js +5 -0
  19. package/dist/ui/BaseTable.d.ts +132 -0
  20. package/dist/ui/BaseTable.js +330 -0
  21. package/dist/ui/DateCalendar.d.ts +10 -0
  22. package/dist/ui/DateCalendar.js +47 -0
  23. package/dist/ui/DateField.d.ts +35 -0
  24. package/dist/ui/DateField.js +37 -0
  25. package/dist/ui/DateOfBirthSelector.d.ts +16 -0
  26. package/dist/ui/DateOfBirthSelector.js +42 -0
  27. package/dist/ui/DateRangePicker.d.ts +8 -0
  28. package/dist/ui/DateRangePicker.js +73 -0
  29. package/dist/ui/DobCalendar.d.ts +17 -0
  30. package/dist/ui/DobCalendar.js +86 -0
  31. package/dist/ui/Formfield.d.ts +12 -0
  32. package/dist/ui/Formfield.js +36 -0
  33. package/dist/ui/GetScrollAlert.d.ts +9 -0
  34. package/dist/ui/GetScrollAlert.js +37 -0
  35. package/dist/ui/RadioGroupContext/RadioGroupContext.d.ts +8 -0
  36. package/dist/ui/RadioGroupContext/RadioGroupContext.js +11 -0
  37. package/dist/ui/SelectCommand.d.ts +69 -0
  38. package/dist/ui/SelectCommand.js +260 -0
  39. package/dist/ui/SlashIcon.d.ts +2 -0
  40. package/dist/ui/SlashIcon.js +5 -0
  41. package/dist/ui/SortingArrows.d.ts +11 -0
  42. package/dist/ui/SortingArrows.js +11 -0
  43. package/dist/ui/TextTags.d.ts +39 -0
  44. package/dist/ui/TextTags.js +73 -0
  45. package/dist/ui/accordion.d.ts +10 -0
  46. package/dist/ui/accordion.js +77 -0
  47. package/dist/ui/alert-dialog.d.ts +20 -0
  48. package/dist/ui/alert-dialog.js +62 -0
  49. package/dist/ui/alert.d.ts +8 -0
  50. package/dist/ui/alert.js +42 -0
  51. package/dist/ui/avatar.d.ts +6 -0
  52. package/dist/ui/avatar.js +32 -0
  53. package/dist/ui/badge.d.ts +10 -0
  54. package/dist/ui/badge.js +58 -0
  55. package/dist/ui/button.d.ts +12 -0
  56. package/dist/ui/button.js +54 -0
  57. package/dist/ui/calendar.d.ts +8 -0
  58. package/dist/ui/calendar.js +45 -0
  59. package/dist/ui/card.d.ts +8 -0
  60. package/dist/ui/card.js +45 -0
  61. package/dist/ui/checkbox.d.ts +12 -0
  62. package/dist/ui/checkbox.js +44 -0
  63. package/dist/ui/command.d.ts +47 -0
  64. package/dist/ui/command.js +67 -0
  65. package/dist/ui/dialog.d.ts +31 -0
  66. package/dist/ui/dialog.js +57 -0
  67. package/dist/ui/dropdown-menu.d.ts +27 -0
  68. package/dist/ui/dropdown-menu.js +72 -0
  69. package/dist/ui/form.d.ts +23 -0
  70. package/dist/ui/form.js +72 -0
  71. package/dist/ui/hover-card.d.ts +7 -0
  72. package/dist/ui/hover-card.js +29 -0
  73. package/dist/ui/input.d.ts +3 -0
  74. package/dist/ui/input.js +20 -0
  75. package/dist/ui/label.d.ts +5 -0
  76. package/dist/ui/label.js +24 -0
  77. package/dist/ui/multi-select.d.ts +44 -0
  78. package/dist/ui/multi-select.js +191 -0
  79. package/dist/ui/navigation-menu.d.ts +11 -0
  80. package/dist/ui/navigation-menu.js +69 -0
  81. package/dist/ui/popover.d.ts +6 -0
  82. package/dist/ui/popover.js +24 -0
  83. package/dist/ui/radio-group.d.ts +7 -0
  84. package/dist/ui/radio-group.js +40 -0
  85. package/dist/ui/radioButtonCard.d.ts +9 -0
  86. package/dist/ui/radioButtonCard.js +23 -0
  87. package/dist/ui/scroll-area.d.ts +5 -0
  88. package/dist/ui/scroll-area.js +29 -0
  89. package/dist/ui/select.d.ts +57 -0
  90. package/dist/ui/select.js +148 -0
  91. package/dist/ui/separator.d.ts +4 -0
  92. package/dist/ui/separator.js +22 -0
  93. package/dist/ui/sheet.d.ts +25 -0
  94. package/dist/ui/sheet.js +65 -0
  95. package/dist/ui/switch.d.ts +10 -0
  96. package/dist/ui/switch.js +47 -0
  97. package/dist/ui/table.d.ts +10 -0
  98. package/dist/ui/table.js +55 -0
  99. package/dist/ui/tabs.d.ts +7 -0
  100. package/dist/ui/tabs.js +33 -0
  101. package/dist/ui/text.d.ts +11 -0
  102. package/dist/ui/text.js +45 -0
  103. package/dist/ui/textarea.d.ts +7 -0
  104. package/dist/ui/textarea.js +21 -0
  105. package/dist/ui/tooltip.d.ts +11 -0
  106. package/dist/ui/tooltip.js +26 -0
  107. package/package.json +123 -0
  108. package/postcss.config.js +6 -0
  109. package/src/assets/Arrow.js +5 -0
  110. package/src/assets/Arrow.tsx +25 -0
  111. package/src/assets/CalenderIcon.js +5 -0
  112. package/src/assets/CalenderIcon.tsx +28 -0
  113. package/src/assets/ClearAll.js +5 -0
  114. package/src/assets/ClearAll.tsx +20 -0
  115. package/src/assets/CrossIcon.js +7 -0
  116. package/src/assets/CrossIcon.tsx +34 -0
  117. package/src/assets/DropDown.js +5 -0
  118. package/src/assets/DropDown.tsx +23 -0
  119. package/src/global.css +560 -0
  120. package/src/index.ts +34 -0
  121. package/src/lib/CommonComponentsUtil.js +22 -0
  122. package/src/lib/CommonComponentsUtil.ts +30 -0
  123. package/src/lib/utils.js +5 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/ui/BaseTable.js +330 -0
  126. package/src/ui/BaseTable.tsx +987 -0
  127. package/src/ui/DateCalendar.js +47 -0
  128. package/src/ui/DateCalendar.tsx +111 -0
  129. package/src/ui/DateField.js +37 -0
  130. package/src/ui/DateField.tsx +142 -0
  131. package/src/ui/DateOfBirthSelector.js +42 -0
  132. package/src/ui/DateOfBirthSelector.tsx +114 -0
  133. package/src/ui/DateRangePicker.js +73 -0
  134. package/src/ui/DateRangePicker.tsx +174 -0
  135. package/src/ui/DobCalendar.js +86 -0
  136. package/src/ui/DobCalendar.tsx +172 -0
  137. package/src/ui/Formfield.js +36 -0
  138. package/src/ui/Formfield.tsx +55 -0
  139. package/src/ui/GetScrollAlert.js +37 -0
  140. package/src/ui/GetScrollAlert.tsx +59 -0
  141. package/src/ui/RadioGroupContext/RadioGroupContext.js +11 -0
  142. package/src/ui/RadioGroupContext/RadioGroupContext.tsx +21 -0
  143. package/src/ui/SelectCommand.js +260 -0
  144. package/src/ui/SelectCommand.tsx +587 -0
  145. package/src/ui/SlashIcon.js +5 -0
  146. package/src/ui/SlashIcon.tsx +22 -0
  147. package/src/ui/SortingArrows.js +11 -0
  148. package/src/ui/SortingArrows.tsx +29 -0
  149. package/src/ui/TextTags.js +73 -0
  150. package/src/ui/TextTags.tsx +149 -0
  151. package/src/ui/accordion.js +77 -0
  152. package/src/ui/accordion.tsx +129 -0
  153. package/src/ui/alert-dialog.js +62 -0
  154. package/src/ui/alert-dialog.tsx +141 -0
  155. package/src/ui/alert.js +42 -0
  156. package/src/ui/alert.tsx +59 -0
  157. package/src/ui/avatar.js +32 -0
  158. package/src/ui/avatar.tsx +50 -0
  159. package/src/ui/badge.js +58 -0
  160. package/src/ui/badge.tsx +66 -0
  161. package/src/ui/button.js +54 -0
  162. package/src/ui/button.tsx +89 -0
  163. package/src/ui/calendar.js +45 -0
  164. package/src/ui/calendar.tsx +88 -0
  165. package/src/ui/card.js +45 -0
  166. package/src/ui/card.tsx +83 -0
  167. package/src/ui/checkbox.js +44 -0
  168. package/src/ui/checkbox.tsx +56 -0
  169. package/src/ui/command.js +67 -0
  170. package/src/ui/command.tsx +166 -0
  171. package/src/ui/dialog.js +57 -0
  172. package/src/ui/dialog.tsx +154 -0
  173. package/src/ui/dropdown-menu.js +72 -0
  174. package/src/ui/dropdown-menu.tsx +200 -0
  175. package/src/ui/form.js +72 -0
  176. package/src/ui/form.tsx +177 -0
  177. package/src/ui/hover-card.js +29 -0
  178. package/src/ui/hover-card.tsx +63 -0
  179. package/src/ui/input.js +20 -0
  180. package/src/ui/input.tsx +22 -0
  181. package/src/ui/label.js +24 -0
  182. package/src/ui/label.tsx +26 -0
  183. package/src/ui/multi-select.js +191 -0
  184. package/src/ui/multi-select.tsx +518 -0
  185. package/src/ui/navigation-menu.js +69 -0
  186. package/src/ui/navigation-menu.tsx +139 -0
  187. package/src/ui/popover.js +24 -0
  188. package/src/ui/popover.tsx +31 -0
  189. package/src/ui/radio-group.js +40 -0
  190. package/src/ui/radio-group.tsx +97 -0
  191. package/src/ui/radioButtonCard.js +23 -0
  192. package/src/ui/radioButtonCard.tsx +57 -0
  193. package/src/ui/scroll-area.js +29 -0
  194. package/src/ui/scroll-area.tsx +48 -0
  195. package/src/ui/select.js +148 -0
  196. package/src/ui/select.tsx +343 -0
  197. package/src/ui/separator.js +22 -0
  198. package/src/ui/separator.tsx +31 -0
  199. package/src/ui/sheet.js +65 -0
  200. package/src/ui/sheet.tsx +136 -0
  201. package/src/ui/switch.js +47 -0
  202. package/src/ui/switch.tsx +60 -0
  203. package/src/ui/table.js +55 -0
  204. package/src/ui/table.tsx +112 -0
  205. package/src/ui/tabs.js +33 -0
  206. package/src/ui/tabs.tsx +55 -0
  207. package/src/ui/text.js +45 -0
  208. package/src/ui/text.tsx +49 -0
  209. package/src/ui/textarea.js +21 -0
  210. package/src/ui/textarea.tsx +28 -0
  211. package/src/ui/tooltip.js +26 -0
  212. package/src/ui/tooltip.tsx +54 -0
  213. package/tailwind.config.js +214 -0
  214. package/tsconfig.json +35 -0
  215. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,987 @@
1
+ "use client";
2
+
3
+ import {
4
+ ColumnDef,
5
+ RowSelectionState,
6
+ SortingState,
7
+ VisibilityState,
8
+ flexRender,
9
+ getCoreRowModel,
10
+ useReactTable,
11
+ } from "@tanstack/react-table";
12
+ import {Text} from '../ui/text'
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from "../ui/table";
21
+
22
+ import ClearAll from "../assets/ClearAll";
23
+ import { ChevronLeft, ChevronRight } from "lucide-react";
24
+ import React, { useEffect, useRef, useState } from "react";
25
+ import { Button } from "../ui/button";
26
+ import { Checkbox } from "../ui/checkbox";
27
+ import {
28
+ DropdownMenu,
29
+ DropdownMenuContent,
30
+ DropdownMenuTrigger,
31
+ } from "../ui/dropdown-menu";
32
+ import {
33
+ Select,
34
+ SelectContent,
35
+ SelectItem,
36
+ SelectTrigger,
37
+ SelectValue,
38
+ } from "../ui/select";
39
+
40
+ import DropDown from "../assets/DropDown"
41
+ import { useTranslation } from "next-i18next";
42
+ import { debounce } from "lodash";
43
+ interface IBaseTable<TData, TValue> {
44
+ /**
45
+ * Columns defined for the table
46
+ */
47
+ columns: ColumnDef<TData, TValue>[];
48
+
49
+ /**
50
+ * Array of data objects to be displayed in the table
51
+ */
52
+ data: TData[];
53
+
54
+ /**
55
+ * Additional CSS classes to apply to the table
56
+ */
57
+ tableStyles?: {
58
+ /**
59
+ * Additional CSS classes to pass to the table container
60
+ */
61
+ table?: string;
62
+ /**
63
+ * Additional CSS classes to pass to each row
64
+ */
65
+ rowStyles?: string;
66
+ /**
67
+ * Additional CSS classes to pass to table container
68
+ */
69
+ tableContainer?: string;
70
+ /**
71
+ * Additional CSS classes to pass to table header
72
+ */
73
+ tableHeader?: string;
74
+ /**
75
+ * Additional Css classes to pass to each cell in the table header
76
+ */
77
+ tableHeaderCell?: string;
78
+ /**
79
+ * Additional Css classes to pass to each cell in the table body
80
+ */
81
+ tableBodyCell?: string;
82
+ };
83
+ /**
84
+ * When there are no results then we have to show this placeholder
85
+ */
86
+ noRecordsPlaceholder?: string;
87
+ /**
88
+
89
+ /**
90
+ * Function to update the current page number
91
+ */
92
+ setCurrent?: (value: React.SetStateAction<number>) => void;
93
+
94
+ /**
95
+ * The current page
96
+ */
97
+ current?: number;
98
+
99
+ /**
100
+ * Total number of pages
101
+ */
102
+ pageCount?: number;
103
+
104
+ /**
105
+ * Function to update the page size
106
+ */
107
+ setPageSize?: (value: React.SetStateAction<number>) => void;
108
+
109
+ /**
110
+ * Number of items to display per page
111
+ */
112
+ pageSize?: number;
113
+
114
+ /**
115
+ * Total number of items in the dataset
116
+ */
117
+ total?: number;
118
+
119
+ /**
120
+ * Flag to indicate whether pagination controls should be displayed
121
+ */
122
+ pagination?: boolean;
123
+
124
+ /**
125
+ * Flag to indicate whether checkboxes should be displayed
126
+ */
127
+ checkboxSelection?: boolean;
128
+
129
+ /**
130
+ * Flag to indicate whether sticky coulmns should be displayed
131
+ */
132
+ columnPinning?: boolean;
133
+
134
+ /**
135
+ * It is used to send the default columns to be selected
136
+ */
137
+ defaultColumns?: string[];
138
+
139
+ /**
140
+ * Row selection state
141
+ */
142
+ rowSelection?: RowSelectionState;
143
+
144
+ /**
145
+ * Function to update the row selection state to track the selected rows
146
+ */
147
+ setRowSelection?: (value: React.SetStateAction<RowSelectionState>) => void;
148
+ /**
149
+ * Flag to indicate whether the column selector need to be displayed or not
150
+ */
151
+ columnSelector?: boolean;
152
+
153
+ setSorting?: any;
154
+
155
+ sorting?: SortingState;
156
+
157
+ /**
158
+ * This variable is used to display the loader in middle of the page and while filtering ideally we dont need to click anything
159
+ */
160
+ isFiltering?: boolean;
161
+ /**
162
+ * This variable is used to disabled the horizontal scrolling.
163
+ */
164
+ noScroll?: boolean;
165
+
166
+ /**
167
+ * This variable is used pass action component if any. E.g: Bulk Actions in participant listing home page.
168
+ */
169
+ actionComponent?: React.ReactNode;
170
+
171
+ /**
172
+ * This variable is used to pass the columns preferences selected by the logged in user
173
+ */
174
+ userColumnPreferences?: { [key: string]: boolean } | null;
175
+
176
+ tableContainerId?: string;
177
+
178
+ /**
179
+ * Function to update the columns selected and applied by the user
180
+ */
181
+ handleUserColumnPreferences?: (
182
+ userColumnPreferences: { [key: string]: boolean } | null
183
+ ) => void;
184
+
185
+ /**
186
+ * This is used to export the table for export excel and csv functionality
187
+ */
188
+ tableId?: string;
189
+ }
190
+
191
+ export function BaseTable<TData, TValue>({
192
+ columns,
193
+ data,
194
+ tableStyles,
195
+ current,
196
+ setCurrent = () => {},
197
+ pageCount,
198
+ total = 0,
199
+ setPageSize = () => {},
200
+ pageSize = 0,
201
+ pagination = false,
202
+ checkboxSelection,
203
+ columnPinning = false,
204
+ defaultColumns = [],
205
+ rowSelection,
206
+ setRowSelection,
207
+ columnSelector,
208
+ sorting,
209
+ setSorting,
210
+ noRecordsPlaceholder = "No results",
211
+ isFiltering = false,
212
+ noScroll,
213
+ actionComponent,
214
+ userColumnPreferences,
215
+ tableContainerId = "base-table-container",
216
+ handleUserColumnPreferences,
217
+ tableId = "",
218
+ }: IBaseTable<TData, TValue>) {
219
+ // Initial visibility state for column selector
220
+ const initialColumnVisibilityChanges: { [key: string]: boolean } =
221
+ columns.reduce(
222
+ (acc: Record<string, boolean>, { accessorKey, enableHiding }: any) => {
223
+ if (accessorKey) {
224
+ // Determine initial visibility for the current column
225
+ acc[accessorKey] =
226
+ // If the defaultColumns array is empty or includes the current column's accessorKey, set visibility to true
227
+ defaultColumns.length === 0 || defaultColumns.includes(accessorKey)
228
+ ? true
229
+ : // If enableHiding is true for the current column, set visibility to false, otherwise set it to true
230
+ enableHiding
231
+ ? false
232
+ : true;
233
+ }
234
+ return acc;
235
+ },
236
+ {}
237
+ );
238
+
239
+ const [columnVisibility, setColumnVisibility] =
240
+ React.useState<VisibilityState>(
241
+ userColumnPreferences
242
+ ? userColumnPreferences
243
+ : initialColumnVisibilityChanges
244
+ );
245
+
246
+ //Local state for column selector to apply chnages when we click on apply button
247
+ const [columnVisibilityChanges, setColumnVisibilityChanges] =
248
+ useState<VisibilityState>(
249
+ userColumnPreferences
250
+ ? userColumnPreferences
251
+ : initialColumnVisibilityChanges
252
+ );
253
+
254
+ // whenever userColumnPreferences get changed we need to update the values of those columns
255
+ useEffect(() => {
256
+ if (userColumnPreferences) {
257
+ setColumnVisibility(userColumnPreferences as VisibilityState);
258
+ setColumnVisibilityChanges(userColumnPreferences as VisibilityState);
259
+ }
260
+ }, [userColumnPreferences]);
261
+
262
+ //initial state for select all checkbox
263
+ const initialSelectAll =
264
+ Object.keys(columnVisibilityChanges).length > 0 &&
265
+ Object.values(columnVisibilityChanges).every(Boolean);
266
+
267
+ const [selectAll, setSelectAll] = useState(initialSelectAll);
268
+
269
+ /**
270
+ * @function getRowId
271
+ * @description this function return id if the row have the id else it will return the index as id
272
+ * @param originalRow
273
+ * @param index
274
+ * @returns index in string format
275
+ */
276
+ const getRowId = (originalRow: any, index: number) => {
277
+ if (checkboxSelection) {
278
+ return originalRow.id.toString();
279
+ } else {
280
+ return index.toString();
281
+ }
282
+ };
283
+ // table hook
284
+ const table = useReactTable({
285
+ data,
286
+ columns: columns,
287
+ enableSortingRemoval: true,
288
+ sortDescFirst: false,
289
+ getCoreRowModel: getCoreRowModel(),
290
+ // getPaginationRowModel: getPaginationRowModel(),
291
+ manualPagination: true,
292
+ onColumnVisibilityChange: setColumnVisibility,
293
+ onRowSelectionChange: setRowSelection,
294
+ getRowId,
295
+ manualSorting: true,
296
+ state: {
297
+ columnVisibility,
298
+ rowSelection,
299
+ sorting,
300
+ },
301
+ onSortingChange: setSorting,
302
+ });
303
+
304
+ /**
305
+ * Function to handle the select all checkbox changes
306
+ */
307
+ const handleSelectAllChange = (checked: boolean) => {
308
+ setSelectAll(checked);
309
+
310
+ const newColumnVisibilityChanges: VisibilityState = {};
311
+
312
+ //Logic for not uncheck the columns which cannot be hidden they should be always true
313
+ Object.keys(columnVisibilityChanges).forEach((columnId) => {
314
+ const column = columns?.find(
315
+ (column: any) => column.accessorKey === columnId
316
+ );
317
+ const canHide = column?.enableHiding;
318
+ newColumnVisibilityChanges[columnId] = canHide === false ? true : checked;
319
+ });
320
+
321
+ setColumnVisibilityChanges(newColumnVisibilityChanges);
322
+ };
323
+
324
+ /**
325
+ * function to handle the columns in column selector
326
+ */
327
+ const handleColumnVisibilityChange = (
328
+ columnId: string,
329
+ isVisible: boolean
330
+ ) => {
331
+ setColumnVisibilityChanges((prevState) => ({
332
+ ...prevState,
333
+ [columnId]: isVisible,
334
+ }));
335
+
336
+ // when i uncheck the individual check box we need to see if all checkboxes or checked or not and we have to update the select all
337
+ const allChecked = Object.values({
338
+ ...columnVisibilityChanges,
339
+ [columnId]: isVisible,
340
+ }).every(Boolean);
341
+ setSelectAll(allChecked);
342
+ };
343
+
344
+ /**
345
+ * function to handle the columns in column selector
346
+ */
347
+ const applyColumnVisibilityChanges = () => {
348
+ table.setColumnVisibility(columnVisibilityChanges);
349
+ handleUserColumnPreferences?.(columnVisibilityChanges);
350
+ setOpen(false);
351
+
352
+ requestAnimationFrame(() => {
353
+ handleScroll(); // Recalculate scroll position after layout change
354
+ });
355
+ };
356
+
357
+ /**
358
+ * functions to clear the columns in column selector
359
+ */
360
+ const clearColumnVisibilityChanges = () => {
361
+ const finalColumnVisibilityChanges = columns.reduce(
362
+ (acc: Record<string, boolean>, column: any) => {
363
+ //When clearing we need to make sure that columns which are not been hidden need to be true always
364
+ if (column.accessorKey) {
365
+ if (column.enableHiding == false) {
366
+ acc[column.accessorKey] = true;
367
+ } else {
368
+ acc[column.accessorKey] = false;
369
+ }
370
+ }
371
+ return acc;
372
+ },
373
+ {}
374
+ );
375
+
376
+ setColumnVisibilityChanges(finalColumnVisibilityChanges);
377
+ setColumnVisibility(finalColumnVisibilityChanges);
378
+ handleUserColumnPreferences?.(finalColumnVisibilityChanges);
379
+ setSelectAll(false);
380
+
381
+ requestAnimationFrame(() => {
382
+ handleScroll(); // Recalculate scroll position after layout change
383
+ });
384
+ };
385
+
386
+ const [scrollLeft, setScrollLeft] = useState(0);
387
+ const tableRef = useRef<HTMLDivElement>(null);
388
+
389
+ /**
390
+ * function to move the scroll bar to left using controls in action
391
+ */
392
+ const handlePrevButtonClick = () => {
393
+ if (tableRef.current) {
394
+ tableRef.current.scrollLeft -= 250;
395
+ setScrollLeft(tableRef.current.scrollLeft);
396
+ }
397
+ };
398
+
399
+ /**
400
+ * function to move the scroll bar to right using controls in action
401
+ */
402
+ const handleNextButtonClick = () => {
403
+ if (tableRef.current) {
404
+ tableRef.current.scrollLeft += 250;
405
+ setScrollLeft(
406
+ tableRef.current.scrollWidth - tableRef.current.clientWidth
407
+ );
408
+ }
409
+ };
410
+
411
+ //state variable to control the opening and closing of the column selector
412
+ const [open, setOpen] = useState(false);
413
+ const { t } = useTranslation(["common", "course.find_course", "bx_v1"]);
414
+
415
+ /**
416
+ * This function will set the drop down to open or close
417
+ * ColumnVisibilityChange is the columns selected in the dropdown
418
+ * While triggering the dropdown we are updated the ColumnVisibilityChange with ColumnVisibility
419
+ * Where ColumnVisibility is the columns selected after the changes are applied
420
+ */
421
+ const handleColumnDropdownChange = () => {
422
+ setOpen(!open);
423
+ setColumnVisibilityChanges({ ...columnVisibility });
424
+ //If all checkboxes are checked or not and Based on that we have to update the select all
425
+ const allChecked = Object.values({
426
+ ...columnVisibility,
427
+ }).every(Boolean);
428
+
429
+ setSelectAll(allChecked);
430
+ };
431
+
432
+ /**
433
+ *This component manages the scroll state of a scrollable element (referred to as `tableRef`).
434
+ * It tracks whether the element is scrolled to the start (leftmost position) or the end (rightmost position).
435
+ * The `isAtStart` state is true when the scroll position is at the left edge of the element,
436
+ * and the `isAtEnd` state is true when the scroll position is at or beyond the right edge of the element.
437
+ */
438
+ const [isAtStart, setIsAtStart] = useState(true);
439
+ const [isAtEnd, setIsAtEnd] = useState(false);
440
+
441
+ const handleScroll = () => {
442
+ if (tableRef.current) {
443
+ const { scrollLeft, clientWidth, scrollWidth } = tableRef.current;
444
+
445
+ const roundedScrollLeft = Math.round(scrollLeft);
446
+ const roundedClientWidth = Math.round(clientWidth);
447
+ const roundedScrollWidth = Math.round(scrollWidth);
448
+
449
+ // Add tolerance for floating-point inaccuracies
450
+ const tolerance = 1;
451
+
452
+ setIsAtStart(roundedScrollLeft <= tolerance);
453
+ setIsAtEnd(
454
+ roundedScrollLeft + roundedClientWidth >= roundedScrollWidth - tolerance
455
+ );
456
+ }
457
+ };
458
+
459
+ const handleResize = debounce(() => {
460
+ handleScroll();
461
+ }, 100);
462
+
463
+ useEffect(() => {
464
+ const tableElement = tableRef.current;
465
+
466
+ if (tableElement) {
467
+ tableElement.addEventListener("scroll", handleScroll, { passive: true });
468
+ window.addEventListener("resize", handleResize);
469
+
470
+ handleScroll(); // Initial scroll state check
471
+ }
472
+
473
+ if (isFiltering && tableRef.current) {
474
+ tableRef.current.scrollLeft = 0;
475
+ }
476
+
477
+ return () => {
478
+ if (tableElement) {
479
+ tableElement.removeEventListener("scroll", handleScroll);
480
+ }
481
+ window.removeEventListener("resize", handleResize);
482
+ };
483
+ }, [tableRef.current, isFiltering]);
484
+
485
+ return (
486
+ <div className="flex flex-col gap-4">
487
+ <div className="flex max-h-[50px] flex-row items-center justify-between">
488
+ <div className="flex flex-row items-center gap-4">
489
+ {columnSelector && (
490
+ <div>
491
+ <DropdownMenu
492
+ open={open}
493
+ onOpenChange={handleColumnDropdownChange}
494
+ >
495
+ <DropdownMenuTrigger asChild>
496
+ <Button
497
+ onClick={() => setOpen(true)}
498
+ variant="outline"
499
+ className="flex h-10 w-[192px] flex-row justify-between rounded-xl hover:border hover:border-solid hover:border-primary"
500
+ id="base-table-column-selector-button"
501
+ >
502
+ {t("course.find_course:columns")}
503
+ <DropDown />
504
+ </Button>
505
+ </DropdownMenuTrigger>
506
+ <DropdownMenuContent
507
+ className="w-[192px] rounded-xl pl-3 pt-2.5"
508
+ align="start"
509
+ >
510
+ <div>
511
+ <div className="scrollbar column-selector-responsive-container flex flex-col gap-4 overflow-y-auto text-grey">
512
+ <div className="flex flex-row items-center gap-4">
513
+ <Checkbox
514
+ checked={selectAll}
515
+ onCheckedChange={handleSelectAllChange}
516
+ id="base-table-column-selector-select-all-checkbox"
517
+ />
518
+ <Text className="text-sm font-bold">
519
+ {t("course.find_course:select_all")}
520
+ </Text>
521
+ </div>
522
+ {table
523
+ .getAllColumns()
524
+ .filter((column) => column?.accessorFn)
525
+ // Here we are filtering the columns which have accessorKey
526
+ .map((column: any, index: number) => {
527
+ if (!column.getCanHide()) {
528
+ //display the disabled options
529
+ return (
530
+ <div
531
+ className="flex flex-row items-center gap-4"
532
+ key={index}
533
+ >
534
+ <Checkbox
535
+ key={column.id}
536
+ disabled={!column.getCanHide()}
537
+ //Disabling the checkbox if the column cannot be hidden
538
+ checked={columnVisibilityChanges[column.id]}
539
+ onCheckedChange={(value: boolean) => {
540
+ handleColumnVisibilityChange(
541
+ column.id,
542
+ value
543
+ );
544
+ }}
545
+ id={`column-${column?.columnDef?.column_name}`}
546
+ />
547
+ {column?.columnDef?.column_name}
548
+ </div>
549
+ );
550
+ }
551
+ })}
552
+ {table
553
+ .getAllColumns()
554
+ .filter(
555
+ (column) => column?.accessorFn && column.getCanHide()
556
+ )
557
+ // Here we are filtering the columns which have accessorKey
558
+ .map((column: any) => {
559
+ // display the enabled options
560
+ return (
561
+ <div className="flex flex-row items-center gap-4">
562
+ <Checkbox
563
+ key={column.id}
564
+ checked={columnVisibilityChanges[column.id]}
565
+ onCheckedChange={(value: boolean) => {
566
+ handleColumnVisibilityChange(
567
+ column.id,
568
+ value
569
+ );
570
+ }}
571
+ />
572
+ {column?.columnDef?.column_name}
573
+ </div>
574
+ );
575
+ })}
576
+ </div>
577
+
578
+ <div className="thin-scrollbar relative flex w-full flex-row items-center gap-4 overflow-auto pb-2.5 pt-2">
579
+ <div
580
+ onClick={clearColumnVisibilityChanges}
581
+ className="cursor-pointer rounded-xl border border-primary p-2 hover:border-solid"
582
+ >
583
+ <ClearAll />
584
+ </div>
585
+ <Button
586
+ size="sm"
587
+ variant="primary"
588
+ onClick={applyColumnVisibilityChanges}
589
+ id="base-table-column-selector-apply-button"
590
+ >
591
+ {t("apply_button")}
592
+ </Button>
593
+ </div>
594
+ </div>
595
+ </DropdownMenuContent>
596
+ </DropdownMenu>
597
+ </div>
598
+ )}
599
+ {/* column selector */}
600
+
601
+ {actionComponent && actionComponent}
602
+ </div>
603
+ {/* If pagination set true then we have to show pagination */}
604
+ <div>
605
+ {!isFiltering && pagination && total > pageSize && (
606
+ <DataPagination
607
+ setCurrent={setCurrent}
608
+ current={current}
609
+ pageCount={pageCount}
610
+ total={total}
611
+ />
612
+ )}
613
+ </div>
614
+ </div>
615
+
616
+ {/* Table */}
617
+ <div>
618
+ <div className="h-full overflow-hidden rounded-xl border">
619
+ <div
620
+ ref={tableRef}
621
+ className={`w-full ${tableStyles?.tableContainer} scrollbar ${
622
+ isFiltering
623
+ ? "overflow-x-hidden"
624
+ : "overflow-x-auto overflow-y-hidden"
625
+ } relative overflow-x-auto overflow-y-hidden`}
626
+ >
627
+ <Table id={tableId} className={`${tableStyles?.table}`}>
628
+ <TableHeader
629
+ className={`w-full bg-primary-light ${tableStyles?.tableHeader}`}
630
+ >
631
+ {table &&
632
+ table?.getHeaderGroups()?.map((headerGroup) => (
633
+ <TableRow
634
+ className="w-full border-none text-base font-bold"
635
+ key={headerGroup?.id}
636
+ >
637
+ {/* If the checkboxSelection is true then we need to show checkboxes */}
638
+ {checkboxSelection && (
639
+ <TableHead
640
+ className={`${
641
+ columnPinning && "sticky left-0 bg-primary-light"
642
+ }`}
643
+ >
644
+ <Checkbox
645
+ checked={table.getIsAllPageRowsSelected()}
646
+ onCheckedChange={(value: boolean) => {
647
+ table.toggleAllPageRowsSelected(value);
648
+ }}
649
+ aria-label="Select all"
650
+ id="base-table-select-all-checkbox"
651
+ />
652
+ </TableHead>
653
+ )}
654
+ {headerGroup?.headers?.map((header, index) => {
655
+ return (
656
+ <TableHead
657
+ //If we have column pinning true then we have to make the first and last column sticky
658
+ className={`${
659
+ columnPinning &&
660
+ index === 0 &&
661
+ `sticky ${
662
+ checkboxSelection ? "left-12" : "left-0"
663
+ } bg-primary-light drop-shadow-right`
664
+ } ${
665
+ !noScroll &&
666
+ columnPinning &&
667
+ index === headerGroup.headers.length - 1 &&
668
+ `sticky right-0 w-[50px] bg-primary-light drop-shadow-left`
669
+ } ${tableStyles?.tableHeaderCell ? tableStyles?.tableHeaderCell : ""} text-grey`}
670
+ key={header?.id}
671
+ >
672
+ {header?.isPlaceholder
673
+ ? null
674
+ : flexRender(
675
+ header?.column?.columnDef?.header,
676
+ header?.getContext()
677
+ )}
678
+
679
+ {!noScroll &&
680
+ index === headerGroup.headers.length - 1 &&
681
+ columnPinning && (
682
+ <div className="flex flex-row gap-2">
683
+ <ChevronLeft
684
+ onClick={handlePrevButtonClick}
685
+ className={`mr-1 size-6 cursor-pointer rounded-full ${
686
+ isAtStart
687
+ ? "bg-white text-primary"
688
+ : "bg-primary text-white"
689
+ }`}
690
+ />
691
+ <ChevronRight
692
+ onClick={handleNextButtonClick}
693
+ className={`size-6 cursor-pointer rounded-full ${
694
+ isAtEnd
695
+ ? "bg-white text-primary"
696
+ : "bg-primary text-white"
697
+ }`}
698
+ id="base-table-columns-right-button"
699
+ />
700
+ </div>
701
+ )}
702
+ </TableHead>
703
+ );
704
+ })}
705
+ </TableRow>
706
+ ))}
707
+ </TableHeader>
708
+ <TableBody>
709
+ {isFiltering ? (
710
+ <TableRow>
711
+ <TableCell
712
+ colSpan={columns?.length}
713
+ className="h-24 text-center"
714
+ >
715
+ <div className="flex w-screen items-center justify-center">
716
+ <div className="loader"></div>
717
+ </div>
718
+ </TableCell>
719
+ </TableRow>
720
+ ) : table && table?.getRowModel()?.rows?.length ? (
721
+ <>
722
+ {table?.getRowModel()?.rows?.map((row) => (
723
+ <TableRow
724
+ className={`{${tableStyles?.rowStyles}`}
725
+ key={row?.id}
726
+ // data-state={row?.getIsSelected() && "selected"}
727
+ >
728
+ {/* If the checkboxSelection is true then we need to show checkboxes */}
729
+ {checkboxSelection && (
730
+ <TableCell
731
+ className={`${
732
+ columnPinning && "sticky left-0 bg-white"
733
+ }`}
734
+ >
735
+ <Checkbox
736
+ checked={row?.getIsSelected()}
737
+ onCheckedChange={(value) =>
738
+ row?.toggleSelected(!!value)
739
+ }
740
+ aria-label="Select row"
741
+ />
742
+ </TableCell>
743
+ )}
744
+
745
+ {row?.getVisibleCells().map((cell, index) => (
746
+ //If we have column pinning true then we have to make the first and last column sticky
747
+ <TableCell
748
+ className={` ${
749
+ columnPinning &&
750
+ index === 0 &&
751
+ `sticky ${
752
+ checkboxSelection ? "left-12" : "left-0"
753
+ } top-0 bg-white drop-shadow-right`
754
+ } ${
755
+ !noScroll &&
756
+ columnPinning &&
757
+ index === row.getVisibleCells().length - 1 &&
758
+ `sticky right-0 top-0 w-[50px] bg-white drop-shadow-left`
759
+ } ${tableStyles?.tableBodyCell ? tableStyles?.tableBodyCell : ""} text-grey`}
760
+ key={cell.id}
761
+ >
762
+ {flexRender(
763
+ cell?.column?.columnDef?.cell,
764
+ cell?.getContext()
765
+ )}
766
+ </TableCell>
767
+ ))}
768
+ </TableRow>
769
+ ))}
770
+ {/* Render Footer Rows within the TableBody */}
771
+ {table
772
+ ?.getFooterGroups()
773
+ ?.some((group) =>
774
+ group.headers.some(
775
+ (header) => header.column.columnDef.footer
776
+ )
777
+ ) &&
778
+ table?.getFooterGroups()?.map((row) => (
779
+ <TableRow key={row.id} className="bg-primary-light">
780
+ {row?.headers?.map((cell, index) => (
781
+ <TableCell
782
+ key={cell.id}
783
+ className={`${
784
+ columnPinning && index === 0
785
+ ? "sticky left-0 bg-primary-light drop-shadow-right"
786
+ : ""
787
+ } ${
788
+ !noScroll &&
789
+ columnPinning &&
790
+ index === row.headers.length - 1
791
+ ? "sticky right-0 bg-white drop-shadow-left"
792
+ : ""
793
+ } ${tableStyles?.tableBodyCell ? tableStyles?.tableBodyCell : ""} text-grey`}
794
+ >
795
+ {flexRender(
796
+ cell?.column?.columnDef?.footer,
797
+ cell?.getContext()
798
+ )}
799
+ </TableCell>
800
+ ))}
801
+ </TableRow>
802
+ ))}
803
+ </>
804
+ ) : (
805
+ <TableRow>
806
+ <TableCell
807
+ colSpan={columns?.length}
808
+ className="h-24 text-left"
809
+ >
810
+ {noRecordsPlaceholder}
811
+ </TableCell>
812
+ </TableRow>
813
+ )}
814
+ </TableBody>
815
+ </Table>
816
+ </div>
817
+ </div>
818
+ {!isFiltering && pagination && (
819
+ <div className="my-6 flex justify-center">
820
+ <div className="w-1/3"></div>
821
+ <div className="flex w-1/3 items-center justify-center">
822
+ {/* When there is more than 1 page then only we need to render this */}
823
+ {total > pageSize && (
824
+ <DataPagination
825
+ setCurrent={setCurrent}
826
+ current={current}
827
+ pageCount={pageCount}
828
+ total={total}
829
+ pageSize={pageSize}
830
+ />
831
+ )}
832
+ </div>
833
+ {total >= 10 && (
834
+ <div
835
+ className="flex w-1/3 items-center justify-end space-x-2"
836
+ id="base-table-pagination-dropdown"
837
+ >
838
+ <Select
839
+ value={pageSize}
840
+ onValueChange={(value) => {
841
+ setCurrent(1);
842
+ setPageSize(Number(value));
843
+ table?.setPageSize(Number(value));
844
+ }}
845
+ >
846
+ <SelectTrigger
847
+ className="h-8 w-[131px]"
848
+ id="base-table-page-size"
849
+ >
850
+ <Text className="text-grey1">
851
+ {t("course.find_course:showing")}
852
+ </Text>
853
+ <SelectValue />
854
+ </SelectTrigger>
855
+ <SelectContent side="top">
856
+ {/* Updated pageSize options to include [10, 25, 50, 100]. */}
857
+ {[10, 25, 50, 100].map(
858
+ (
859
+ pageSize // Till now there is no limit will change after confirming TODO
860
+ ) => (
861
+ <SelectItem
862
+ key={pageSize}
863
+ value={`${pageSize}`}
864
+ id={`base-table-${pageSize}`}
865
+ >
866
+ {pageSize}
867
+ </SelectItem>
868
+ )
869
+ )}
870
+ </SelectContent>
871
+ </Select>
872
+ <Text className="text-sm font-normal">
873
+ {t("course.find_course:of")} {total}
874
+ </Text>
875
+ </div>
876
+ )}
877
+ </div>
878
+ )}
879
+ </div>
880
+ </div>
881
+ );
882
+ }
883
+
884
+ interface DataPaginationProps {
885
+ setCurrent?: (value: React.SetStateAction<number>) => void;
886
+ current?: number;
887
+ pageCount?: number;
888
+ total?: number;
889
+ pageSize?: number;
890
+ }
891
+
892
+ const DataPagination = ({
893
+ setCurrent = () => {},
894
+ total = 0,
895
+ current = 1,
896
+ pageCount = 1,
897
+ pageSize = 0,
898
+ }: DataPaginationProps) => {
899
+ const PagesArray = [];
900
+ const DOTS = ". . .";
901
+ if (pageCount <= 4) {
902
+ // If there are 4 or fewer pages, show all pages without ellipses
903
+ for (let i = 1; i <= pageCount; i++) {
904
+ PagesArray.push(i);
905
+ }
906
+ } else {
907
+ if (current <= 3) {
908
+ // If current page is 4 or less, show pages 1 to 4, then ellipses, then last page
909
+ PagesArray.push(1, 2, 3, 4, DOTS, pageCount);
910
+ } else if (current >= pageCount - 2) {
911
+ // If current page is near the end, show first page, ellipses, and last 4 pages
912
+ PagesArray.push(
913
+ 1,
914
+ DOTS,
915
+ pageCount - 3,
916
+ pageCount - 2,
917
+ pageCount - 1,
918
+ pageCount
919
+ );
920
+ } else {
921
+ // Otherwise,first page , ellipses, current page, ellipses, and last page
922
+ PagesArray.push(
923
+ 1,
924
+ DOTS,
925
+ current - 1,
926
+ current,
927
+ current + 1,
928
+ DOTS,
929
+ pageCount
930
+ );
931
+ }
932
+ }
933
+
934
+ const { t } = useTranslation(["common", "bx_v1"]);
935
+
936
+ return (
937
+ <div className="flex flex-row items-center space-x-2 self-center p-2 text-xs">
938
+ {/* prev button */}
939
+ {/* Check if there are more than one page, and if so, display a button for navigating to the previous page. */}
940
+ {pageCount > 1 && (
941
+ <Button
942
+ variant="outline"
943
+ className={`h-8 min-w-8 rounded-sm border-none p-0 text-xs !font-semibold ${current <= 1 ? "text-grey2-light-active" : " "}`}
944
+ onClick={() => setCurrent(current - 1)}
945
+ disabled={current <= 1}
946
+ id="base-table-pagination-prev-button"
947
+ >
948
+ {t("bx_v1:cm_prev")}
949
+ </Button>
950
+ )}
951
+ {/* pages buttons */}
952
+ {total > pageSize &&
953
+ PagesArray.map((page: any, index: any) => (
954
+ <div key={index}>
955
+ {/* Check if the current page is a placeholder for ellipsis.If yes, display the ellipsis.Otherwise, display a button for the page. */}
956
+ {page === DOTS ? (
957
+ <span className="p-2 text-xs">{DOTS}</span>
958
+ ) : (
959
+ <Button
960
+ variant={page === current ? "primary" : "outline"}
961
+ onClick={() => {
962
+ setCurrent(page);
963
+ }}
964
+ className="size-8 rounded-lg p-3 text-xs"
965
+ id={`base-table-pagination-page-${page}-button`}
966
+ >
967
+ {page}
968
+ </Button>
969
+ )}
970
+ </div>
971
+ ))}
972
+ {/* next button */}
973
+ {/* Check if there are more than one page, and if so, display a button for navigating to the next page. */}
974
+ {pageCount > 1 && (
975
+ <Button
976
+ variant="outline"
977
+ className={`h-8 min-w-8 rounded-sm border-none p-0 text-xs !font-semibold ${current >= pageCount ? "text-grey2-light-active" : " "}`}
978
+ onClick={() => setCurrent(current + 1)}
979
+ disabled={current >= pageCount}
980
+ id="base-table-pagination-next-button"
981
+ >
982
+ {t("next")}
983
+ </Button>
984
+ )}
985
+ </div>
986
+ );
987
+ };