@bsol-oss/react-datatable5 12.0.0-beta.8 → 12.0.0-beta.80

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 (127) hide show
  1. package/README.md +224 -5
  2. package/dist/index.d.ts +606 -96
  3. package/dist/index.js +2679 -610
  4. package/dist/index.mjs +2683 -619
  5. package/dist/types/components/DataTable/DataTable.d.ts +12 -7
  6. package/dist/types/components/DataTable/DataTableServer.d.ts +6 -4
  7. package/dist/types/components/DataTable/DefaultTable.d.ts +12 -14
  8. package/dist/types/components/DataTable/DefaultTableServer.d.ts +23 -0
  9. package/dist/types/components/DataTable/context/DataTableContext.d.ts +21 -3
  10. package/dist/types/components/DataTable/context/useDataTableContext.d.ts +2 -2
  11. package/dist/types/components/DataTable/controls/ReloadButton.d.ts +1 -2
  12. package/dist/types/components/DataTable/controls/ResetFilteringButton.d.ts +1 -4
  13. package/dist/types/components/DataTable/controls/ResetSelectionButton.d.ts +1 -4
  14. package/dist/types/components/DataTable/controls/ResetSortingButton.d.ts +1 -4
  15. package/dist/types/components/DataTable/controls/TableControls.d.ts +10 -2
  16. package/dist/types/components/DataTable/display/Table.d.ts +3 -3
  17. package/dist/types/components/DataTable/display/TableBody.d.ts +1 -2
  18. package/dist/types/components/DataTable/display/TableBodySkeleton.d.ts +5 -0
  19. package/dist/types/components/DataTable/display/TableCardContainer.d.ts +6 -3
  20. package/dist/types/components/DataTable/display/TableDataDisplay.d.ts +6 -1
  21. package/dist/types/components/DataTable/display/TableFooter.d.ts +1 -5
  22. package/dist/types/components/DataTable/display/TableHeader.d.ts +46 -8
  23. package/dist/types/components/DataTable/useDataTableServer.d.ts +55 -3
  24. package/dist/types/components/DatePicker/DatePicker.d.ts +23 -0
  25. package/dist/types/components/DatePicker/DateTimePicker.d.ts +11 -0
  26. package/dist/types/components/DatePicker/DurationPicker.d.ts +12 -0
  27. package/dist/types/components/DatePicker/IsoTimePicker.d.ts +16 -0
  28. package/dist/types/components/DatePicker/PickerDemo.d.ts +1 -0
  29. package/dist/types/components/DatePicker/UniversalPicker.d.ts +9 -0
  30. package/dist/types/components/DatePicker/index.d.ts +7 -0
  31. package/dist/types/components/Filter/TagFilter.d.ts +5 -1
  32. package/dist/types/components/Form/SchemaFormContext.d.ts +22 -6
  33. package/dist/types/components/Form/components/FileDropzone.d.ts +2 -2
  34. package/dist/types/components/Form/components/core/DefaultForm.d.ts +1 -0
  35. package/dist/types/components/Form/components/core/FormBody.d.ts +2 -1
  36. package/dist/types/components/Form/components/core/FormRoot.d.ts +21 -8
  37. package/dist/types/components/Form/components/fields/BooleanPicker.d.ts +1 -1
  38. package/dist/types/components/Form/components/fields/ColumnRenderer.d.ts +3 -2
  39. package/dist/types/components/Form/components/fields/CustomInput.d.ts +8 -0
  40. package/dist/types/components/Form/components/fields/DatePicker.d.ts +2 -7
  41. package/dist/types/components/Form/components/fields/DateRangePicker.d.ts +2 -0
  42. package/dist/types/components/Form/components/fields/DateTimePicker.d.ts +2 -0
  43. package/dist/types/components/Form/components/fields/EnumPicker.d.ts +3 -2
  44. package/dist/types/components/Form/components/fields/FilePicker.d.ts +2 -5
  45. package/dist/types/components/Form/components/fields/IdPicker.d.ts +1 -1
  46. package/dist/types/components/Form/components/fields/NumberInputField.d.ts +1 -1
  47. package/dist/types/components/Form/components/fields/ObjectInput.d.ts +1 -1
  48. package/dist/types/components/Form/components/fields/RecordInput.d.ts +1 -1
  49. package/dist/types/components/Form/components/fields/SchemaRenderer.d.ts +1 -1
  50. package/dist/types/components/Form/components/fields/StringInputField.d.ts +19 -5
  51. package/dist/types/components/Form/components/fields/TextAreaInput.d.ts +12 -0
  52. package/dist/types/components/Form/components/{DatePicker.d.ts → fields/TimePicker.d.ts} +2 -2
  53. package/dist/types/components/Form/components/fields/types.d.ts +6 -0
  54. package/dist/types/components/Form/components/types/CustomJSONSchema7.d.ts +77 -4
  55. package/dist/types/components/Form/components/viewers/CustomViewer.d.ts +8 -0
  56. package/dist/types/components/Form/components/viewers/DateTimeViewer.d.ts +7 -0
  57. package/dist/types/components/Form/components/viewers/NumberViewer.d.ts +1 -1
  58. package/dist/types/components/Form/components/viewers/TextAreaViewer.d.ts +12 -0
  59. package/dist/types/components/Form/components/viewers/TimeViewer.d.ts +7 -0
  60. package/dist/types/components/Form/useForm.d.ts +6 -3
  61. package/dist/types/components/Form/utils/ajvResolver.d.ts +13 -0
  62. package/dist/types/components/Form/utils/buildErrorMessages.d.ts +223 -0
  63. package/dist/types/components/Form/utils/formatBytes.d.ts +6 -0
  64. package/dist/types/components/Form/utils/getFieldError.d.ts +6 -0
  65. package/dist/types/components/Form/utils/useFormI18n.d.ts +53 -0
  66. package/dist/types/components/Form/utils/validateData.d.ts +9 -0
  67. package/dist/types/components/TextArea/TextArea.d.ts +22 -0
  68. package/dist/types/components/TimePicker/TimePicker.d.ts +21 -0
  69. package/dist/types/components/ui/field.d.ts +3 -3
  70. package/dist/types/index.d.ts +19 -2
  71. package/package.json +18 -3
  72. package/dist/types/components/Controls/DensityFeature.d.ts +0 -23
  73. package/dist/types/components/Controls/DensityToggleButton.d.ts +0 -6
  74. package/dist/types/components/Controls/EditFilterButton.d.ts +0 -9
  75. package/dist/types/components/Controls/EditOrderButton.d.ts +0 -7
  76. package/dist/types/components/Controls/EditSortingButton.d.ts +0 -7
  77. package/dist/types/components/Controls/EditViewButton.d.ts +0 -7
  78. package/dist/types/components/Controls/FilterDialog.d.ts +0 -5
  79. package/dist/types/components/Controls/PageSizeControl.d.ts +0 -4
  80. package/dist/types/components/Controls/Pagination.d.ts +0 -1
  81. package/dist/types/components/Controls/ResetFilteringButton.d.ts +0 -4
  82. package/dist/types/components/Controls/ResetSelectionButton.d.ts +0 -4
  83. package/dist/types/components/Controls/ResetSortingButton.d.ts +0 -4
  84. package/dist/types/components/Controls/RowCountText.d.ts +0 -1
  85. package/dist/types/components/Controls/SelectAllRowsToggle.d.ts +0 -8
  86. package/dist/types/components/Controls/TablePagination.d.ts +0 -1
  87. package/dist/types/components/Controls/ViewDialog.d.ts +0 -5
  88. package/dist/types/components/DataTable/CardHeader.d.ts +0 -13
  89. package/dist/types/components/DataTable/DataDisplay.d.ts +0 -6
  90. package/dist/types/components/DataTable/ReloadButton.d.ts +0 -5
  91. package/dist/types/components/DataTable/Table.d.ts +0 -10
  92. package/dist/types/components/DataTable/TableBody.d.ts +0 -21
  93. package/dist/types/components/DataTable/TableCardContainer.d.ts +0 -7
  94. package/dist/types/components/DataTable/TableCards.d.ts +0 -11
  95. package/dist/types/components/DataTable/TableComponent.d.ts +0 -6
  96. package/dist/types/components/DataTable/TableControls.d.ts +0 -21
  97. package/dist/types/components/DataTable/TableFilter.d.ts +0 -1
  98. package/dist/types/components/DataTable/TableFilterTags.d.ts +0 -1
  99. package/dist/types/components/DataTable/TableFilters.d.ts +0 -1
  100. package/dist/types/components/DataTable/TableFooter.d.ts +0 -9
  101. package/dist/types/components/DataTable/TableHeader.d.ts +0 -13
  102. package/dist/types/components/DataTable/TableLoadingComponent.d.ts +0 -5
  103. package/dist/types/components/DataTable/TableOrderer.d.ts +0 -1
  104. package/dist/types/components/DataTable/TableSelector.d.ts +0 -1
  105. package/dist/types/components/DataTable/TableSorter.d.ts +0 -1
  106. package/dist/types/components/DataTable/TableViewer.d.ts +0 -1
  107. package/dist/types/components/DataTable/TextCell.d.ts +0 -10
  108. package/dist/types/components/DataTable/components/EmptyState.d.ts +0 -5
  109. package/dist/types/components/DataTable/components/ErrorAlert.d.ts +0 -4
  110. package/dist/types/components/DataTable/components/RecordDisplay.d.ts +0 -9
  111. package/dist/types/components/DataTable/components/TextCell.d.ts +0 -10
  112. package/dist/types/components/Filter/DateRangeFilter.d.ts +0 -9
  113. package/dist/types/components/Filter/FilterOptions.d.ts +0 -4
  114. package/dist/types/components/Form/Form.d.ts +0 -36
  115. package/dist/types/components/Form/components/ArrayRenderer.d.ts +0 -7
  116. package/dist/types/components/Form/components/BooleanPicker.d.ts +0 -7
  117. package/dist/types/components/Form/components/ColumnRenderer.d.ts +0 -7
  118. package/dist/types/components/Form/components/EnumPicker.d.ts +0 -8
  119. package/dist/types/components/Form/components/FilePicker.d.ts +0 -5
  120. package/dist/types/components/Form/components/IdPicker.d.ts +0 -8
  121. package/dist/types/components/Form/components/IdViewer.d.ts +0 -5
  122. package/dist/types/components/Form/components/NumberInputField.d.ts +0 -7
  123. package/dist/types/components/Form/components/ObjectInput.d.ts +0 -7
  124. package/dist/types/components/Form/components/RecordInput.d.ts +0 -7
  125. package/dist/types/components/Form/components/SchemaRenderer.d.ts +0 -7
  126. package/dist/types/components/Form/components/StringInputField.d.ts +0 -20
  127. package/dist/types/components/Form/components/TagPicker.d.ts +0 -30
package/dist/index.mjs CHANGED
@@ -1,26 +1,26 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Image, EmptyState as EmptyState$2, VStack, Alert, Card, Checkbox as Checkbox$1, Table as Table$1, Tooltip as Tooltip$1, Group, InputElement, Icon, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, List, Accordion, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading } from '@chakra-ui/react';
2
+ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Image, EmptyState as EmptyState$2, VStack, Alert, Card, Group, InputElement, Tooltip as Tooltip$1, Icon, List, Table as Table$1, Checkbox as Checkbox$1, Skeleton, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
- import React__default, { createContext, useContext, useState, useEffect, useRef } from 'react';
6
- import { LuX, LuCheck, LuChevronRight, LuChevronDown } from 'react-icons/lu';
7
- import { MdOutlineSort, MdFilterAlt, MdSearch, MdClose, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist } from 'react-icons/md';
8
- import { FaUpDown, FaGripLinesVertical } from 'react-icons/fa6';
5
+ import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
6
+ import { LuX, LuCheck, LuChevronRight, LuImage, LuFile, LuSearch } from 'react-icons/lu';
7
+ import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist, MdDateRange } from 'react-icons/md';
8
+ import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
10
- import { CgClose } from 'react-icons/cg';
10
+ import { CgClose, CgTrash } from 'react-icons/cg';
11
11
  import Dayzed from '@bsol-oss/dayzed-react19';
12
12
  import { HiMiniEllipsisHorizontal, HiChevronLeft, HiChevronRight } from 'react-icons/hi2';
13
- import { IoMdEye, IoMdCheckbox } from 'react-icons/io';
13
+ import { IoMdEye, IoMdCheckbox, IoMdClock } from 'react-icons/io';
14
14
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
15
15
  import { bind, bindAll } from 'bind-event-listener';
16
16
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
17
17
  import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
18
18
  import rafSchd from 'raf-schd';
19
19
  import invariant from 'tiny-invariant';
20
- import { HiColorSwatch } from 'react-icons/hi';
20
+ import { HiColorSwatch, HiOutlineInformationCircle } from 'react-icons/hi';
21
21
  import { flexRender, makeStateUpdater, functionalUpdate, useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getPaginationRowModel, createColumnHelper } from '@tanstack/react-table';
22
22
  import { rankItem } from '@tanstack/match-sorter-utils';
23
- import { BsExclamationCircleFill } from 'react-icons/bs';
23
+ import { BsExclamationCircleFill, BsClock } from 'react-icons/bs';
24
24
  import { useDebounce } from '@uidotdev/usehooks';
25
25
  import { useQueryClient, useQuery } from '@tanstack/react-query';
26
26
  import { IoReload } from 'react-icons/io5';
@@ -28,7 +28,11 @@ import { GrAscend, GrDescend } from 'react-icons/gr';
28
28
  import { useTranslation } from 'react-i18next';
29
29
  import axios from 'axios';
30
30
  import { FormProvider, useFormContext, useForm as useForm$1 } from 'react-hook-form';
31
+ import Ajv from 'ajv';
32
+ import addFormats from 'ajv-formats';
31
33
  import dayjs from 'dayjs';
34
+ import utc from 'dayjs/plugin/utc';
35
+ import timezone from 'dayjs/plugin/timezone';
32
36
  import { TiDeleteOutline } from 'react-icons/ti';
33
37
 
34
38
  const DataTableContext = createContext({
@@ -37,6 +41,56 @@ const DataTableContext = createContext({
37
41
  setGlobalFilter: () => { },
38
42
  type: "client",
39
43
  translate: {},
44
+ data: [],
45
+ columns: [],
46
+ columnOrder: [],
47
+ columnFilters: [],
48
+ density: "sm",
49
+ sorting: [],
50
+ setPagination: function () {
51
+ throw new Error("Function not implemented.");
52
+ },
53
+ setSorting: function () {
54
+ throw new Error("Function not implemented.");
55
+ },
56
+ setColumnFilters: function () {
57
+ throw new Error("Function not implemented.");
58
+ },
59
+ setRowSelection: function () {
60
+ throw new Error("Function not implemented.");
61
+ },
62
+ setColumnOrder: function () {
63
+ throw new Error("Function not implemented.");
64
+ },
65
+ setDensity: function () {
66
+ throw new Error("Function not implemented.");
67
+ },
68
+ setColumnVisibility: function () {
69
+ throw new Error("Function not implemented.");
70
+ },
71
+ pagination: {
72
+ pageIndex: 0,
73
+ pageSize: 10,
74
+ },
75
+ rowSelection: {},
76
+ columnVisibility: {},
77
+ tableLabel: {
78
+ view: "View",
79
+ edit: "Edit",
80
+ filterButtonText: "Filter",
81
+ filterTitle: "Filter",
82
+ filterReset: "Reset",
83
+ filterClose: "Close",
84
+ reloadTooltip: "Reload",
85
+ reloadButtonText: "Reload",
86
+ resetSelection: "Reset Selection",
87
+ resetSorting: "Reset Sorting",
88
+ rowCountText: "Row Count",
89
+ hasErrorText: "Has Error",
90
+ globalFilterPlaceholder: "Search",
91
+ trueLabel: "True",
92
+ falseLabel: "False",
93
+ },
40
94
  });
41
95
 
42
96
  const useDataTableContext = () => {
@@ -92,11 +146,13 @@ const TableSorter = () => {
92
146
  }) }))) }));
93
147
  };
94
148
 
95
- const ResetSortingButton = ({ text = "Reset Sorting", }) => {
149
+ const ResetSortingButton = () => {
96
150
  const { table } = useDataTableContext();
151
+ const { tableLabel } = useDataTableContext();
152
+ const { resetSorting } = tableLabel;
97
153
  return (jsx(Button$1, { onClick: () => {
98
154
  table.resetSorting();
99
- }, children: text }));
155
+ }, children: resetSorting }));
100
156
  };
101
157
 
102
158
  const EditSortingButton = ({ text, icon = jsx(MdOutlineSort, {}), title = "Edit Sorting", }) => {
@@ -124,7 +180,7 @@ const monthNamesFull = [
124
180
  "November",
125
181
  "December",
126
182
  ];
127
- const weekdayNamesShort$1 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
183
+ const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
128
184
  function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, selected = [], firstDayOfWeek = 0, }) {
129
185
  const [hoveredDate, setHoveredDate] = useState();
130
186
  const onMouseLeave = () => {
@@ -159,7 +215,7 @@ function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, se
159
215
  offset: 12,
160
216
  }), children: ">>" })] }), jsx(Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", gap: 4, children: calendars.map((calendar) => (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { justifyContent: "center", children: [monthNamesFull[calendar.month], " ", calendar.year] }), jsx(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
161
217
  const weekday = (weekdayNum + firstDayOfWeek) % 7;
162
- return (jsx(Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort$1[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
218
+ return (jsx(Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
163
219
  }) }), jsx(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: calendar.weeks.map((week, windex) => week.map((dateObj, index) => {
164
220
  const key = `${calendar.month}${calendar.year}${windex}${index}`;
165
221
  if (!dateObj) {
@@ -280,8 +336,17 @@ const Tag = React.forwardRef(function Tag(props, ref) {
280
336
  return (jsxs(Tag$1.Root, { ref: ref, ...rest, children: [startElement && (jsx(Tag$1.StartElement, { children: startElement })), jsx(Tag$1.Label, { children: children }), endElement && (jsx(Tag$1.EndElement, { children: endElement })), closable && (jsx(Tag$1.EndElement, { children: jsx(Tag$1.CloseTrigger, { onClick: onClose }) }))] }));
281
337
  });
282
338
 
283
- const TagFilter = ({ availableTags, selectedTags, onTagChange, }) => {
339
+ const TagFilter = ({ availableTags, selectedTags, onTagChange, selectOne = false, }) => {
284
340
  const toggleTag = (tag) => {
341
+ if (selectOne) {
342
+ if (selectedTags.includes(tag)) {
343
+ onTagChange([]);
344
+ }
345
+ else {
346
+ onTagChange([tag]);
347
+ }
348
+ return;
349
+ }
285
350
  if (selectedTags.includes(tag)) {
286
351
  onTagChange(selectedTags.filter((t) => t !== tag));
287
352
  }
@@ -289,10 +354,14 @@ const TagFilter = ({ availableTags, selectedTags, onTagChange, }) => {
289
354
  onTagChange([...selectedTags, tag]);
290
355
  }
291
356
  };
292
- return (jsx(Flex, { flexFlow: "wrap", p: "0.5rem", gap: "0.5rem", children: availableTags.map((tag) => (jsx(Tag, { variant: selectedTags.includes(tag) ? "solid" : "outline", cursor: "pointer", closable: selectedTags.includes(tag) ? true : undefined, onClick: () => toggleTag(tag), children: tag }))) }));
357
+ return (jsx(Flex, { flexFlow: "wrap", p: "0.5rem", gap: "0.5rem", children: availableTags.map((tag) => {
358
+ const { label, value } = tag;
359
+ return (jsx(Tag, { variant: selectedTags.includes(value) ? "solid" : "outline", cursor: "pointer", closable: selectedTags.includes(value) ? true : undefined, onClick: () => toggleTag(value), children: label ?? value }));
360
+ }) }));
293
361
  };
294
362
 
295
363
  const Filter = ({ column }) => {
364
+ const { tableLabel } = useDataTableContext();
296
365
  const { filterVariant } = column.columnDef.meta ?? {};
297
366
  const displayName = column.columnDef.meta?.displayName ?? column.id;
298
367
  const filterOptions = column.columnDef.meta?.filterOptions ?? [];
@@ -307,10 +376,14 @@ const Filter = ({ column }) => {
307
376
  if (filterVariant === "select") {
308
377
  return (jsxs(Flex, { flexFlow: "column", gap: "0.25rem", children: [jsx(Text, { children: displayName }), jsx(RadioGroup, { value: column.getFilterValue() ? String(column.getFilterValue()) : "", onValueChange: (details) => {
309
378
  column.setFilterValue(details.value);
310
- }, children: jsx(Flex, { flexFlow: "wrap", gap: "0.5rem", children: filterOptions.map((item) => (jsx(Radio, { value: item, children: item }, item))) }) })] }, column.id));
379
+ }, children: jsxs(Flex, { flexFlow: "wrap", gap: "0.5rem", children: [filterOptions.length === 0 && jsx(Text, { children: "No filter options" }), filterOptions.length > 0 &&
380
+ filterOptions.map((item) => (jsx(Radio, { value: item.value, children: item.label }, item.value)))] }) })] }, column.id));
311
381
  }
312
382
  if (filterVariant === "tag") {
313
- return (jsxs(Flex, { flexFlow: "column", gap: "0.25rem", children: [jsx(Text, { children: displayName }), jsx(TagFilter, { availableTags: filterOptions, selectedTags: (column.getFilterValue() ?? []), onTagChange: (tags) => {
383
+ return (jsxs(Flex, { flexFlow: "column", gap: "0.25rem", children: [jsx(Text, { children: displayName }), jsx(TagFilter, { availableTags: filterOptions.map((item) => ({
384
+ label: item.label,
385
+ value: item.value,
386
+ })), selectedTags: (column.getFilterValue() ?? []), onTagChange: (tags) => {
314
387
  if (tags.length === 0) {
315
388
  return column.setFilterValue(undefined);
316
389
  }
@@ -318,7 +391,11 @@ const Filter = ({ column }) => {
318
391
  } })] }, column.id));
319
392
  }
320
393
  if (filterVariant === "boolean") {
321
- return (jsxs(Flex, { flexFlow: "column", gap: "0.25rem", children: [jsx(Text, { children: displayName }), jsx(TagFilter, { availableTags: ["true", "false"], selectedTags: (column.getFilterValue() ?? []), onTagChange: (tags) => {
394
+ const { trueLabel, falseLabel } = tableLabel;
395
+ return (jsxs(Flex, { flexFlow: "column", gap: "0.25rem", children: [jsx(Text, { children: displayName }), jsx(TagFilter, { availableTags: [
396
+ { label: trueLabel, value: "true" },
397
+ { label: falseLabel, value: "false" },
398
+ ], selectedTags: (column.getFilterValue() ?? []), onTagChange: (tags) => {
322
399
  if (tags.length === 0) {
323
400
  return column.setFilterValue(undefined);
324
401
  }
@@ -373,17 +450,20 @@ const TableFilter = () => {
373
450
  }) }));
374
451
  };
375
452
 
376
- const ResetFilteringButton = ({ text = "Reset Filtering", }) => {
453
+ const ResetFilteringButton = () => {
377
454
  const { table } = useDataTableContext();
455
+ const { tableLabel } = useDataTableContext();
456
+ const { filterReset } = tableLabel;
378
457
  return (jsx(Button$1, { onClick: () => {
379
458
  table.resetColumnFilters();
380
- }, children: text }));
459
+ }, children: filterReset }));
381
460
  };
382
461
 
383
462
  const FilterDialog = ({ icon = jsx(MdFilterAlt, {}), }) => {
384
463
  const filterModal = useDisclosure();
385
- const { translate } = useDataTableContext();
386
- return (jsxs(DialogRoot, { size: ["full", "full", "md", "md"], open: filterModal.open, children: [jsx(DialogTrigger, { asChild: true, children: jsxs(Button$1, { as: Box, variant: "ghost", onClick: filterModal.onOpen, children: [icon, " ", translate.t("filterDialog.buttonText")] }) }), jsxs(DialogContent, { children: [jsx(DialogHeader, { children: jsx(DialogTitle, { children: translate.t("filterDialog.title") }) }), jsx(DialogBody, { display: "flex", flexFlow: "column", children: jsx(TableFilter, {}) }), jsxs(DialogFooter, { children: [jsx(ResetFilteringButton, { text: translate.t("filterDialog.reset") }), jsx(Button$1, { onClick: filterModal.onClose, variant: "subtle", children: translate.t("filterDialog.close") })] }), jsx(DialogCloseTrigger, { onClick: filterModal.onClose })] })] }));
464
+ const { tableLabel } = useDataTableContext();
465
+ const { filterButtonText, filterTitle, filterClose } = tableLabel;
466
+ return (jsxs(DialogRoot, { size: ["full", "full", "md", "md"], open: filterModal.open, children: [jsx(DialogTrigger, { asChild: true, children: jsxs(Button$1, { as: Box, variant: "ghost", onClick: filterModal.onOpen, children: [icon, " ", filterButtonText] }) }), jsxs(DialogContent, { children: [jsx(DialogHeader, { children: jsx(DialogTitle, { children: filterTitle }) }), jsx(DialogBody, { display: "flex", flexFlow: "column", children: jsx(TableFilter, {}) }), jsxs(DialogFooter, { children: [jsx(ResetFilteringButton, {}), jsx(Button$1, { onClick: filterModal.onClose, variant: "subtle", children: filterClose })] }), jsx(DialogCloseTrigger, { onClick: filterModal.onClose })] })] }));
387
467
  };
388
468
 
389
469
  const MenuContent = React.forwardRef(function MenuContent(props, ref) {
@@ -502,11 +582,13 @@ const Pagination = () => {
502
582
  }, children: jsxs(HStack, { children: [jsx(PaginationPrevTrigger, {}), jsx(PaginationItems, {}), jsx(PaginationNextTrigger, {})] }) }));
503
583
  };
504
584
 
505
- const ResetSelectionButton = ({ text = "Reset Selection", }) => {
585
+ const ResetSelectionButton = () => {
506
586
  const { table } = useDataTableContext();
587
+ const { tableLabel } = useDataTableContext();
588
+ const { resetSelection } = tableLabel;
507
589
  return (jsx(Button$1, { onClick: () => {
508
590
  table.resetRowSelection();
509
- }, children: text }));
591
+ }, children: resetSelection }));
510
592
  };
511
593
 
512
594
  const RowCountText = () => {
@@ -2421,8 +2503,8 @@ CheckboxCard$1.Indicator;
2421
2503
  function ColumnCard({ columnId }) {
2422
2504
  const ref = useRef(null);
2423
2505
  const [dragging, setDragging] = useState(false); // NEW
2424
- const { table } = useDataTableContext();
2425
- const displayName = columnId;
2506
+ const { table, translate } = useDataTableContext();
2507
+ const displayName = translate.t(columnId);
2426
2508
  const column = table.getColumn(columnId);
2427
2509
  invariant(column);
2428
2510
  useEffect(() => {
@@ -2437,7 +2519,7 @@ function ColumnCard({ columnId }) {
2437
2519
  onDrop: () => setDragging(false), // NEW
2438
2520
  });
2439
2521
  }, [columnId, table]);
2440
- return (jsxs(Grid, { ref: ref, templateColumns: "auto 1fr", gap: "0.5rem", alignItems: "center", style: dragging ? { opacity: 0.4 } : {}, children: [jsx(Flex, { alignItems: "center", padding: "0", cursor: "grab", children: jsx(FaGripLinesVertical, { color: "gray.400" }) }), jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: jsx(CheckboxCard, { variant: "surface", label: displayName, checked: column.getIsVisible(), onChange: column.getToggleVisibilityHandler() }) })] }));
2522
+ return (jsxs(Grid, { ref: ref, templateColumns: "auto 1fr", gap: "0.5rem", alignItems: "center", style: dragging ? { opacity: 0.4 } : {}, children: [jsx(Flex, { alignItems: "center", padding: "0", cursor: "grab", children: jsx(FaGripLinesVertical, { color: "colorPalette.400" }) }), jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: jsx(CheckboxCard, { variant: "surface", label: displayName, checked: column.getIsVisible(), onChange: column.getToggleVisibilityHandler() }) })] }));
2441
2523
  }
2442
2524
  function CardContainer({ location, children }) {
2443
2525
  const ref = useRef(null);
@@ -2456,7 +2538,6 @@ function CardContainer({ location, children }) {
2456
2538
  onDrop: () => setIsDraggedOver(false),
2457
2539
  });
2458
2540
  }, [location]);
2459
- // const isDark = (location + location) % 2 === 1;
2460
2541
  function getColor(isDraggedOver) {
2461
2542
  if (isDraggedOver) {
2462
2543
  return {
@@ -2466,7 +2547,6 @@ function CardContainer({ location, children }) {
2466
2547
  },
2467
2548
  };
2468
2549
  }
2469
- // return isDark ? "lightgrey" : "white";
2470
2550
  return {
2471
2551
  backgroundColor: undefined,
2472
2552
  _dark: {
@@ -2517,8 +2597,9 @@ const TableViewer = () => {
2517
2597
 
2518
2598
  const ViewDialog = ({ icon = jsx(IoMdEye, {}) }) => {
2519
2599
  const viewModel = useDisclosure();
2520
- const { translate } = useDataTableContext();
2521
- return (jsxs(DialogRoot, { children: [jsx(DialogBackdrop, {}), jsx(DialogTrigger, { asChild: true, children: jsxs(Button$1, { as: Box, variant: "ghost", onClick: viewModel.onOpen, children: [icon, " ", translate.t("viewDialog.buttonText")] }) }), jsxs(DialogContent, { children: [jsx(DialogCloseTrigger, {}), jsx(DialogHeader, { children: jsx(DialogTitle, { children: translate.t("viewDialog.title") }) }), jsx(DialogBody, { children: jsx(TableViewer, {}) }), jsx(DialogFooter, {})] })] }));
2600
+ const { tableLabel } = useDataTableContext();
2601
+ const { view } = tableLabel;
2602
+ return (jsxs(DialogRoot, { children: [jsx(DialogBackdrop, {}), jsx(DialogTrigger, { asChild: true, children: jsxs(Button$1, { as: Box, variant: "ghost", onClick: viewModel.onOpen, children: [icon, " ", view] }) }), jsxs(DialogContent, { children: [jsx(DialogCloseTrigger, {}), jsx(DialogHeader, { children: jsx(DialogTitle, { children: view }) }), jsx(DialogBody, { children: jsx(TableViewer, {}) }), jsx(DialogFooter, {})] })] }));
2522
2603
  };
2523
2604
 
2524
2605
  const CardHeader = ({ row, imageColumnId = undefined, titleColumnId = undefined, tagColumnId = undefined, tagIcon = undefined, showTag = true, imageProps = {}, }) => {
@@ -2569,7 +2650,7 @@ const RecordDisplay = ({ object, boxProps, translate, prefix = "", }) => {
2569
2650
  return jsx(Fragment, { children: "null" });
2570
2651
  }
2571
2652
  return (jsx(Grid, { rowGap: 1, padding: 1, overflow: "auto", ...boxProps, children: Object.entries(object).map(([field, value]) => {
2572
- return (jsxs(Grid, { columnGap: 2, gridTemplateColumns: "auto 1fr", children: [jsx(Text, { color: "gray.400", children: getColumn({ field }) }), typeof value === "object" ? (jsx(RecordDisplay, { object: value, prefix: `${prefix}${field}.`, translate: translate })) : (jsx(Text, { children: JSON.stringify(value) }))] }, field));
2653
+ return (jsxs(Grid, { columnGap: 2, gridTemplateColumns: "auto 1fr", children: [jsx(Text, { color: "colorPalette.400", children: getColumn({ field }) }), typeof value === "object" ? (jsx(RecordDisplay, { object: value, prefix: `${prefix}${field}.`, translate: translate })) : (jsx(Text, { children: JSON.stringify(value) }))] }, field));
2573
2654
  }) }));
2574
2655
  };
2575
2656
 
@@ -2619,7 +2700,7 @@ const CellRenderer = ({ cell }) => {
2619
2700
  paddingY: 2,
2620
2701
  }, object: value })] }, cell.id));
2621
2702
  }
2622
- return (jsxs(Box, { gridColumn, gridRow, children: [jsx(Box, { color: 'gray.400', children: getLabel({ columnId: cell.column.id }) }), jsx(Box, { wordBreak: "break-word", textOverflow: "ellipsis", overflow: "hidden", children: `${formatValue(cell.getValue())}` })] }, cell.id));
2703
+ return (jsxs(Box, { gridColumn, gridRow, children: [jsx(Box, { color: "colorPalette.400", children: getLabel({ columnId: cell.column.id }) }), jsx(Box, { wordBreak: "break-word", textOverflow: "ellipsis", overflow: "hidden", children: `${formatValue(cell.getValue())}` })] }, cell.id));
2623
2704
  };
2624
2705
  const DataDisplay = ({ variant = "" }) => {
2625
2706
  const { table, translate } = useDataTableContext();
@@ -2741,7 +2822,23 @@ const fuzzyFilter = (row, columnId, value, addMeta) => {
2741
2822
  *
2742
2823
  * @link https://tanstack.com/table/latest/docs/guide/column-defs
2743
2824
  */
2744
- function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSelection = true, enableSubRowSelection = true, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, translate, children, }) {
2825
+ function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSelection = true, enableSubRowSelection = true, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, translate, children, tableLabel = {
2826
+ view: 'View',
2827
+ edit: 'Edit',
2828
+ filterButtonText: 'Filter',
2829
+ filterTitle: 'Filter',
2830
+ filterReset: 'Reset',
2831
+ filterClose: 'Close',
2832
+ reloadTooltip: 'Reload',
2833
+ reloadButtonText: 'Reload',
2834
+ resetSelection: 'Reset Selection',
2835
+ resetSorting: 'Reset Sorting',
2836
+ rowCountText: 'Row Count',
2837
+ hasErrorText: 'Has Error',
2838
+ globalFilterPlaceholder: 'Search',
2839
+ trueLabel: 'True',
2840
+ falseLabel: 'False',
2841
+ }, }) {
2745
2842
  const table = useReactTable({
2746
2843
  _features: [DensityFeature],
2747
2844
  data: data,
@@ -2759,12 +2856,12 @@ function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSel
2759
2856
  enableRowSelection: enableRowSelection,
2760
2857
  enableMultiRowSelection: enableMultiRowSelection,
2761
2858
  enableSubRowSelection: enableSubRowSelection,
2762
- columnResizeMode: "onChange",
2859
+ columnResizeMode: 'onChange',
2763
2860
  // global filter start
2764
2861
  filterFns: {
2765
2862
  fuzzy: fuzzyFilter,
2766
2863
  },
2767
- globalFilterFn: "fuzzy",
2864
+ globalFilterFn: 'fuzzy',
2768
2865
  state: {
2769
2866
  pagination,
2770
2867
  sorting,
@@ -2792,9 +2889,9 @@ function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSel
2792
2889
  table: table,
2793
2890
  globalFilter,
2794
2891
  setGlobalFilter,
2795
- type: "client",
2892
+ type: 'client',
2796
2893
  translate,
2797
- columns,
2894
+ columns: columns,
2798
2895
  sorting,
2799
2896
  setSorting,
2800
2897
  columnFilters,
@@ -2809,6 +2906,8 @@ function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSel
2809
2906
  setDensity,
2810
2907
  columnVisibility,
2811
2908
  setColumnVisibility,
2909
+ data,
2910
+ tableLabel,
2812
2911
  }, children: children }));
2813
2912
  }
2814
2913
 
@@ -2823,10 +2922,26 @@ function DataTable({ columns, data, enableRowSelection = true, enableMultiRowSel
2823
2922
  *
2824
2923
  * @link https://tanstack.com/table/latest/docs/guide/column-defs
2825
2924
  */
2826
- function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSelection = true, enableSubRowSelection = true, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, query, url, translate, children, }) {
2925
+ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSelection = true, enableSubRowSelection = true, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, query, url, translate, children, tableLabel = {
2926
+ view: "View",
2927
+ edit: "Edit",
2928
+ filterButtonText: "Filter",
2929
+ filterTitle: "Filter",
2930
+ filterReset: "Reset",
2931
+ filterClose: "Close",
2932
+ reloadTooltip: "Reload",
2933
+ reloadButtonText: "Reload",
2934
+ resetSelection: "Reset Selection",
2935
+ resetSorting: "Reset Sorting",
2936
+ rowCountText: "Row Count",
2937
+ hasErrorText: "Has Error",
2938
+ globalFilterPlaceholder: "Search",
2939
+ trueLabel: "True",
2940
+ falseLabel: "False",
2941
+ }, }) {
2827
2942
  const table = useReactTable({
2828
2943
  _features: [DensityFeature],
2829
- data: query.data?.data ?? [],
2944
+ data: (query.data?.data ?? []),
2830
2945
  rowCount: query.data?.count ?? 0,
2831
2946
  columns: columns,
2832
2947
  getCoreRowModel: getCoreRowModel(),
@@ -2872,12 +2987,12 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
2872
2987
  // for tanstack-table ts bug end
2873
2988
  });
2874
2989
  return (jsx(DataTableContext.Provider, { value: {
2875
- table: { ...table },
2990
+ table: table,
2876
2991
  globalFilter,
2877
2992
  setGlobalFilter,
2878
2993
  type: "server",
2879
2994
  translate,
2880
- columns,
2995
+ columns: columns,
2881
2996
  sorting,
2882
2997
  setSorting,
2883
2998
  columnFilters,
@@ -2892,15 +3007,108 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
2892
3007
  setDensity,
2893
3008
  columnVisibility,
2894
3009
  setColumnVisibility,
3010
+ data: query.data?.data ?? [],
3011
+ tableLabel,
2895
3012
  }, children: jsx(DataTableServerContext.Provider, { value: { url, query }, children: children }) }));
2896
3013
  }
2897
3014
 
3015
+ const InputGroup = React.forwardRef(function InputGroup(props, ref) {
3016
+ const { startElement, startElementProps, endElement, endElementProps, children, startOffset = "6px", endOffset = "6px", ...rest } = props;
3017
+ return (jsxs(Group, { ref: ref, ...rest, children: [startElement && (jsx(InputElement, { pointerEvents: "none", ...startElementProps, children: startElement })), React.cloneElement(children, {
3018
+ ...(startElement && {
3019
+ ps: `calc(var(--input-height) - ${startOffset})`,
3020
+ }),
3021
+ ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
3022
+ // @ts-expect-error chakra generated files
3023
+ ...children.props,
3024
+ }), endElement && (jsx(InputElement, { placement: "end", ...endElementProps, children: endElement }))] }));
3025
+ });
3026
+
3027
+ const GlobalFilter = () => {
3028
+ const { table, tableLabel } = useDataTableContext();
3029
+ const { globalFilterPlaceholder } = tableLabel;
3030
+ const [searchTerm, setSearchTerm] = useState("");
3031
+ const debouncedSearchTerm = useDebounce(searchTerm, 500);
3032
+ useEffect(() => {
3033
+ const searchHN = async () => {
3034
+ table.setGlobalFilter(debouncedSearchTerm);
3035
+ };
3036
+ searchHN();
3037
+ }, [debouncedSearchTerm]);
3038
+ return (jsx(Fragment, { children: jsx(InputGroup, { flex: "1", startElement: jsx(MdSearch, {}), children: jsx(Input, { placeholder: globalFilterPlaceholder, variant: "outline", onChange: (e) => {
3039
+ setSearchTerm(e.target.value);
3040
+ } }) }) }));
3041
+ };
3042
+
3043
+ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
3044
+ const { showArrow, children, disabled, portalled, content, contentProps, portalRef, ...rest } = props;
3045
+ if (disabled)
3046
+ return children;
3047
+ return (jsxs(Tooltip$1.Root, { ...rest, children: [jsx(Tooltip$1.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Tooltip$1.Positioner, { children: jsxs(Tooltip$1.Content, { ref: ref, ...contentProps, children: [showArrow && (jsx(Tooltip$1.Arrow, { children: jsx(Tooltip$1.ArrowTip, {}) })), content] }) }) })] }));
3048
+ });
3049
+
3050
+ const ReloadButton = ({ variant = "icon", }) => {
3051
+ const { url } = useDataTableServerContext();
3052
+ const queryClient = useQueryClient();
3053
+ const { tableLabel } = useDataTableContext();
3054
+ const { reloadTooltip, reloadButtonText } = tableLabel;
3055
+ if (variant === "icon") {
3056
+ return (jsx(Tooltip, { showArrow: true, content: reloadTooltip, children: jsx(Button, { variant: "ghost", onClick: () => {
3057
+ queryClient.invalidateQueries({ queryKey: [url] });
3058
+ }, "aria-label": "refresh", children: jsx(IoReload, {}) }) }));
3059
+ }
3060
+ return (jsxs(Button, { variant: "ghost", onClick: () => {
3061
+ queryClient.invalidateQueries({ queryKey: [url] });
3062
+ }, children: [jsx(IoReload, {}), " ", reloadButtonText] }));
3063
+ };
3064
+
3065
+ const TableFilterTags = () => {
3066
+ const { table } = useDataTableContext();
3067
+ return (jsx(Flex, { gap: "0.5rem", flexFlow: "wrap", children: table.getState().columnFilters.map(({ id, value }) => {
3068
+ return (jsx(Tag, { gap: "0.5rem", closable: true, cursor: "pointer", onClick: () => {
3069
+ table.setColumnFilters(table.getState().columnFilters.filter((filter) => {
3070
+ return filter.value != value;
3071
+ }));
3072
+ }, children: `${id}: ${value}` }, `${id}-${value}`));
3073
+ }) }));
3074
+ };
3075
+
3076
+ const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsx(Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsx(Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
3077
+ const { tableLabel, table } = useDataTableContext();
3078
+ const { rowCountText, hasErrorText } = tableLabel;
3079
+ return (jsxs(Grid, { templateRows: "auto 1fr", width: fitTableWidth ? "fit-content" : "100%", height: fitTableHeight ? "fit-content" : "100%", gap: "0.5rem", ...gridProps, children: [jsxs(Flex, { flexFlow: "column", gap: 2, children: [jsxs(Flex, { justifyContent: "space-between", children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: "0.5rem", alignItems: "center", justifySelf: "end", children: [loading && jsx(Spinner, { size: "sm" }), hasError && (jsx(Tooltip, { content: hasErrorText, children: jsx(Icon, { as: BsExclamationCircleFill, color: "red.400" }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), filterTagsOptions.length > 0 && (jsx(Flex, { flexFlow: "column", gap: "0.5rem", children: filterTagsOptions.map((option) => {
3080
+ const { column, options } = option;
3081
+ const tableColumn = table.getColumn(column);
3082
+ return (jsxs(Flex, { alignItems: "center", flexFlow: "wrap", gap: "0.5rem", children: [tableColumn?.columnDef.meta?.displayName && (jsx(Text, { children: tableColumn?.columnDef.meta?.displayName })), jsx(TagFilter, { availableTags: options, selectedTags: tableColumn?.getFilterValue() ?? [], selectOne: true, onTagChange: (tags) => {
3083
+ if (tags.length === 0) {
3084
+ return tableColumn?.setFilterValue(undefined);
3085
+ }
3086
+ tableColumn?.setFilterValue(tags);
3087
+ } })] }, column));
3088
+ }) })), showFilterTags && (jsx(Flex, { children: jsx(TableFilterTags, {}) }))] }), jsx(Grid, { overflow: "auto", bg: { base: "colorPalette.50", _dark: "colorPalette.950" }, children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxs(Flex, { justifyContent: "space-between", children: [jsxs(Flex, { gap: "1rem", alignItems: "center", children: [showPageSizeControl && jsx(PageSizeControl, {}), showPageCountText && (jsxs(Flex, { children: [jsx(Text, { paddingRight: "0.5rem", children: rowCountText }), jsx(RowCountText, {})] }))] }), jsx(Box, { justifySelf: "end", children: showPagination && jsx(Pagination, {}) })] }))] }));
3089
+ };
3090
+
3091
+ const EmptyState = React.forwardRef(function EmptyState(props, ref) {
3092
+ const { title, description, icon, children, ...rest } = props;
3093
+ return (jsx(EmptyState$2.Root, { ref: ref, ...rest, children: jsxs(EmptyState$2.Content, { children: [icon && (jsx(EmptyState$2.Indicator, { children: icon })), description ? (jsxs(VStack, { textAlign: "center", children: [jsx(EmptyState$2.Title, { children: title }), jsx(EmptyState$2.Description, { children: description })] })) : (jsx(EmptyState$2.Title, { children: title })), children] }) }));
3094
+ });
3095
+
3096
+ const EmptyResult = (jsx(EmptyState, { icon: jsx(HiColorSwatch, {}), title: "No results found", description: "Try adjusting your search", children: jsxs(List.Root, { variant: "marker", children: [jsx(List.Item, { children: "Try removing filters" }), jsx(List.Item, { children: "Try different keywords" })] }) }));
3097
+ const Table = ({ children, emptyComponent = EmptyResult, canResize = true, showLoading = false, ...props }) => {
3098
+ const { table } = useDataTableContext();
3099
+ // Skip empty check when loading to allow skeleton to render
3100
+ if (!showLoading && table.getRowModel().rows.length <= 0) {
3101
+ return emptyComponent;
3102
+ }
3103
+ return (jsx(Table$1.Root, { stickyHeader: true, variant: 'outline', width: canResize ? table.getCenterTotalSize() : undefined, display: 'grid', alignContent: 'start', overflowY: 'auto', bg: { base: 'colorPalette.50', _dark: 'colorPalette.950' }, ...props, children: children }));
3104
+ };
3105
+
2898
3106
  const Checkbox = React.forwardRef(function Checkbox(props, ref) {
2899
3107
  const { icon, children, inputProps, rootRef, ...rest } = props;
2900
3108
  return (jsxs(Checkbox$1.Root, { ref: rootRef, ...rest, children: [jsx(Checkbox$1.HiddenInput, { ref: ref, ...inputProps }), jsx(Checkbox$1.Control, { children: icon || jsx(Checkbox$1.Indicator, {}) }), children != null && (jsx(Checkbox$1.Label, { children: children }))] }));
2901
3109
  });
2902
3110
 
2903
- const TableBody = ({ pinnedBgColor = { light: "gray.50", dark: "gray.700" }, showSelector = false, alwaysShowSelector = true, canResize = true, }) => {
3111
+ const TableBody = ({ showSelector = false, canResize = true, }) => {
2904
3112
  "use no memo";
2905
3113
  const { table } = useDataTableContext();
2906
3114
  const SELECTION_BOX_WIDTH = 20;
@@ -2914,12 +3122,7 @@ const TableBody = ({ pinnedBgColor = { light: "gray.50", dark: "gray.700" }, sho
2914
3122
  left: showSelector
2915
3123
  ? `${cell.column.getStart("left") + SELECTION_BOX_WIDTH + table.getDensityValue() * 2}px`
2916
3124
  : `${cell.column.getStart("left")}px`,
2917
- background: pinnedBgColor.light,
2918
- position: "sticky",
2919
- zIndex: -1,
2920
- _dark: {
2921
- backgroundColor: pinnedBgColor.dark,
2922
- },
3125
+ position: "relative",
2923
3126
  }
2924
3127
  : {};
2925
3128
  return tdProps;
@@ -2938,133 +3141,91 @@ const TableBody = ({ pinnedBgColor = { light: "gray.50", dark: "gray.700" }, sho
2938
3141
  };
2939
3142
  };
2940
3143
  return (jsx(Table$1.Body, { children: table.getRowModel().rows.map((row, index) => {
2941
- return (jsxs(Table$1.Row, { display: "flex", zIndex: 1, onMouseEnter: () => handleRowHover(index), onMouseLeave: () => handleRowHover(-1), ...getTrProps({ hoveredRow, index }), children: [showSelector && (jsx(TableRowSelector, { index: index, row: row, hoveredRow: hoveredRow, alwaysShowSelector: alwaysShowSelector })), row.getVisibleCells().map((cell, index) => {
3144
+ return (jsxs(Table$1.Row, { display: "flex", zIndex: 1, onMouseEnter: () => handleRowHover(index), onMouseLeave: () => handleRowHover(-1), ...getTrProps({ hoveredRow, index }), children: [showSelector && (jsx(TableRowSelector, { index: index, row: row, hoveredRow: hoveredRow })), row.getVisibleCells().map((cell, index) => {
2942
3145
  return (jsx(Table$1.Cell, { padding: `${table.getDensityValue()}px`,
2943
3146
  // styling resize and pinning start
2944
- flex: `${canResize ? "0" : "1"} 0 ${cell.column.getSize()}px`, backgroundColor: "white", ...getTdProps(cell), _dark: {
2945
- backgroundColor: "gray.950",
2946
- }, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, `chakra-table-rowcell-${cell.id}-${index}`));
3147
+ flex: `${canResize ? "0" : "1"} 0 ${cell.column.getSize()}px`,
3148
+ // this is to avoid the cell from being too wide
3149
+ minWidth: `0`, color: {
3150
+ base: "colorPalette.900",
3151
+ _dark: "colorPalette.100",
3152
+ },
3153
+ bg: { base: "colorPalette.50", _dark: "colorPalette.950" }, ...getTdProps(cell), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, `chakra-table-rowcell-${cell.id}-${index}`));
2947
3154
  })] }, `chakra-table-row-${row.id}`));
2948
3155
  }) }));
2949
3156
  };
2950
- const TableRowSelector = ({ index, row, hoveredRow, pinnedBgColor = { light: "gray.50", dark: "gray.700" }, alwaysShowSelector = true, }) => {
3157
+ const TableRowSelector = ({ row, }) => {
2951
3158
  const { table } = useDataTableContext();
2952
3159
  const SELECTION_BOX_WIDTH = 20;
2953
- const isCheckBoxVisible = (current_index, current_row) => {
2954
- if (alwaysShowSelector) {
2955
- return true;
2956
- }
2957
- if (current_row.getIsSelected()) {
2958
- return true;
2959
- }
2960
- if (hoveredRow == current_index) {
2961
- return true;
2962
- }
2963
- return false;
2964
- };
2965
- return (jsxs(Table$1.Cell, { padding: `${table.getDensityValue()}px`, ...(table.getIsSomeColumnsPinned("left")
2966
- ? {
2967
- left: `0px`,
2968
- backgroundColor: pinnedBgColor.light,
2969
- position: "sticky",
2970
- zIndex: 1,
2971
- _dark: { backgroundColor: pinnedBgColor.dark },
2972
- }
2973
- : {}),
2974
- // styling resize and pinning end
2975
- display: "grid", children: [!isCheckBoxVisible(index, row) && (jsx(Box, { as: "span", margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px` })), isCheckBoxVisible(index, row) && (jsx(Box, { margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, isChecked: row.getIsSelected(),
2976
- disabled: !row.getCanSelect(),
2977
- onChange: row.getToggleSelectedHandler() }) }))] }));
3160
+ return (jsx(Table$1.Cell, { padding: `${table.getDensityValue()}px`, display: "grid", color: {
3161
+ base: "colorPalette.900",
3162
+ _dark: "colorPalette.100",
3163
+ },
3164
+ bg: { base: "colorPalette.50", _dark: "colorPalette.950" }, justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, checked: row.getIsSelected(),
3165
+ disabled: !row.getCanSelect(),
3166
+ onCheckedChange: row.getToggleSelectedHandler() }) }));
2978
3167
  };
2979
3168
 
2980
- const Tooltip = React.forwardRef(function Tooltip(props, ref) {
2981
- const { showArrow, children, disabled, portalled, content, contentProps, portalRef, ...rest } = props;
2982
- if (disabled)
2983
- return children;
2984
- return (jsxs(Tooltip$1.Root, { ...rest, children: [jsx(Tooltip$1.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Tooltip$1.Positioner, { children: jsxs(Tooltip$1.Content, { ref: ref, ...contentProps, children: [showArrow && (jsx(Tooltip$1.Arrow, { children: jsx(Tooltip$1.ArrowTip, {}) })), content] }) }) })] }));
2985
- });
2986
-
2987
- const InputGroup = React.forwardRef(function InputGroup(props, ref) {
2988
- const { startElement, startElementProps, endElement, endElementProps, children, startOffset = "6px", endOffset = "6px", ...rest } = props;
2989
- return (jsxs(Group, { ref: ref, ...rest, children: [startElement && (jsx(InputElement, { pointerEvents: "none", ...startElementProps, children: startElement })), React.cloneElement(children, {
2990
- ...(startElement && {
2991
- ps: `calc(var(--input-height) - ${startOffset})`,
2992
- }),
2993
- ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
2994
- // @ts-expect-error chakra generated files
2995
- ...children.props,
2996
- }), endElement && (jsx(InputElement, { placement: "end", ...endElementProps, children: endElement }))] }));
2997
- });
2998
-
2999
- const GlobalFilter = () => {
3169
+ const TableBodySkeleton = ({ showSelector = false, canResize = true, }) => {
3170
+ 'use no memo';
3000
3171
  const { table } = useDataTableContext();
3001
- const [searchTerm, setSearchTerm] = useState("");
3002
- const debouncedSearchTerm = useDebounce(searchTerm, 500);
3003
- useEffect(() => {
3004
- const searchHN = async () => {
3005
- table.setGlobalFilter(debouncedSearchTerm);
3172
+ const SELECTION_BOX_WIDTH = 20;
3173
+ const [hoveredRow, setHoveredRow] = useState(-1);
3174
+ const handleRowHover = (index) => {
3175
+ setHoveredRow(index);
3176
+ };
3177
+ const getTdProps = (column) => {
3178
+ const tdProps = column.getIsPinned()
3179
+ ? {
3180
+ left: showSelector
3181
+ ? `${column.getStart('left') + SELECTION_BOX_WIDTH + table.getDensityValue() * 2}px`
3182
+ : `${column.getStart('left')}px`,
3183
+ position: 'relative',
3184
+ }
3185
+ : {};
3186
+ return tdProps;
3187
+ };
3188
+ const getTrProps = ({ hoveredRow, index, }) => {
3189
+ if (hoveredRow === -1) {
3190
+ return {};
3191
+ }
3192
+ if (hoveredRow === index) {
3193
+ return {
3194
+ opacity: '1',
3195
+ };
3196
+ }
3197
+ return {
3198
+ opacity: '0.8',
3006
3199
  };
3007
- searchHN();
3008
- }, [debouncedSearchTerm]);
3009
- return (jsx(Fragment, { children: jsx(InputGroup, { flex: "1", startElement: jsx(MdSearch, {}), children: jsx(Input, { placeholder: "Outline", variant: "outline", onChange: (e) => {
3010
- setSearchTerm(e.target.value);
3011
- } }) }) }));
3012
- };
3013
-
3014
- const ReloadButton = ({ text = "Reload", variant = "icon", }) => {
3015
- const { url } = useDataTableServerContext();
3016
- const queryClient = useQueryClient();
3017
- if (variant === "icon") {
3018
- return (jsx(Tooltip, { showArrow: true, content: "This is the tooltip content", children: jsx(Button, { variant: "ghost", onClick: () => {
3019
- queryClient.invalidateQueries({ queryKey: [url] });
3020
- }, "aria-label": "refresh", children: jsx(IoReload, {}) }) }));
3021
- }
3022
- return (jsxs(Button, { variant: "ghost", onClick: () => {
3023
- queryClient.invalidateQueries({ queryKey: [url] });
3024
- }, children: [jsx(IoReload, {}), " ", text] }));
3025
- };
3026
-
3027
- const FilterOptions = ({ column }) => {
3028
- const { table } = useDataTableContext();
3029
- const tableColumn = table.getColumn(column);
3030
- const options = tableColumn?.columnDef.meta?.filterOptions ?? [];
3031
- return (jsx(Fragment, { children: options.map((option) => {
3032
- const selected = table.getColumn(column)?.getFilterValue() === option;
3033
- return (jsxs(Button$1, { size: "sm", onClick: () => {
3034
- if (selected) {
3035
- table.setColumnFilters((state) => {
3036
- return state.filter((filter) => {
3037
- return filter.id !== column;
3038
- });
3039
- });
3040
- return;
3041
- }
3042
- table.getColumn(column)?.setFilterValue(option);
3043
- }, variant: selected ? "solid" : "outline", display: "flex", gap: "0.25rem", children: [option, selected && jsx(MdClose, {})] }, option));
3200
+ };
3201
+ // Get the number of skeleton rows based on current pageSize
3202
+ const pageSize = table.getState().pagination.pageSize;
3203
+ const visibleColumns = table.getVisibleLeafColumns();
3204
+ return (jsx(Table$1.Body, { children: Array.from({ length: pageSize }).map((_, rowIndex) => {
3205
+ return (jsxs(Table$1.Row, { display: 'flex', zIndex: 1, onMouseEnter: () => handleRowHover(rowIndex), onMouseLeave: () => handleRowHover(-1), ...getTrProps({ hoveredRow, index: rowIndex }), children: [showSelector && jsx(TableRowSelectorSkeleton, {}), visibleColumns.map((column, colIndex) => {
3206
+ return (jsx(Table$1.Cell, { padding: `${table.getDensityValue()}px`,
3207
+ // styling resize and pinning start
3208
+ flex: `${canResize ? '0' : '1'} 0 ${column.getSize()}px`,
3209
+ // this is to avoid the cell from being too wide
3210
+ minWidth: `0`, color: {
3211
+ base: 'colorPalette.900',
3212
+ _dark: 'colorPalette.100',
3213
+ },
3214
+ bg: { base: 'colorPalette.50', _dark: 'colorPalette.950' }, ...getTdProps(column), children: jsx(Skeleton, { height: "20px", width: "80%" }) }, `chakra-table-skeleton-cell-${rowIndex}-${colIndex}`));
3215
+ })] }, `chakra-table-skeleton-row-${rowIndex}`));
3044
3216
  }) }));
3045
3217
  };
3046
-
3047
- const TableFilterTags = () => {
3218
+ const TableRowSelectorSkeleton = () => {
3048
3219
  const { table } = useDataTableContext();
3049
- return (jsx(Flex, { gap: "0.5rem", flexFlow: "wrap", children: table.getState().columnFilters.map(({ id, value }) => {
3050
- return (jsx(Tag, { gap: "0.5rem", closable: true, cursor: "pointer", onClick: () => {
3051
- table.setColumnFilters(table.getState().columnFilters.filter((filter) => {
3052
- return filter.value != value;
3053
- }));
3054
- }, children: `${id}: ${value}` }, `${id}-${value}`));
3055
- }) }));
3056
- };
3057
-
3058
- const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsx(Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterOptions = [], extraItems = jsx(Fragment, {}), loading = false, hasError = false, }) => {
3059
- const { translate } = useDataTableContext();
3060
- return (jsxs(Grid, { templateRows: "auto 1fr auto", width: fitTableWidth ? "fit-content" : "100%", height: fitTableHeight ? "fit-content" : "100%", gap: "0.5rem", children: [jsxs(Flex, { flexFlow: "column", gap: 2, children: [jsxs(Flex, { justifyContent: "space-between", children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: "0.5rem", alignItems: "center", justifySelf: "end", children: [loading && jsx(Spinner, { size: "sm" }), hasError && (jsx(Tooltip, { content: translate.t("hasError"), children: jsx(Icon, { as: BsExclamationCircleFill, color: "red.400" }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), filterOptions.length > 0 && (jsx(Flex, { flexFlow: "column", gap: "0.5rem", children: filterOptions.map((column) => {
3061
- return (jsxs(Flex, { alignItems: "center", flexFlow: "wrap", gap: "0.5rem", children: [showFilterName && jsxs(Text, { children: [column, ":"] }), jsx(FilterOptions, { column: column })] }, column));
3062
- }) })), showFilterTags && (jsx(Flex, { children: jsx(TableFilterTags, {}) }))] }), jsx(Grid, { overflow: "auto", backgroundColor: "gray.50", _dark: {
3063
- backgroundColor: "gray.900",
3064
- }, children: children }), jsxs(Flex, { justifyContent: "space-between", children: [jsxs(Flex, { gap: "1rem", alignItems: "center", children: [showPageSizeControl && jsx(PageSizeControl, {}), showPageCountText && (jsxs(Flex, { children: [jsx(Text, { paddingRight: "0.5rem", children: translate.t("rowcount.total") }), jsx(RowCountText, {})] }))] }), jsx(Box, { justifySelf: "end", children: showPagination && jsx(Pagination, {}) })] })] }));
3220
+ const SELECTION_BOX_WIDTH = 20;
3221
+ return (jsx(Table$1.Cell, { padding: `${table.getDensityValue()}px`, display: 'grid', color: {
3222
+ base: 'colorPalette.900',
3223
+ _dark: 'colorPalette.100',
3224
+ },
3225
+ bg: { base: 'colorPalette.50', _dark: 'colorPalette.950' }, justifyItems: 'center', alignItems: 'center', children: jsx(Skeleton, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px` }) }));
3065
3226
  };
3066
3227
 
3067
- const TableFooter = ({ pinnedBgColor = { light: "gray.50", dark: "gray.700" }, showSelector = false, alwaysShowSelector = true, }) => {
3228
+ const TableFooter = ({ showSelector = false, alwaysShowSelector = true, }) => {
3068
3229
  const table = useDataTableContext().table;
3069
3230
  const SELECTION_BOX_WIDTH = 20;
3070
3231
  const [hoveredCheckBox, setHoveredCheckBox] = useState(false);
@@ -3083,65 +3244,62 @@ const TableFooter = ({ pinnedBgColor = { light: "gray.50", dark: "gray.700" }, s
3083
3244
  }
3084
3245
  return false;
3085
3246
  };
3086
- const getThProps = (header) => {
3087
- const thProps = header.column.getIsPinned()
3088
- ? {
3089
- left: showSelector
3090
- ? `${header.getStart("left") + SELECTION_BOX_WIDTH + table.getDensityValue() * 2}px`
3091
- : `${header.getStart("left") + table.getDensityValue() * 2}px`,
3092
- background: pinnedBgColor.light,
3093
- position: "sticky",
3094
- zIndex: 1,
3095
- _dark: {
3096
- backgroundColor: pinnedBgColor.dark,
3097
- },
3098
- }
3099
- : {};
3100
- return thProps;
3101
- };
3102
- return (jsx(Table$1.Footer, { children: table.getFooterGroups().map((footerGroup) => (jsxs(Table$1.Row, { display: "flex", children: [showSelector && (jsxs(Table$1.Header
3103
- // styling resize and pinning start
3104
- , {
3105
- // styling resize and pinning start
3106
- padding: `${table.getDensityValue()}px`, ...(table.getIsSomeColumnsPinned("left")
3107
- ? {
3108
- left: `0px`,
3109
- backgroundColor: pinnedBgColor.light,
3110
- position: "sticky",
3111
- zIndex: 1,
3112
- _dark: { backgroundColor: pinnedBgColor.dark },
3113
- }
3114
- : {}),
3115
- // styling resize and pinning end
3116
- onMouseEnter: () => handleRowHover(true), onMouseLeave: () => handleRowHover(false), display: "grid", children: [isCheckBoxVisible() && (jsx(Box, { margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, isChecked: table.getIsAllRowsSelected(),
3247
+ return (jsx(Table$1.Footer, { children: table.getFooterGroups().map((footerGroup) => (jsxs(Table$1.Row, { display: "flex", children: [showSelector && (jsxs(Table$1.Header, { padding: `${table.getDensityValue()}px`, onMouseEnter: () => handleRowHover(true), onMouseLeave: () => handleRowHover(false), display: "grid", children: [isCheckBoxVisible() && (jsx(Box, { margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, isChecked: table.getIsAllRowsSelected(),
3117
3248
  // indeterminate: table.getIsSomeRowsSelected(),
3118
3249
  onChange: table.getToggleAllRowsSelectedHandler() }) })), !isCheckBoxVisible() && (jsx(Box, { as: "span", margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px` }))] })), footerGroup.headers.map((header) => (jsx(Table$1.Cell, { padding: "0", columnSpan: `${header.colSpan}`,
3119
3250
  // styling resize and pinning start
3120
- maxWidth: `${header.getSize()}px`, width: `${header.getSize()}px`, display: "grid", ...getThProps(header), children: jsx(MenuRoot$1, { children: jsx(MenuTrigger$1, { asChild: true, children: jsx(Box, { padding: `${table.getDensityValue()}px`, display: "flex", alignItems: "center", justifyContent: "start", borderRadius: "0rem", _hover: { backgroundColor: "gray.100" }, children: jsxs(Flex, { gap: "0.5rem", alignItems: "center", children: [header.isPlaceholder
3251
+ maxWidth: `${header.getSize()}px`, width: `${header.getSize()}px`, display: "grid", children: jsx(MenuRoot$1, { children: jsx(MenuTrigger$1, { asChild: true, children: jsx(Box, { padding: `${table.getDensityValue()}px`, display: "flex", alignItems: "center", justifyContent: "start", borderRadius: "0rem", children: jsxs(Flex, { gap: "0.5rem", alignItems: "center", children: [header.isPlaceholder
3121
3252
  ? null
3122
- : flexRender(header.column.columnDef.footer, header.getContext()), jsx(Box, { children: header.column.getCanSort() && (jsxs(Fragment, { children: [header.column.getIsSorted() === false && (
3123
- // <UpDownIcon />
3124
- jsx(Fragment, {})), header.column.getIsSorted() === "asc" && (jsx(BiUpArrow, {})), header.column.getIsSorted() === "desc" && (jsx(BiDownArrow, {}))] })) })] }) }) }) }) }, `chakra-table-footer-${header.column.id}-${footerGroup.id}`)))] }, `chakra-table-footergroup-${footerGroup.id}`))) }));
3253
+ : flexRender(header.column.columnDef.footer, header.getContext()), jsx(Box, { children: header.column.getCanSort() && (jsxs(Fragment, { children: [header.column.getIsSorted() === false && jsx(Fragment, {}), header.column.getIsSorted() === "asc" && (jsx(BiUpArrow, {})), header.column.getIsSorted() === "desc" && (jsx(BiDownArrow, {}))] })) })] }) }) }) }) }, `chakra-table-footer-${header.column.id}-${footerGroup.id}`)))] }, `chakra-table-footergroup-${footerGroup.id}`))) }));
3125
3254
  };
3126
3255
 
3127
- const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dark: "gray.700" }, showSelector = false, isSticky = true, alwaysShowSelector = true, tHeadProps = {}, }) => {
3256
+ // Default text values
3257
+ const DEFAULT_HEADER_TEXTS = {
3258
+ pinColumn: "Pin Column",
3259
+ cancelPin: "Cancel Pin",
3260
+ sortAscending: "Sort Ascending",
3261
+ sortDescending: "Sort Descending",
3262
+ clearSorting: "Clear Sorting",
3263
+ };
3264
+ /**
3265
+ * TableHeader component with configurable text strings.
3266
+ *
3267
+ * @example
3268
+ * // Using default texts
3269
+ * <TableHeader />
3270
+ *
3271
+ * @example
3272
+ * // Customizing default texts for all columns
3273
+ * <TableHeader
3274
+ * defaultTexts={{
3275
+ * pinColumn: "Pin This Column",
3276
+ * sortAscending: "Sort A-Z"
3277
+ * }}
3278
+ * />
3279
+ *
3280
+ * @example
3281
+ * // Customizing texts per column via meta
3282
+ * const columns = [
3283
+ * columnHelper.accessor("name", {
3284
+ * header: "Name",
3285
+ * meta: {
3286
+ * headerTexts: {
3287
+ * pinColumn: "Pin Name Column",
3288
+ * sortAscending: "Sort Names A-Z"
3289
+ * }
3290
+ * }
3291
+ * })
3292
+ * ];
3293
+ */
3294
+ const TableHeader = ({ canResize = true, showSelector = false, isSticky = true, tableHeaderProps = {}, tableRowProps = {}, defaultTexts = {}, }) => {
3128
3295
  const { table } = useDataTableContext();
3129
3296
  const SELECTION_BOX_WIDTH = 20;
3130
- const [hoveredCheckBox, setHoveredCheckBox] = useState(false);
3131
- const handleRowHover = (isHovered) => {
3132
- setHoveredCheckBox(isHovered);
3133
- };
3134
- const isCheckBoxVisible = () => {
3135
- if (alwaysShowSelector) {
3136
- return true;
3137
- }
3138
- if (table.getIsAllRowsSelected()) {
3139
- return true;
3140
- }
3141
- if (hoveredCheckBox) {
3142
- return true;
3143
- }
3144
- return false;
3297
+ // Merge default texts with provided defaults
3298
+ const mergedDefaultTexts = { ...DEFAULT_HEADER_TEXTS, ...defaultTexts };
3299
+ // Helper function to get text for a specific header
3300
+ const getHeaderText = (header, key) => {
3301
+ const columnMeta = header.column.columnDef.meta;
3302
+ return columnMeta?.headerTexts?.[key] || mergedDefaultTexts[key];
3145
3303
  };
3146
3304
  const getThProps = (header) => {
3147
3305
  const thProps = header.column.getIsPinned()
@@ -3149,12 +3307,8 @@ const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dar
3149
3307
  left: showSelector
3150
3308
  ? `${header.getStart("left") + SELECTION_BOX_WIDTH + table.getDensityValue() * 2}px`
3151
3309
  : `${header.getStart("left")}px`,
3152
- background: pinnedBgColor.light,
3153
3310
  position: "sticky",
3154
3311
  zIndex: 100 + 1,
3155
- _dark: {
3156
- backgroundColor: pinnedBgColor.dark,
3157
- },
3158
3312
  }
3159
3313
  : {};
3160
3314
  return thProps;
@@ -3163,21 +3317,13 @@ const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dar
3163
3317
  position: "sticky",
3164
3318
  top: 0,
3165
3319
  };
3166
- return (jsx(Table$1.Header, { ...(isSticky ? stickyProps : {}), ...tHeadProps, children: table.getHeaderGroups().map((headerGroup) => (jsxs(Table$1.Row, { display: "flex", children: [showSelector && (jsxs(Table$1.ColumnHeader
3167
- // styling resize and pinning start
3168
- , { ...(table.getIsSomeColumnsPinned("left")
3169
- ? {
3170
- left: `0px`,
3171
- backgroundColor: pinnedBgColor.light,
3172
- position: "sticky",
3173
- zIndex: 1,
3174
- _dark: { backgroundColor: pinnedBgColor.dark },
3175
- }
3176
- : {}),
3177
- // styling resize and pinning end
3178
- padding: `${table.getDensityValue()}px`, onMouseEnter: () => handleRowHover(true), onMouseLeave: () => handleRowHover(false), display: "grid", children: [isCheckBoxVisible() && (jsx(Box, { margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, isChecked: table.getIsAllRowsSelected(),
3179
- // indeterminate: table.getIsSomeRowsSelected(),
3180
- onChange: table.getToggleAllRowsSelectedHandler() }) })), !isCheckBoxVisible() && (jsx(Box, { as: "span", margin: "0rem", display: "grid", justifyItems: "center", alignItems: "center", width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px` }))] })), headerGroup.headers.map((header) => {
3320
+ return (jsx(Table$1.Header, { ...(isSticky ? stickyProps : {}), bgColor: "transparent", ...tableHeaderProps, children: table.getHeaderGroups().map((headerGroup) => (jsxs(Table$1.Row, { display: "flex", bgColor: "transparent", ...tableRowProps, children: [showSelector && (jsx(Table$1.ColumnHeader, { padding: `${table.getDensityValue()}px`, display: "grid", color: {
3321
+ base: "colorPalette.900",
3322
+ _dark: "colorPalette.100",
3323
+ },
3324
+ bg: { base: "colorPalette.50", _dark: "colorPalette.950" }, justifyItems: "center", alignItems: "center", children: jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, checked: table.getIsAllRowsSelected(),
3325
+ // indeterminate: table.getIsSomeRowsSelected(),
3326
+ onChange: table.getToggleAllRowsSelectedHandler() }) })), headerGroup.headers.map((header) => {
3181
3327
  const resizeProps = {
3182
3328
  onMouseDown: header.getResizeHandler(),
3183
3329
  onTouchStart: header.getResizeHandler(),
@@ -3185,18 +3331,32 @@ const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dar
3185
3331
  };
3186
3332
  return (jsxs(Table$1.ColumnHeader, { padding: 0, columnSpan: `${header.colSpan}`,
3187
3333
  // styling resize and pinning start
3188
- flex: `${canResize ? "0" : "1"} 0 ${header.column.getSize()}px`, display: "grid", gridTemplateColumns: "1fr auto", zIndex: 1500 + header.index, ...getThProps(header), children: [jsxs(MenuRoot, { children: [jsx(MenuTrigger, { asChild: true, children: jsx(Flex, { padding: `${table.getDensityValue()}px`, alignItems: "center", justifyContent: "start", borderRadius: "0rem", overflow: "auto", _hover: {
3189
- backgroundColor: "gray.100",
3190
- _dark: {
3191
- backgroundColor: "gray.700",
3334
+ flex: `${canResize ? "0" : "1"} 0 ${header.column.getSize()}px`, display: "grid", gridTemplateColumns: "1fr auto", zIndex: 1500 + header.index, color: {
3335
+ base: "colorPalette.800",
3336
+ _dark: "colorPalette.200",
3337
+ },
3338
+ bg: { base: "colorPalette.100", _dark: "colorPalette.900" }, ...getThProps(header), children: [jsxs(MenuRoot, { children: [jsx(MenuTrigger, { asChild: true, children: jsx(Flex, { padding: `${table.getDensityValue()}px`, alignItems: "center", justifyContent: "start", borderRadius: "0rem", overflow: "auto", color: {
3339
+ base: "colorPalette.800",
3340
+ _dark: "colorPalette.200",
3341
+ _hover: {
3342
+ base: "colorPalette.700",
3343
+ _dark: "colorPalette.300",
3344
+ },
3345
+ },
3346
+ bg: {
3347
+ base: "colorPalette.100",
3348
+ _dark: "colorPalette.900",
3349
+ _hover: {
3350
+ base: "colorPalette.200",
3351
+ _dark: "colorPalette.800",
3192
3352
  },
3193
3353
  }, children: jsxs(Flex, { gap: "0.5rem", alignItems: "center", children: [header.isPlaceholder
3194
3354
  ? null
3195
3355
  : flexRender(header.column.columnDef.header, header.getContext()), jsx(Box, { children: header.column.getCanSort() && (jsxs(Fragment, { children: [header.column.getIsSorted() === false && jsx(Fragment, {}), header.column.getIsSorted() === "asc" && (jsx(BiUpArrow, {})), header.column.getIsSorted() === "desc" && (jsx(BiDownArrow, {}))] })) }), jsx(Box, { children: header.column.getIsFiltered() && jsx(MdFilterListAlt, {}) })] }) }) }), jsxs(MenuContent, { children: [!header.column.getIsPinned() && (jsx(MenuItem, { asChild: true, value: "pin-column", children: jsxs(Button, { variant: "ghost", onClick: () => {
3196
3356
  header.column.pin("left");
3197
- }, children: [jsx(MdPushPin, {}), "Pin Column"] }) })), header.column.getIsPinned() && (jsx(MenuItem, { asChild: true, value: "cancel-pin", children: jsxs(Button, { variant: "ghost", onClick: () => {
3357
+ }, children: [jsx(MdPushPin, {}), getHeaderText(header, "pinColumn")] }) })), header.column.getIsPinned() && (jsx(MenuItem, { asChild: true, value: "cancel-pin", children: jsxs(Button, { variant: "ghost", onClick: () => {
3198
3358
  header.column.pin(false);
3199
- }, children: [jsx(MdCancel, {}), "Cancel Pin"] }) })), header.column.getCanSort() && (jsxs(Fragment, { children: [jsx(MenuItem, { asChild: true, value: "sort-ascend", children: jsxs(Button, { variant: "ghost", onClick: () => {
3359
+ }, children: [jsx(MdCancel, {}), getHeaderText(header, "cancelPin")] }) })), header.column.getCanSort() && (jsxs(Fragment, { children: [jsx(MenuItem, { asChild: true, value: "sort-ascend", children: jsxs(Button, { variant: "ghost", onClick: () => {
3200
3360
  table.setSorting((state) => {
3201
3361
  return [
3202
3362
  ...state.filter((column) => {
@@ -3205,7 +3365,7 @@ const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dar
3205
3365
  { id: header.id, desc: false },
3206
3366
  ];
3207
3367
  });
3208
- }, children: [jsx(GrAscend, {}), "Sort Ascending"] }) }), jsx(MenuItem, { asChild: true, value: "sort-descend", children: jsxs(Button, { variant: "ghost", onClick: () => {
3368
+ }, children: [jsx(GrAscend, {}), getHeaderText(header, "sortAscending")] }) }), jsx(MenuItem, { asChild: true, value: "sort-descend", children: jsxs(Button, { variant: "ghost", onClick: () => {
3209
3369
  table.setSorting((state) => {
3210
3370
  return [
3211
3371
  ...state.filter((column) => {
@@ -3214,42 +3374,53 @@ const TableHeader = ({ canResize = true, pinnedBgColor = { light: "gray.50", dar
3214
3374
  { id: header.id, desc: true },
3215
3375
  ];
3216
3376
  });
3217
- }, children: [jsx(GrDescend, {}), "Sort Descending"] }) }), header.column.getIsSorted() && (jsx(MenuItem, { asChild: true, value: "sort-descend", children: jsxs(Button, { variant: "ghost", onClick: () => {
3377
+ }, children: [jsx(GrDescend, {}), getHeaderText(header, "sortDescending")] }) }), header.column.getIsSorted() && (jsx(MenuItem, { asChild: true, value: "clear-sorting", children: jsxs(Button, { variant: "ghost", onClick: () => {
3218
3378
  header.column.clearSorting();
3219
- }, children: [jsx(MdClear, {}), "Clear Sorting"] }) }))] }))] })] }), canResize && (jsx(Box, { borderRight: "0.2rem solid", borderRightColor: header.column.getIsResizing() ? "gray.700" : "transparent", position: "relative", right: "0.1rem", width: "2px", height: "100%", userSelect: "none", style: { touchAction: "none" }, _hover: {
3379
+ }, children: [jsx(MdClear, {}), getHeaderText(header, "clearSorting")] }) }))] }))] })] }), canResize && (jsx(Box, { borderRight: "0.2rem solid", borderRightColor: header.column.getIsResizing()
3380
+ ? "colorPalette.700"
3381
+ : "transparent", position: "relative", right: "0.1rem", width: "2px", height: "100%", userSelect: "none", style: { touchAction: "none" }, _hover: {
3220
3382
  borderRightColor: header.column.getIsResizing()
3221
- ? "gray.700"
3222
- : "gray.400",
3383
+ ? "colorPalette.700"
3384
+ : "colorPalette.400",
3223
3385
  }, ...resizeProps }))] }, `chakra-table-header-${header.id}`));
3224
3386
  })] }, `chakra-table-headergroup-${headerGroup.id}`))) }));
3225
3387
  };
3226
3388
 
3227
- const EmptyState = React.forwardRef(function EmptyState(props, ref) {
3228
- const { title, description, icon, children, ...rest } = props;
3229
- return (jsx(EmptyState$2.Root, { ref: ref, ...rest, children: jsxs(EmptyState$2.Content, { children: [icon && (jsx(EmptyState$2.Indicator, { children: icon })), description ? (jsxs(VStack, { textAlign: "center", children: [jsx(EmptyState$2.Title, { children: title }), jsx(EmptyState$2.Description, { children: description })] })) : (jsx(EmptyState$2.Title, { children: title })), children] }) }));
3230
- });
3231
-
3232
- const EmptyResult = (jsx(EmptyState, { icon: jsx(HiColorSwatch, {}), title: "No results found", description: "Try adjusting your search", children: jsxs(List.Root, { variant: "marker", children: [jsx(List.Item, { children: "Try removing filters" }), jsx(List.Item, { children: "Try different keywords" })] }) }));
3233
- const Table = ({ children, emptyComponent = EmptyResult, canResize = true, ...props }) => {
3234
- const { table } = useDataTableContext();
3235
- if (table.getRowModel().rows.length <= 0) {
3236
- return emptyComponent;
3389
+ const DefaultTable = ({ showFooter = false, tableProps = {}, tableHeaderProps = {}, tableBodyProps = {}, tableFooterProps = {}, controlProps = {}, variant = '', isLoading = false, }) => {
3390
+ if (variant === 'greedy') {
3391
+ const bodyComponent = isLoading ? (jsx(TableBodySkeleton, { showSelector: tableBodyProps.showSelector, canResize: false })) : (jsx(TableBody, { ...tableBodyProps, canResize: false, ...tableBodyProps }));
3392
+ return (jsx(TableControls, { ...controlProps, children: jsxs(Table, { canResize: false, showLoading: isLoading, ...tableProps, children: [jsx(TableHeader, { canResize: false, ...tableHeaderProps }), bodyComponent, showFooter && (jsx(TableFooter, { canResize: false, ...tableFooterProps }))] }) }));
3237
3393
  }
3238
- return (jsx(Table$1.Root, { stickyHeader: true, variant: "outline", width: canResize ? table.getCenterTotalSize() : undefined, display: "grid", alignContent: "start", overflowY: "auto", ...props, children: children }));
3394
+ const bodyComponent = isLoading ? (jsx(TableBodySkeleton, { showSelector: tableBodyProps.showSelector, canResize: tableBodyProps.canResize })) : (jsx(TableBody, { ...tableBodyProps }));
3395
+ return (jsx(TableControls, { ...controlProps, children: jsxs(Table, { showLoading: isLoading, ...tableProps, children: [jsx(TableHeader, { ...tableHeaderProps }), bodyComponent, showFooter && jsx(TableFooter, { ...tableFooterProps })] }) }));
3239
3396
  };
3240
3397
 
3241
- const DefaultTable = ({ showFooter = false, tableProps = {}, tableHeaderProps = {}, tableBodyProps = {}, controlProps = {}, tableFooterProps = {}, variant = "", }) => {
3242
- if (variant === "greedy") {
3243
- return (jsx(TableControls, { ...controlProps, children: jsxs(Table, { canResize: false, ...{ ...tableProps }, children: [jsx(TableHeader, { canResize: false, ...tableHeaderProps }), jsx(TableBody, { canResize: false, ...tableBodyProps }), showFooter && (jsx(TableFooter, { canResize: false, ...tableFooterProps }))] }) }));
3244
- }
3245
- return (jsx(TableControls, { ...controlProps, children: jsxs(Table, { ...tableProps, children: [jsx(TableHeader, { ...tableHeaderProps }), jsx(TableBody, { ...tableBodyProps }), showFooter && jsx(TableFooter, { ...tableFooterProps })] }) }));
3398
+ /**
3399
+ * DefaultTableServer is a wrapper around DefaultTable that automatically
3400
+ * detects server-side loading state from DataTableServerContext.
3401
+ *
3402
+ * Use this component when working with DataTableServer to automatically
3403
+ * show skeleton loading state during data fetching.
3404
+ *
3405
+ * @example
3406
+ * ```tsx
3407
+ * <DataTableServer columns={columns} {...datatableServer}>
3408
+ * <DefaultTableServer />
3409
+ * </DataTableServer>
3410
+ * ```
3411
+ */
3412
+ const DefaultTableServer = ({ isLoading: isLoadingOverride, ...props }) => {
3413
+ // Automatically detect loading state from server context
3414
+ const serverContext = useDataTableServerContext();
3415
+ const isLoading = isLoadingOverride ?? serverContext?.query?.isLoading ?? false;
3416
+ return jsx(DefaultTable, { ...props, isLoading: isLoading });
3246
3417
  };
3247
3418
 
3248
- const TableCardContainer = ({ children, variant = "", ...props }) => {
3419
+ const TableCardContainer = ({ children, variant = "", gap = "1rem", gridTemplateColumns = "repeat(auto-fit, minmax(20rem, 1fr))", direction = "row", ...props }) => {
3249
3420
  if (variant === "carousel") {
3250
- return (jsx(Flex, { overflow: "scroll", gap: "1rem", children: children }));
3421
+ return (jsx(Flex, { overflow: "auto", gap: gap, direction: direction, ...props, children: children }));
3251
3422
  }
3252
- return (jsx(Grid, { gridTemplateColumns: "repeat(auto-fit, minmax(20rem, 1fr))", gap: "0.5rem", ...props, children: children }));
3423
+ return (jsx(Grid, { gridTemplateColumns: gridTemplateColumns, gap: gap, ...props, children: children }));
3253
3424
  };
3254
3425
 
3255
3426
  const DefaultCardTitle = () => {
@@ -3278,8 +3449,8 @@ const TableComponent = ({ render = () => {
3278
3449
  };
3279
3450
 
3280
3451
  const TableLoadingComponent = ({ render, }) => {
3281
- const { loading } = useDataTableContext();
3282
- return jsx(Fragment, { children: render(loading) });
3452
+ const { query } = useDataTableServerContext();
3453
+ return jsx(Fragment, { children: render(query.isLoading) });
3283
3454
  };
3284
3455
 
3285
3456
  const SelectAllRowsToggle = ({ selectAllIcon = jsx(MdOutlineChecklist, {}), clearAllIcon = jsx(MdClear, {}), selectAllText = "", clearAllText = "", }) => {
@@ -3365,50 +3536,43 @@ const useDataTable = ({ default: { sorting: defaultSorting = [], pagination: def
3365
3536
  };
3366
3537
  };
3367
3538
 
3368
- const useDataTableServer = ({ url, default: { sorting: defaultSorting = [], pagination: defaultPagination = {
3369
- pageIndex: 0, //initial page index
3370
- pageSize: 10, //default page size
3371
- }, rowSelection: defaultRowSelection = {}, columnFilters: defaultColumnFilters = [], columnOrder: defaultColumnOrder = [], columnVisibility: defaultColumnVisibility = {}, globalFilter: defaultGlobalFilter = "", density: defaultDensity = "sm", } = {
3372
- sorting: [],
3373
- pagination: {
3539
+ const useDataTableServer = (props) => {
3540
+ const { url, default: defaultProps, keyPrefix, placeholderData, queryFn: customQueryFn, } = props;
3541
+ const { sorting: defaultSorting, pagination: defaultPagination, rowSelection: defaultRowSelection, columnFilters: defaultColumnFilters, columnOrder: defaultColumnOrder, columnVisibility: defaultColumnVisibility, globalFilter: defaultGlobalFilter, density: defaultDensity, } = defaultProps || {};
3542
+ const [sorting, setSorting] = useState(defaultSorting || []);
3543
+ const [columnFilters, setColumnFilters] = useState(defaultColumnFilters || []); // can set initial column filter state here
3544
+ const [pagination, setPagination] = useState(defaultPagination || {
3374
3545
  pageIndex: 0, //initial page index
3375
- pageSize: 10, //age size
3376
- },
3377
- rowSelection: {},
3378
- columnFilters: [],
3379
- columnOrder: [],
3380
- columnVisibility: {},
3381
- globalFilter: "",
3382
- density: "sm",
3383
- }, keyPrefix, }) => {
3384
- const [sorting, setSorting] = useState(defaultSorting);
3385
- const [columnFilters, setColumnFilters] = useState(defaultColumnFilters); // can set initial column filter state here
3386
- const [pagination, setPagination] = useState(defaultPagination);
3387
- const [rowSelection, setRowSelection] = useState(defaultRowSelection);
3388
- const [columnOrder, setColumnOrder] = useState(defaultColumnOrder);
3389
- const [globalFilter, setGlobalFilter] = useState(defaultGlobalFilter);
3390
- const [density, setDensity] = useState(defaultDensity);
3391
- const [columnVisibility, setColumnVisibility] = useState(defaultColumnVisibility);
3546
+ pageSize: 10, //default page size
3547
+ });
3548
+ const [rowSelection, setRowSelection] = useState(defaultRowSelection || {});
3549
+ const [columnOrder, setColumnOrder] = useState(defaultColumnOrder || []);
3550
+ const [globalFilter, setGlobalFilter] = useState(defaultGlobalFilter || "");
3551
+ const [density, setDensity] = useState(defaultDensity || "sm");
3552
+ const [columnVisibility, setColumnVisibility] = useState(defaultColumnVisibility || {});
3553
+ const { pageSize, pageIndex } = pagination;
3392
3554
  const params = {
3393
- offset: pagination.pageIndex * pagination.pageSize,
3394
- limit: pagination.pageSize,
3555
+ offset: pageIndex * pageSize,
3556
+ limit: pageSize,
3395
3557
  sorting,
3396
3558
  where: columnFilters,
3397
3559
  searching: globalFilter,
3398
3560
  };
3561
+ const defaultQueryFn = async () => {
3562
+ if (!url) {
3563
+ throw new Error("url is required");
3564
+ }
3565
+ const response = await axios.get(url, {
3566
+ params,
3567
+ });
3568
+ return response.data;
3569
+ };
3399
3570
  const query = useQuery({
3400
3571
  queryKey: [url, JSON.stringify(params)],
3401
- queryFn: () => {
3402
- return axios
3403
- .get(url, {
3404
- params,
3405
- })
3406
- .then((res) => res.data);
3407
- },
3408
- placeholderData: {
3409
- count: 0,
3410
- data: [],
3411
- },
3572
+ queryFn: customQueryFn !== undefined
3573
+ ? () => customQueryFn(params)
3574
+ : defaultQueryFn,
3575
+ placeholderData,
3412
3576
  });
3413
3577
  const translate = useTranslation("", { keyPrefix });
3414
3578
  return {
@@ -3498,23 +3662,33 @@ const getColumns = ({ schema, include = [], ignore = [], width = [], meta = {},
3498
3662
  return columns;
3499
3663
  };
3500
3664
 
3501
- const TableDataDisplay = () => {
3502
- const { table, columns, translate } = useDataTableContext();
3503
- const { query } = useDataTableServerContext();
3504
- const { data } = query;
3505
- const columnDef = table._getColumnDefs();
3506
- console.log(columnDef, "glp");
3507
- console.log(columnDef, columns, table.getState().columnOrder, data, "glp");
3665
+ const TableDataDisplay = ({ colorPalette, emptyComponent, }) => {
3666
+ const { columns, translate, data } = useDataTableContext();
3508
3667
  const columnsMap = Object.fromEntries(columns.map((def) => {
3509
- return [def.accessorKey, def];
3668
+ const { accessorKey, id } = def;
3669
+ if (accessorKey) {
3670
+ return [accessorKey, def];
3671
+ }
3672
+ return [id, def];
3510
3673
  }));
3511
3674
  const columnHeaders = Object.keys(columnsMap);
3675
+ const totalWidths = columns
3676
+ .map(({ size }) => {
3677
+ if (!!size === false) {
3678
+ return 0;
3679
+ }
3680
+ if (typeof size === "number") {
3681
+ return size;
3682
+ }
3683
+ return 0;
3684
+ })
3685
+ .reduce((previous, current) => previous + current, 0);
3512
3686
  const columnWidths = columns
3513
3687
  .map(({ size }) => {
3514
3688
  if (!!size === false) {
3515
3689
  return "1fr";
3516
3690
  }
3517
- return `${size}px`;
3691
+ return `minmax(${size}px, ${(size / totalWidths) * 100}%)`;
3518
3692
  })
3519
3693
  .join(" ");
3520
3694
  console.log({ columnWidths }, "hadfg");
@@ -3523,48 +3697,55 @@ const TableDataDisplay = () => {
3523
3697
  overflow: "auto",
3524
3698
  paddingX: "2",
3525
3699
  py: "1",
3700
+ color: { base: "colorPalette.900", _dark: "colorPalette.100" },
3701
+ bgColor: { base: "colorPalette.50", _dark: "colorPalette.950" },
3526
3702
  borderBottomColor: { base: "colorPalette.200", _dark: "colorPalette.800" },
3527
3703
  borderBottomWidth: "1px",
3704
+ ...{ colorPalette },
3528
3705
  };
3529
- return (jsxs(Grid, { templateColumns: `${columnWidths}`, overflow: "auto", borderWidth: "1px", borderColor: { base: "colorPalette.200", _dark: "colorPalette.800" }, children: [jsx(Grid, { templateColumns: `${columnWidths}`, column: `1/span ${columns.length}`, bg: { base: "colorPalette.200", _dark: "colorPalette.800" }, children: columnHeaders.map((header) => {
3530
- return (jsx(Box, { flex: "1 0 0%", paddingX: "2", py: "1", children: translate.t(`column_header.${header}`) }));
3531
- }) }), data?.data.map((record) => {
3706
+ if (data.length <= 0) {
3707
+ return jsx(Fragment, { children: emptyComponent });
3708
+ }
3709
+ return (jsxs(Grid, { templateColumns: `${columnWidths}`, overflow: "auto", borderWidth: "1px", color: { base: "colorPalette.900", _dark: "colorPalette.100" }, borderColor: { base: "colorPalette.200", _dark: "colorPalette.800" }, colorPalette, children: [jsx(Grid, { templateColumns: `${columnWidths}`, column: `1/span ${columns.length}`, bg: { base: "colorPalette.200", _dark: "colorPalette.800" }, colorPalette, children: columnHeaders.map((header) => {
3710
+ return (jsx(Box, { flex: "1 0 0%", paddingX: "2", py: "1", overflow: "auto", textOverflow: "ellipsis", children: translate.t(`column_header.${header}`) }));
3711
+ }) }), data.map((record) => {
3532
3712
  return (jsx(Fragment, { children: columnHeaders.map((header) => {
3713
+ const { cell } = columnsMap[header];
3714
+ const value = record[header];
3533
3715
  if (!!record === false) {
3534
3716
  return (jsx(Box, { ...cellProps, children: translate.t(`column_cell.placeholder`) }));
3535
3717
  }
3536
- if (!!record[header] === false) {
3537
- return (jsx(Box, { ...cellProps, children: translate.t(`column_cell.placeholder`) }));
3718
+ if (cell) {
3719
+ return (jsx(Box, { ...cellProps, children: cell({ row: { original: record } }) }));
3538
3720
  }
3539
- if (typeof record[header] === "object") {
3540
- return (jsx(Box, { ...cellProps, children: jsx(RecordDisplay, { object: record[header] }) }));
3721
+ if (typeof value === "object") {
3722
+ return (jsx(Box, { ...cellProps, children: jsx(RecordDisplay, { object: value }) }));
3541
3723
  }
3542
- return jsx(Box, { ...cellProps, children: record[header] ?? "" });
3724
+ return jsx(Box, { ...cellProps, children: value });
3543
3725
  }) }));
3544
3726
  })] }));
3545
3727
  };
3546
3728
 
3547
- const AccordionItemTrigger = React.forwardRef(function AccordionItemTrigger(props, ref) {
3548
- const { children, indicatorPlacement = "end", ...rest } = props;
3549
- return (jsxs(Accordion.ItemTrigger, { ...rest, ref: ref, children: [indicatorPlacement === "start" && (jsx(Accordion.ItemIndicator, { rotate: { base: "-90deg", _open: "0deg" }, children: jsx(LuChevronDown, {}) })), jsx(HStack, { gap: "4", flex: "1", textAlign: "start", width: "full", children: children }), indicatorPlacement === "end" && (jsx(Accordion.ItemIndicator, { children: jsx(LuChevronDown, {}) }))] }));
3550
- });
3551
- const AccordionItemContent = React.forwardRef(function AccordionItemContent(props, ref) {
3552
- return (jsx(Accordion.ItemContent, { children: jsx(Accordion.ItemBody, { ...props, ref: ref }) }));
3553
- });
3554
- const AccordionRoot = Accordion.Root;
3555
- const AccordionItem = Accordion.Item;
3556
-
3557
3729
  //@ts-expect-error TODO: find appropriate type
3558
3730
  const SchemaFormContext = createContext({
3559
3731
  schema: {},
3560
- serverUrl: "",
3561
- requestUrl: "",
3732
+ serverUrl: '',
3733
+ requestUrl: '',
3562
3734
  order: [],
3563
3735
  ignore: [],
3564
3736
  include: [],
3565
3737
  onSubmit: async () => { },
3566
3738
  rowNumber: 0,
3567
3739
  requestOptions: {},
3740
+ timezone: 'Asia/Hong_Kong',
3741
+ displayConfig: {
3742
+ showSubmitButton: true,
3743
+ showResetButton: true,
3744
+ showTitle: true,
3745
+ },
3746
+ requireConfirmation: false,
3747
+ onFormSubmit: async () => { },
3748
+ ajvResolver: async () => ({ values: {}, errors: {} }),
3568
3749
  });
3569
3750
 
3570
3751
  const useSchemaContext = () => {
@@ -3575,6 +3756,202 @@ const clearEmptyString = (object) => {
3575
3756
  return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ""));
3576
3757
  };
3577
3758
 
3759
+ const validateData = (data, schema) => {
3760
+ const ajv = new Ajv({
3761
+ strict: false,
3762
+ allErrors: true,
3763
+ });
3764
+ addFormats(ajv);
3765
+ const validate = ajv.compile(schema);
3766
+ const validationResult = validate(data);
3767
+ const errors = validate.errors;
3768
+ console.log(errors, data);
3769
+ return {
3770
+ isValid: validationResult,
3771
+ validate,
3772
+ errors,
3773
+ };
3774
+ };
3775
+
3776
+ /**
3777
+ * Gets the schema node for a field by following the path from root schema
3778
+ */
3779
+ const getSchemaNodeForField = (schema, fieldPath) => {
3780
+ if (!fieldPath || fieldPath === '') {
3781
+ return schema;
3782
+ }
3783
+ const pathParts = fieldPath.split('.');
3784
+ let currentSchema = schema;
3785
+ for (const part of pathParts) {
3786
+ if (currentSchema &&
3787
+ currentSchema.properties &&
3788
+ currentSchema.properties[part] &&
3789
+ typeof currentSchema.properties[part] === 'object' &&
3790
+ currentSchema.properties[part] !== null) {
3791
+ currentSchema = currentSchema.properties[part];
3792
+ }
3793
+ else {
3794
+ return undefined;
3795
+ }
3796
+ }
3797
+ return currentSchema;
3798
+ };
3799
+ /**
3800
+ * Converts AJV error objects to react-hook-form field errors format
3801
+ */
3802
+ const convertAjvErrorsToFieldErrors = (errors, schema) => {
3803
+ if (!errors || errors.length === 0) {
3804
+ return {};
3805
+ }
3806
+ const fieldErrors = {};
3807
+ errors.forEach((error) => {
3808
+ let fieldName = '';
3809
+ // Special handling for required keyword: map to the specific missing property
3810
+ if (error.keyword === 'required') {
3811
+ const basePath = (error.instancePath || '')
3812
+ .replace(/^\//, '')
3813
+ .replace(/\//g, '.');
3814
+ const missingProperty = (error.params &&
3815
+ error.params.missingProperty);
3816
+ if (missingProperty) {
3817
+ fieldName = basePath
3818
+ ? `${basePath}.${missingProperty}`
3819
+ : missingProperty;
3820
+ }
3821
+ else {
3822
+ // Fallback to schemaPath conversion if missingProperty is unavailable
3823
+ fieldName = (error.schemaPath || '')
3824
+ .replace(/^#\//, '#.')
3825
+ .replace(/\//g, '.');
3826
+ }
3827
+ }
3828
+ else {
3829
+ const fieldPath = error.instancePath || error.schemaPath;
3830
+ if (fieldPath) {
3831
+ fieldName = fieldPath.replace(/^\//, '').replace(/\//g, '.');
3832
+ }
3833
+ }
3834
+ if (fieldName) {
3835
+ // Get the schema node for this field to check for custom error messages
3836
+ const fieldSchema = getSchemaNodeForField(schema, fieldName);
3837
+ const customMessage = fieldSchema?.errorMessages?.[error.keyword];
3838
+ // Provide helpful fallback message if no custom message is provided
3839
+ const fallbackMessage = customMessage ||
3840
+ `Missing error message for ${error.keyword}. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`;
3841
+ if (error.keyword === 'required') {
3842
+ // Required errors override any existing non-required errors for this field
3843
+ fieldErrors[fieldName] = {
3844
+ type: 'required',
3845
+ keyword: error.keyword,
3846
+ params: error.params,
3847
+ message: fallbackMessage,
3848
+ };
3849
+ }
3850
+ else {
3851
+ const existing = fieldErrors[fieldName];
3852
+ if (existing) {
3853
+ // Do not override required errors
3854
+ if (existing.type === 'required') {
3855
+ return;
3856
+ }
3857
+ // Combine messages if multiple errors for same field
3858
+ existing.message = existing.message
3859
+ ? `${existing.message}; ${fallbackMessage}`
3860
+ : fallbackMessage;
3861
+ }
3862
+ else {
3863
+ fieldErrors[fieldName] = {
3864
+ type: error.keyword,
3865
+ keyword: error.keyword,
3866
+ params: error.params,
3867
+ message: fallbackMessage,
3868
+ };
3869
+ }
3870
+ }
3871
+ }
3872
+ });
3873
+ return fieldErrors;
3874
+ };
3875
+ /**
3876
+ * AJV resolver for react-hook-form
3877
+ * Integrates AJV validation with react-hook-form's validation system
3878
+ */
3879
+ /**
3880
+ * Strips null, undefined, and empty string values from an object
3881
+ */
3882
+ const stripEmptyValues = (obj) => {
3883
+ if (obj === null || obj === undefined) {
3884
+ return undefined;
3885
+ }
3886
+ if (typeof obj === 'string' && obj.trim() === '') {
3887
+ return undefined;
3888
+ }
3889
+ if (Array.isArray(obj)) {
3890
+ const filtered = obj
3891
+ .map(stripEmptyValues)
3892
+ .filter((item) => item !== undefined);
3893
+ return filtered.length > 0 ? filtered : undefined;
3894
+ }
3895
+ if (typeof obj === 'object' && obj !== null) {
3896
+ const result = {};
3897
+ let hasValues = false;
3898
+ for (const [key, value] of Object.entries(obj)) {
3899
+ const cleanedValue = stripEmptyValues(value);
3900
+ if (cleanedValue !== undefined) {
3901
+ result[key] = cleanedValue;
3902
+ hasValues = true;
3903
+ }
3904
+ }
3905
+ return hasValues ? result : undefined;
3906
+ }
3907
+ return obj;
3908
+ };
3909
+ const ajvResolver = (schema) => {
3910
+ return async (values) => {
3911
+ try {
3912
+ // Strip empty values before validation
3913
+ const cleanedValues = stripEmptyValues(values);
3914
+ // Use empty object for validation if all values were stripped
3915
+ const valuesToValidate = cleanedValues === undefined ? {} : cleanedValues;
3916
+ const { isValid, errors } = validateData(valuesToValidate, schema);
3917
+ console.debug('AJV Validation Result:', {
3918
+ isValid,
3919
+ errors,
3920
+ cleanedValues,
3921
+ valuesToValidate,
3922
+ });
3923
+ if (isValid) {
3924
+ return {
3925
+ values: (cleanedValues || {}),
3926
+ errors: {},
3927
+ };
3928
+ }
3929
+ const fieldErrors = convertAjvErrorsToFieldErrors(errors, schema);
3930
+ console.debug('AJV Validation Failed:', {
3931
+ errors,
3932
+ fieldErrors,
3933
+ cleanedValues,
3934
+ valuesToValidate,
3935
+ });
3936
+ return {
3937
+ values: {},
3938
+ errors: fieldErrors,
3939
+ };
3940
+ }
3941
+ catch (error) {
3942
+ return {
3943
+ values: {},
3944
+ errors: {
3945
+ root: {
3946
+ type: 'validation',
3947
+ message: error instanceof Error ? error.message : 'Validation failed',
3948
+ },
3949
+ },
3950
+ };
3951
+ }
3952
+ };
3953
+ };
3954
+
3578
3955
  const idPickerSanityCheck = (column, foreign_key) => {
3579
3956
  if (!!foreign_key == false) {
3580
3957
  throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
@@ -3590,13 +3967,65 @@ const idPickerSanityCheck = (column, foreign_key) => {
3590
3967
  throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
3591
3968
  }
3592
3969
  };
3593
- const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, }) => {
3970
+ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, customSuccessRenderer, displayConfig = {
3971
+ showSubmitButton: true,
3972
+ showResetButton: true,
3973
+ showTitle: true,
3974
+ }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
3594
3975
  const [isSuccess, setIsSuccess] = useState(false);
3595
3976
  const [isError, setIsError] = useState(false);
3596
3977
  const [isSubmiting, setIsSubmiting] = useState(false);
3597
3978
  const [isConfirming, setIsConfirming] = useState(false);
3598
3979
  const [validatedData, setValidatedData] = useState();
3599
3980
  const [error, setError] = useState();
3981
+ const onBeforeSubmit = () => {
3982
+ setIsSubmiting(true);
3983
+ };
3984
+ const onAfterSubmit = () => {
3985
+ setIsSubmiting(false);
3986
+ };
3987
+ const onSubmitError = (error) => {
3988
+ setIsError(true);
3989
+ setError(error);
3990
+ };
3991
+ const onSubmitSuccess = () => {
3992
+ setIsSuccess(true);
3993
+ };
3994
+ const defaultOnSubmit = async (promise) => {
3995
+ try {
3996
+ console.log('onBeforeSubmit');
3997
+ onBeforeSubmit();
3998
+ await promise;
3999
+ console.log('onSubmitSuccess');
4000
+ onSubmitSuccess();
4001
+ }
4002
+ catch (error) {
4003
+ console.log('onSubmitError', error);
4004
+ onSubmitError(error);
4005
+ }
4006
+ finally {
4007
+ onAfterSubmit();
4008
+ }
4009
+ };
4010
+ const defaultSubmitPromise = (data) => {
4011
+ const options = {
4012
+ method: 'POST',
4013
+ url: `${serverUrl}`,
4014
+ data: clearEmptyString(data),
4015
+ ...requestOptions,
4016
+ };
4017
+ return axios.request(options);
4018
+ };
4019
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4020
+ const onFormSubmit = async (data) => {
4021
+ // AJV validation is now handled by react-hook-form resolver
4022
+ // This function will only be called if validation passes
4023
+ if (onSubmit === undefined) {
4024
+ await defaultOnSubmit(Promise.resolve(defaultSubmitPromise(data)));
4025
+ return;
4026
+ }
4027
+ await defaultOnSubmit(Promise.resolve(onSubmit(data)));
4028
+ };
3600
4029
  return (jsx(SchemaFormContext.Provider, { value: {
3601
4030
  schema,
3602
4031
  serverUrl,
@@ -3623,11 +4052,21 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3623
4052
  error,
3624
4053
  setError,
3625
4054
  getUpdatedData,
4055
+ customErrorRenderer,
4056
+ customSuccessRenderer,
4057
+ displayConfig,
4058
+ requireConfirmation,
4059
+ onFormSubmit,
4060
+ dateTimePickerLabels,
4061
+ idPickerLabels,
4062
+ enumPickerLabels,
4063
+ filePickerLabels,
4064
+ ajvResolver: ajvResolver(schema),
3626
4065
  }, children: jsx(FormProvider, { ...form, children: children }) }));
3627
4066
  };
3628
4067
 
3629
4068
  function removeIndex(str) {
3630
- return str.replace(/\.\d+\./g, '.');
4069
+ return str.replace(/\.\d+\./g, ".");
3631
4070
  }
3632
4071
 
3633
4072
  const ArrayRenderer = ({ schema, column, prefix, }) => {
@@ -3639,13 +4078,17 @@ const ArrayRenderer = ({ schema, column, prefix, }) => {
3639
4078
  const isRequired = required?.some((columnId) => columnId === column);
3640
4079
  const { formState: { errors }, setValue, watch, } = useFormContext();
3641
4080
  const fields = (watch(colLabel) ?? []);
3642
- return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: "1/span12", children: [`${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, isRequired && jsx("span", { children: "*" })] }), fields.map((field, index) => (jsxs(Flex, { flexFlow: "column", children: [jsx(Grid, { gap: "4", padding: "4", gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat("auto-fit", auto)`, children: jsx(SchemaRenderer, { column: `${index}`,
3643
- prefix: `${colLabel}.`,
3644
- schema: items }) }), jsx(Flex, { justifyContent: "end", children: jsx(Button$1, { variant: "ghost", onClick: () => {
3645
- setValue(colLabel, fields.filter((_, curIndex) => {
3646
- return curIndex === index;
3647
- }));
3648
- }, children: translate.t(removeIndex(`${colLabel}.remove`)) }) })] }, `${colLabel}.${index}`))), jsx(Flex, { children: jsx(Button$1, { onClick: () => {
4081
+ return (jsxs(Flex, { gridRow, gridColumn, flexFlow: "column", gap: 2, children: [jsxs(Box, { as: "label", children: [`${translate.t(removeIndex(`${colLabel}.field_label`))}`, isRequired && jsx("span", { children: "*" })] }), jsx(Flex, { flexFlow: "column", gap: 2, children: fields.map((field, index) => (jsxs(Grid, { gridTemplateColumns: '1fr auto', gap: 2, bgColor: { base: "colorPalette.100", _dark: "colorPalette.900" }, p: 2, borderRadius: 4, borderWidth: 1, borderColor: {
4082
+ base: "colorPalette.200",
4083
+ _dark: "colorPalette.800",
4084
+ }, children: [jsx(Grid, { gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: jsx(SchemaRenderer, { column: `${index}`,
4085
+ prefix: `${colLabel}.`,
4086
+ // @ts-expect-error find suitable types
4087
+ schema: { showLabel: false, ...(items ?? {}) } }) }), jsx(Flex, { justifyContent: "end", children: jsx(Button$1, { variant: "ghost", onClick: () => {
4088
+ setValue(colLabel, fields.filter((_, curIndex) => {
4089
+ return curIndex !== index;
4090
+ }));
4091
+ }, children: jsx(Icon, { children: jsx(CgTrash, {}) }) }) })] }, `${colLabel}.${index}`))) }), jsx(Flex, { children: jsx(Button$1, { onClick: () => {
3649
4092
  if (type === "number") {
3650
4093
  setValue(colLabel, [...fields, 0]);
3651
4094
  return;
@@ -3659,48 +4102,49 @@ const ArrayRenderer = ({ schema, column, prefix, }) => {
3659
4102
  return;
3660
4103
  }
3661
4104
  setValue(colLabel, [...fields, {}]);
3662
- }, children: translate.t(removeIndex(`${colLabel}.add`)) }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
4105
+ }, children: translate.t(removeIndex(`${colLabel}.add`)) }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
3663
4106
  };
3664
4107
 
3665
4108
  const Field = React.forwardRef(function Field(props, ref) {
3666
4109
  const { label, children, helperText, errorText, optionalText, ...rest } = props;
3667
- return (jsxs(Field$1.Root, { ref: ref, ...rest, children: [label && (jsxs(Field$1.Label, { children: [label, jsx(Field$1.RequiredIndicator, { fallback: optionalText })] })), children, helperText && (jsx(Field$1.HelperText, { children: helperText })), errorText && (jsx(Field$1.ErrorText, { children: errorText }))] }));
4110
+ return (jsxs(Field$1.Root, { ref: ref, ...rest, children: [label && (jsxs(Field$1.Label, { children: [label, jsx(Field$1.RequiredIndicator, { color: rest.invalid && rest.required ? 'red.500' : undefined, fallback: optionalText })] })), children, helperText && (jsx(Field$1.HelperText, { children: helperText })), !!errorText && (jsxs(Field$1.ErrorText, { children: [rest.required && rest.invalid && (jsx("span", { style: { color: 'var(--chakra-colors-red-500)' }, children: "* " })), errorText] }))] }));
3668
4111
  });
3669
4112
 
3670
4113
  const BooleanPicker = ({ schema, column, prefix }) => {
3671
4114
  const { watch, formState: { errors }, setValue, } = useFormContext();
3672
4115
  const { translate } = useSchemaContext();
3673
- const { required, gridColumn, gridRow } = schema;
4116
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
3674
4117
  const isRequired = required?.some((columnId) => columnId === column);
3675
4118
  const colLabel = `${prefix}${column}`;
3676
4119
  const value = watch(colLabel);
3677
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
3678
- gridRow, children: [jsx(CheckboxCard, { checked: value, variant: "surface", onChange: () => {
3679
- setValue(colLabel, !value);
3680
- } }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
4120
+ return (jsx(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, alignItems: 'stretch', gridColumn,
4121
+ gridRow, errorText: errors[`${colLabel}`]
4122
+ ? translate.t(removeIndex(`${colLabel}.field_required`))
4123
+ : undefined, invalid: !!errors[colLabel], children: jsx(CheckboxCard, { checked: value, variant: 'surface', onChange: () => {
4124
+ setValue(colLabel, !value);
4125
+ } }) }));
4126
+ };
4127
+
4128
+ const CustomInput = ({ column, schema, prefix }) => {
4129
+ const formContext = useFormContext();
4130
+ const { inputRender } = schema;
4131
+ return (inputRender &&
4132
+ inputRender({
4133
+ column,
4134
+ schema,
4135
+ prefix,
4136
+ formContext,
4137
+ }));
3681
4138
  };
3682
4139
 
3683
- const monthNamesShort = [
3684
- "Jan",
3685
- "Feb",
3686
- "Mar",
3687
- "Apr",
3688
- "May",
3689
- "Jun",
3690
- "Jul",
3691
- "Aug",
3692
- "Sep",
3693
- "Oct",
3694
- "Nov",
3695
- "Dec",
3696
- ];
3697
- const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3698
4140
  const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firstDayOfWeek = 0, }) => {
4141
+ const { labels } = useContext(DatePickerContext);
4142
+ const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel } = labels;
3699
4143
  if (calendars.length) {
3700
4144
  return (jsxs(Grid, { children: [jsxs(Grid, { templateColumns: "repeat(4, auto)", justifyContent: "center", children: [jsx(Button$1, { variant: "ghost", ...getBackProps({
3701
4145
  calendars,
3702
4146
  offset: 12,
3703
- }), children: "<<" }), jsx(Button$1, { variant: "ghost", ...getBackProps({ calendars }), children: "Back" }), jsx(Button$1, { variant: "ghost", ...getForwardProps({ calendars }), children: "Next" }), jsx(Button$1, { variant: "ghost", ...getForwardProps({
4147
+ }), children: "<<" }), jsx(Button$1, { variant: "ghost", ...getBackProps({ calendars }), children: backButtonLabel }), jsx(Button$1, { variant: "ghost", ...getForwardProps({ calendars }), children: forwardButtonLabel }), jsx(Button$1, { variant: "ghost", ...getForwardProps({
3704
4148
  calendars,
3705
4149
  offset: 12,
3706
4150
  }), children: ">>" })] }), jsx(Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", children: calendars.map((calendar) => (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { justifyContent: "center", children: [monthNamesShort[calendar.month], " ", calendar.year] }), jsxs(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
@@ -3743,9 +4187,52 @@ const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firs
3743
4187
  }
3744
4188
  return null;
3745
4189
  };
4190
+ const DatePickerContext = createContext({
4191
+ labels: {
4192
+ monthNamesShort: [
4193
+ "Jan",
4194
+ "Feb",
4195
+ "Mar",
4196
+ "Apr",
4197
+ "May",
4198
+ "Jun",
4199
+ "Jul",
4200
+ "Aug",
4201
+ "Sep",
4202
+ "Oct",
4203
+ "Nov",
4204
+ "Dec",
4205
+ ],
4206
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4207
+ backButtonLabel: "Back",
4208
+ forwardButtonLabel: "Next",
4209
+ },
4210
+ });
3746
4211
  let DatePicker$1 = class DatePicker extends React__default.Component {
3747
4212
  render() {
3748
- return (jsx(Dayzed, { onDateSelected: this.props.onDateSelected, selected: this.props.selected, firstDayOfWeek: this.props.firstDayOfWeek, showOutsideDays: this.props.showOutsideDays, date: this.props.date, minDate: this.props.minDate, maxDate: this.props.maxDate, monthsToDisplay: this.props.monthsToDisplay, render: (dayzedData) => (jsx(Calendar, { ...dayzedData, firstDayOfWeek: this.props.firstDayOfWeek })) }));
4213
+ const { labels = {
4214
+ monthNamesShort: [
4215
+ "Jan",
4216
+ "Feb",
4217
+ "Mar",
4218
+ "Apr",
4219
+ "May",
4220
+ "Jun",
4221
+ "Jul",
4222
+ "Aug",
4223
+ "Sep",
4224
+ "Oct",
4225
+ "Nov",
4226
+ "Dec",
4227
+ ],
4228
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4229
+ backButtonLabel: "Back",
4230
+ forwardButtonLabel: "Next",
4231
+ }, } = this.props;
4232
+ return (jsx(DatePickerContext.Provider, { value: { labels }, children: jsx(Dayzed, { onDateSelected: this.props.onDateSelected, selected: this.props.selected, firstDayOfWeek: this.props.firstDayOfWeek, showOutsideDays: this.props.showOutsideDays, date: this.props.date, minDate: this.props.minDate, maxDate: this.props.maxDate, monthsToDisplay: this.props.monthsToDisplay, render:
4233
+ // @ts-expect-error - Dayzed types need to be fixed
4234
+ (dayzedData) => (jsx(Calendar, { ...dayzedData,
4235
+ firstDayOfWeek: this.props.firstDayOfWeek })) }) }));
3749
4236
  }
3750
4237
  };
3751
4238
 
@@ -3767,28 +4254,270 @@ const PopoverRoot = Popover.Root;
3767
4254
  const PopoverBody = Popover.Body;
3768
4255
  const PopoverTrigger = Popover.Trigger;
3769
4256
 
4257
+ /**
4258
+ * Custom hook to simplify i18n translation for form fields.
4259
+ * Automatically handles colLabel construction and removeIndex logic.
4260
+ *
4261
+ * @param column - The column name
4262
+ * @param prefix - The prefix for the field (usually empty string or parent path)
4263
+ * @returns Object with translation helper functions
4264
+ *
4265
+ * @example
4266
+ * ```tsx
4267
+ * const formI18n = useFormI18n(column, prefix);
4268
+ *
4269
+ * // Get field label
4270
+ * <Field label={formI18n.label()} />
4271
+ *
4272
+ * // Get error message
4273
+ * <Text>{formI18n.required()}</Text>
4274
+ *
4275
+ * // Get custom translation key
4276
+ * <Text>{formI18n.t('add_more')}</Text>
4277
+ *
4278
+ * // Access the raw colLabel
4279
+ * const colLabel = formI18n.colLabel;
4280
+ * ```
4281
+ */
4282
+ const useFormI18n = (column, prefix = "") => {
4283
+ const { translate } = useSchemaContext();
4284
+ const colLabel = `${prefix}${column}`;
4285
+ return {
4286
+ /**
4287
+ * The constructed column label (prefix + column)
4288
+ */
4289
+ colLabel,
4290
+ /**
4291
+ * Get the field label translation
4292
+ * Equivalent to: translate.t(removeIndex(`${colLabel}.field_label`))
4293
+ */
4294
+ label: (options) => {
4295
+ return translate.t(removeIndex(`${colLabel}.field_label`), options);
4296
+ },
4297
+ /**
4298
+ * Get the required error message translation
4299
+ * Equivalent to: translate.t(removeIndex(`${colLabel}.field_required`))
4300
+ */
4301
+ required: (options) => {
4302
+ return translate.t(removeIndex(`${colLabel}.field_required`), options);
4303
+ },
4304
+ /**
4305
+ * Get a translation for any custom key relative to the field
4306
+ * Equivalent to: translate.t(removeIndex(`${colLabel}.${key}`))
4307
+ *
4308
+ * @param key - The translation key suffix (e.g., 'add_more', 'total', etc.)
4309
+ * @param options - Optional translation options (e.g., defaultValue, interpolation variables)
4310
+ */
4311
+ t: (key, options) => {
4312
+ return translate.t(removeIndex(`${colLabel}.${key}`), options);
4313
+ },
4314
+ /**
4315
+ * Access to the original translate object for edge cases
4316
+ */
4317
+ translate,
4318
+ };
4319
+ };
4320
+
4321
+ dayjs.extend(utc);
4322
+ dayjs.extend(timezone);
3770
4323
  const DatePicker = ({ column, schema, prefix }) => {
3771
4324
  const { watch, formState: { errors }, setValue, } = useFormContext();
3772
- const { translate } = useSchemaContext();
3773
- const { required, gridColumn, gridRow } = schema;
4325
+ const { timezone, dateTimePickerLabels } = useSchemaContext();
4326
+ const formI18n = useFormI18n(column, prefix);
4327
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
3774
4328
  const isRequired = required?.some((columnId) => columnId === column);
3775
- const colLabel = `${prefix}${column}`;
4329
+ const colLabel = formI18n.colLabel;
3776
4330
  const [open, setOpen] = useState(false);
3777
4331
  const selectedDate = watch(colLabel);
3778
- const formatedDate = dayjs(selectedDate).format("YYYY-MM-DD");
3779
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
3780
- gridRow, children: [jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsx(Button, { size: "sm", variant: "outline", onClick: () => {
3781
- setOpen(true);
3782
- }, children: selectedDate !== undefined ? `${formatedDate}` : "" }) }), jsx(PopoverContent, { children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DatePicker$1
3783
- // @ts-expect-error TODO: find appropriate types
3784
- , {
3785
- // @ts-expect-error TODO: find appropriate types
3786
- selected: new Date(selectedDate),
3787
- // @ts-expect-error TODO: find appropriate types
3788
- onDateSelected: ({ date }) => {
3789
- setValue(colLabel, dayjs(date).format("YYYY-MM-DD"));
3790
- setOpen(false);
3791
- } })] }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
4332
+ const displayDate = dayjs(selectedDate)
4333
+ .tz(timezone)
4334
+ .format(displayDateFormat);
4335
+ useEffect(() => {
4336
+ try {
4337
+ if (selectedDate) {
4338
+ // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
4339
+ // For example, parse as UTC:
4340
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4341
+ if (!parsedDate.isValid())
4342
+ return;
4343
+ // Format according to dateFormat from schema
4344
+ const formatted = parsedDate.format(dateFormat);
4345
+ // Update the form value only if different to avoid loops
4346
+ if (formatted !== selectedDate) {
4347
+ setValue(colLabel, formatted, {
4348
+ shouldValidate: true,
4349
+ shouldDirty: true,
4350
+ });
4351
+ }
4352
+ }
4353
+ }
4354
+ catch (e) {
4355
+ console.error(e);
4356
+ }
4357
+ }, [selectedDate, dateFormat, colLabel, setValue]);
4358
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4359
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4360
+ setOpen(true);
4361
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), jsx(PopoverContent, { children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
4362
+ setValue(colLabel, dayjs(date).format(dateFormat));
4363
+ setOpen(false);
4364
+ }, labels: {
4365
+ monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
4366
+ formI18n.translate.t(`common.month_1`, {
4367
+ defaultValue: 'January',
4368
+ }),
4369
+ formI18n.translate.t(`common.month_2`, {
4370
+ defaultValue: 'February',
4371
+ }),
4372
+ formI18n.translate.t(`common.month_3`, {
4373
+ defaultValue: 'March',
4374
+ }),
4375
+ formI18n.translate.t(`common.month_4`, {
4376
+ defaultValue: 'April',
4377
+ }),
4378
+ formI18n.translate.t(`common.month_5`, {
4379
+ defaultValue: 'May',
4380
+ }),
4381
+ formI18n.translate.t(`common.month_6`, {
4382
+ defaultValue: 'June',
4383
+ }),
4384
+ formI18n.translate.t(`common.month_7`, {
4385
+ defaultValue: 'July',
4386
+ }),
4387
+ formI18n.translate.t(`common.month_8`, {
4388
+ defaultValue: 'August',
4389
+ }),
4390
+ formI18n.translate.t(`common.month_9`, {
4391
+ defaultValue: 'September',
4392
+ }),
4393
+ formI18n.translate.t(`common.month_10`, {
4394
+ defaultValue: 'October',
4395
+ }),
4396
+ formI18n.translate.t(`common.month_11`, {
4397
+ defaultValue: 'November',
4398
+ }),
4399
+ formI18n.translate.t(`common.month_12`, {
4400
+ defaultValue: 'December',
4401
+ }),
4402
+ ],
4403
+ weekdayNamesShort: dateTimePickerLabels?.weekdayNamesShort ?? [
4404
+ formI18n.translate.t(`common.weekday_1`, {
4405
+ defaultValue: 'Sun',
4406
+ }),
4407
+ formI18n.translate.t(`common.weekday_2`, {
4408
+ defaultValue: 'Mon',
4409
+ }),
4410
+ formI18n.translate.t(`common.weekday_3`, {
4411
+ defaultValue: 'Tue',
4412
+ }),
4413
+ formI18n.translate.t(`common.weekday_4`, {
4414
+ defaultValue: 'Wed',
4415
+ }),
4416
+ formI18n.translate.t(`common.weekday_5`, {
4417
+ defaultValue: 'Thu',
4418
+ }),
4419
+ formI18n.translate.t(`common.weekday_6`, {
4420
+ defaultValue: 'Fri',
4421
+ }),
4422
+ formI18n.translate.t(`common.weekday_7`, {
4423
+ defaultValue: 'Sat',
4424
+ }),
4425
+ ],
4426
+ backButtonLabel: dateTimePickerLabels?.backButtonLabel ??
4427
+ formI18n.translate.t(`common.back_button`, {
4428
+ defaultValue: 'Back',
4429
+ }),
4430
+ forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ??
4431
+ formI18n.translate.t(`common.forward_button`, {
4432
+ defaultValue: 'Forward',
4433
+ }),
4434
+ } })] }) })] }) }));
4435
+ };
4436
+
4437
+ dayjs.extend(utc);
4438
+ dayjs.extend(timezone);
4439
+ const DateRangePicker = ({ column, schema, prefix, }) => {
4440
+ const { watch, formState: { errors }, setValue, } = useFormContext();
4441
+ const { timezone, dateTimePickerLabels } = useSchemaContext();
4442
+ const formI18n = useFormI18n(column, prefix);
4443
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4444
+ const isRequired = required?.some((columnId) => columnId === column);
4445
+ const colLabel = formI18n.colLabel;
4446
+ const [open, setOpen] = useState(false);
4447
+ const selectedDateRange = watch(colLabel);
4448
+ // Convert string[] to Date[] for the picker
4449
+ const selectedDates = (selectedDateRange ?? [])
4450
+ .map((dateStr) => {
4451
+ if (!dateStr)
4452
+ return null;
4453
+ const parsed = dayjs(dateStr).tz(timezone);
4454
+ return parsed.isValid() ? parsed.toDate() : null;
4455
+ })
4456
+ .filter((date) => date !== null);
4457
+ // Format display string
4458
+ const getDisplayText = () => {
4459
+ if (!selectedDateRange || selectedDateRange.length === 0) {
4460
+ return '';
4461
+ }
4462
+ if (selectedDateRange.length === 1) {
4463
+ const date = dayjs(selectedDateRange[0]).tz(timezone);
4464
+ return date.isValid() ? date.format(displayDateFormat) : '';
4465
+ }
4466
+ if (selectedDateRange.length === 2) {
4467
+ const startDate = dayjs(selectedDateRange[0]).tz(timezone);
4468
+ const endDate = dayjs(selectedDateRange[1]).tz(timezone);
4469
+ if (startDate.isValid() && endDate.isValid()) {
4470
+ return `${startDate.format(displayDateFormat)} - ${endDate.format(displayDateFormat)}`;
4471
+ }
4472
+ }
4473
+ return '';
4474
+ };
4475
+ useEffect(() => {
4476
+ try {
4477
+ if (selectedDateRange && selectedDateRange.length > 0) {
4478
+ // Format dates according to dateFormat from schema
4479
+ const formatted = selectedDateRange
4480
+ .map((dateStr) => {
4481
+ if (!dateStr)
4482
+ return null;
4483
+ const parsed = dayjs(dateStr).tz(timezone);
4484
+ return parsed.isValid() ? parsed.format(dateFormat) : null;
4485
+ })
4486
+ .filter((date) => date !== null);
4487
+ // Update the form value only if different to avoid loops
4488
+ // Compare arrays element by element
4489
+ const needsUpdate = formatted.length !== selectedDateRange.length ||
4490
+ formatted.some((val, idx) => val !== selectedDateRange[idx]);
4491
+ if (needsUpdate && formatted.length > 0) {
4492
+ setValue(colLabel, formatted, {
4493
+ shouldValidate: true,
4494
+ shouldDirty: true,
4495
+ });
4496
+ }
4497
+ }
4498
+ }
4499
+ catch (e) {
4500
+ console.error(e);
4501
+ }
4502
+ }, [selectedDateRange, dateFormat, colLabel, setValue, timezone]);
4503
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4504
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4505
+ setOpen(true);
4506
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), getDisplayText()] }) }), jsx(PopoverContent, { minW: '600px', children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(RangeDatePicker, { selected: selectedDates, onDateSelected: ({ selected, selectable, date }) => {
4507
+ const newDates = getRangeDates({
4508
+ selectable,
4509
+ date,
4510
+ selectedDates,
4511
+ }) ?? [];
4512
+ // Convert Date[] to string[]
4513
+ const formattedDates = newDates
4514
+ .map((dateObj) => dayjs(dateObj).tz(timezone).format(dateFormat))
4515
+ .filter((dateStr) => dateStr);
4516
+ setValue(colLabel, formattedDates, {
4517
+ shouldValidate: true,
4518
+ shouldDirty: true,
4519
+ });
4520
+ }, monthsToDisplay: 2 })] }) })] }) }));
3792
4521
  };
3793
4522
 
3794
4523
  function filterArray(array, searchTerm) {
@@ -3801,17 +4530,18 @@ function filterArray(array, searchTerm) {
3801
4530
  });
3802
4531
  }
3803
4532
 
3804
- const EnumPicker = ({ column, isMultiple = false, schema, prefix, }) => {
4533
+ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
3805
4534
  const { watch, formState: { errors }, setValue, } = useFormContext();
3806
- const { translate } = useSchemaContext();
3807
- const { required } = schema;
4535
+ const { enumPickerLabels } = useSchemaContext();
4536
+ const formI18n = useFormI18n(column, prefix);
4537
+ const { required, variant } = schema;
3808
4538
  const isRequired = required?.some((columnId) => columnId === column);
3809
- const { gridColumn, gridRow, renderDisplay } = schema;
4539
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
3810
4540
  const [searchText, setSearchText] = useState();
3811
4541
  const [limit, setLimit] = useState(10);
3812
4542
  const [openSearchResult, setOpenSearchResult] = useState();
3813
4543
  const ref = useRef(null);
3814
- const colLabel = `${prefix}${column}`;
4544
+ const colLabel = formI18n.colLabel;
3815
4545
  const watchEnum = watch(colLabel);
3816
4546
  const watchEnums = (watch(colLabel) ?? []);
3817
4547
  const dataList = schema.enum ?? [];
@@ -3821,32 +4551,63 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, }) => {
3821
4551
  setSearchText(event.target.value);
3822
4552
  setLimit(10);
3823
4553
  };
3824
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
3825
- gridRow, children: [isMultiple && (jsxs(Flex, { flexFlow: "wrap", gap: 1, children: [watchEnums.map((enumValue) => {
4554
+ if (variant === 'radio') {
4555
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4556
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(RadioGroup$1.Root, { defaultValue: "1", children: jsx(HStack, { gap: "6", children: filterArray(dataList, searchText ?? '').map((item) => {
4557
+ return (jsxs(RadioGroup$1.Item, { onClick: () => {
4558
+ if (!isMultiple) {
4559
+ setOpenSearchResult(false);
4560
+ setValue(colLabel, item);
4561
+ return;
4562
+ }
4563
+ const newSet = new Set([...(watchEnums ?? []), item]);
4564
+ setValue(colLabel, [...newSet]);
4565
+ }, value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
4566
+ ? renderDisplay(item)
4567
+ : formI18n.t(item) })] }, `${colLabel}-${item}`));
4568
+ }) }) }) }));
4569
+ }
4570
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4571
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxs(Flex, { flexFlow: 'wrap', gap: 1, children: [watchEnums.map((enumValue) => {
3826
4572
  const item = enumValue;
3827
- if (item === undefined) {
3828
- return jsx(Fragment, { children: "undefined" });
4573
+ if (!!item === false) {
4574
+ return jsx(Fragment, {});
3829
4575
  }
3830
- return (jsx(Tag, { closable: true, onClick: () => {
3831
- // setSelectedEnums((state) => state.filter((id) => id != item));
4576
+ return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
3832
4577
  setValue(column, watchEnums.filter((id) => id != item));
3833
4578
  }, children: !!renderDisplay === true
3834
4579
  ? renderDisplay(item)
3835
- : translate.t(removeIndex(`${colLabel}.${item}`)) }));
3836
- }), jsx(Tag, { cursor: "pointer", onClick: () => {
4580
+ : formI18n.t(item) }, item));
4581
+ }), jsx(Tag, { size: "lg", cursor: 'pointer', onClick: () => {
3837
4582
  setOpenSearchResult(true);
3838
- }, children: translate.t(removeIndex(`${colLabel}.addMore`)) })] })), !isMultiple && (jsx(Button, { variant: "outline", onClick: () => {
4583
+ }, children: enumPickerLabels?.addMore ?? formI18n.t('add_more') }, `${colLabel}-add-more-tag`)] })), !isMultiple && (jsx(Button, { variant: 'outline', onClick: () => {
3839
4584
  setOpenSearchResult(true);
3840
- }, children: watchEnum === undefined
3841
- ? ""
3842
- : translate.t(removeIndex(`${colLabel}.${watchEnum}`)) })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: "bottom-start" }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { children: jsxs(PopoverBody, { display: "grid", gap: 1, children: [jsx(Input, { placeholder: translate.t(`${column}.typeToSearch`), onChange: (event) => {
4585
+ }, justifyContent: 'start', children: !!watchEnum === false ? '' : formI18n.t(watchEnum ?? 'null') })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start' }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { portalled: false, children: jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsx(Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: (event) => {
3843
4586
  onSearchChange(event);
3844
4587
  setOpenSearchResult(true);
3845
- }, autoComplete: "off", ref: ref }), jsx(PopoverTitle, {}), jsx(Text, { children: `${translate.t(`${column}.total`)}: ${count}, ${translate.t(`${column}.showing`)} ${limit}` }), jsxs(Grid, { gridTemplateColumns: "repeat(auto-fit, minmax(15rem, 1fr))", overflow: "auto", maxHeight: "50vh", children: [jsx(Flex, { flexFlow: "column wrap", children: filterArray(dataList, searchText ?? "").map((item) => {
4588
+ }, autoComplete: "off", ref: ref }), jsx(PopoverTitle, {}), showTotalAndLimit && (jsx(Text, { children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${count}, ${enumPickerLabels?.showing ?? formI18n.t('showing')} ${limit}` })), jsxs(Grid, { overflow: 'auto', maxHeight: '20rem', children: [jsx(Flex, { flexFlow: 'column wrap', children: dataList
4589
+ .filter((item) => {
4590
+ const searchTerm = (searchText || '').toLowerCase();
4591
+ if (!searchTerm)
4592
+ return true;
4593
+ // Check if the original enum value contains the search text
4594
+ const enumValueMatch = item
4595
+ .toLowerCase()
4596
+ .includes(searchTerm);
4597
+ // Check if the display value (translation) contains the search text
4598
+ const displayValue = !!renderDisplay === true
4599
+ ? renderDisplay(item)
4600
+ : formI18n.t(item);
4601
+ // Convert to string and check if it includes the search term
4602
+ const displayValueString = String(displayValue).toLowerCase();
4603
+ const displayValueMatch = displayValueString.includes(searchTerm);
4604
+ return enumValueMatch || displayValueMatch;
4605
+ })
4606
+ .map((item) => {
3846
4607
  const selected = isMultiple
3847
4608
  ? watchEnums.some((enumValue) => item === enumValue)
3848
4609
  : watchEnum == item;
3849
- return (jsx(Box, { cursor: "pointer", onClick: () => {
4610
+ return (jsx(Box, { cursor: 'pointer', onClick: () => {
3850
4611
  if (!isMultiple) {
3851
4612
  setOpenSearchResult(false);
3852
4613
  setValue(colLabel, item);
@@ -3854,10 +4615,11 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, }) => {
3854
4615
  }
3855
4616
  const newSet = new Set([...(watchEnums ?? []), item]);
3856
4617
  setValue(colLabel, [...newSet]);
3857
- }, ...(selected ? { color: "gray.400/50" } : {}), children: !!renderDisplay === true
4618
+ }, ...(selected ? { color: 'colorPalette.400/50' } : {}), children: !!renderDisplay === true
3858
4619
  ? renderDisplay(item)
3859
- : translate.t(removeIndex(`${colLabel}.${item}`)) }, `${colLabel}-${item}`));
3860
- }) }), isDirty && (jsx(Fragment, { children: dataList.length <= 0 && (jsx(Fragment, { children: translate.t(removeIndex(`${colLabel}.emptySearchResult`)) })) }))] })] }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
4620
+ : formI18n.t(item) }, `${colLabel}-${item}`));
4621
+ }) }), isDirty && (jsx(Fragment, { children: dataList.length <= 0 && (jsx(Fragment, { children: enumPickerLabels?.emptySearchResult ??
4622
+ formI18n.t('empty_search_result') })) }))] })] }) })] })] }));
3861
4623
  };
3862
4624
 
3863
4625
  function isEnteringWindow(_ref) {
@@ -4163,7 +4925,7 @@ function getText(_ref2) {
4163
4925
  return source.getStringData(textMediaType);
4164
4926
  }
4165
4927
 
4166
- const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = "Drop files here or click to upload", }) => {
4928
+ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = 'Drop files here or click to upload', }) => {
4167
4929
  const ref = useRef(null);
4168
4930
  const [isDraggedOver, setIsDraggedOver] = useState(false);
4169
4931
  useEffect(() => {
@@ -4177,7 +4939,7 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4177
4939
  onDrop: ({ source }) => {
4178
4940
  const files = getFiles({ source });
4179
4941
  const text = getText({ source });
4180
- console.log(files, text, "dfposa");
4942
+ console.log(files, text, 'dfposa');
4181
4943
  onDrop({ files, text });
4182
4944
  },
4183
4945
  });
@@ -4186,9 +4948,9 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4186
4948
  function getColor(isDraggedOver) {
4187
4949
  if (isDraggedOver) {
4188
4950
  return {
4189
- backgroundColor: "blue.400",
4951
+ backgroundColor: 'blue.400',
4190
4952
  _dark: {
4191
- backgroundColor: "blue.400",
4953
+ backgroundColor: 'blue.400',
4192
4954
  },
4193
4955
  };
4194
4956
  }
@@ -4209,28 +4971,223 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
4209
4971
  const filesArray = [...event.target.files];
4210
4972
  onDrop({ files: filesArray });
4211
4973
  };
4212
- return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor: "pointer", onClick: handleClick, borderStyle: "dashed", borderColor: "gray.400", alignContent: "center", justifyContent: "center", borderWidth: 1, borderRadius: 4, ...gridProps, children: [children, !!children === false && (jsxs(Fragment, { children: [jsx(Flex, { children: placeholder }), jsx(Input, { type: "file", multiple: true, style: { display: "none" }, ref: fileInput, onChange: handleChange })] }))] }));
4974
+ return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor: 'pointer', onClick: handleClick, borderStyle: 'dashed', borderColor: 'colorPalette.400', alignContent: 'center', justifyContent: 'center', borderWidth: 1, borderRadius: 4, minH: "120px", ...gridProps, children: [children, !!children === false && (jsxs(Fragment, { children: [jsx(Flex, { children: placeholder }), jsx(Input, { type: "file", multiple: true, style: { display: 'none' }, ref: fileInput, onChange: handleChange })] }))] }));
4213
4975
  };
4214
4976
 
4977
+ /**
4978
+ * Format bytes to human-readable string
4979
+ * @param bytes - The number of bytes to format
4980
+ * @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
4981
+ */
4982
+ function formatBytes(bytes) {
4983
+ if (bytes === 0)
4984
+ return '0 Bytes';
4985
+ const k = 1024;
4986
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
4987
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
4988
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4989
+ }
4990
+
4991
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4992
+ const [searchTerm, setSearchTerm] = useState('');
4993
+ const [selectedFileId, setSelectedFileId] = useState('');
4994
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
4995
+ const { data: filesData, isLoading, isError, } = useQuery({
4996
+ queryKey: ['file-picker-library', searchTerm],
4997
+ queryFn: async () => {
4998
+ if (!onFetchFiles)
4999
+ return { data: [] };
5000
+ const files = await onFetchFiles(searchTerm.trim() || '');
5001
+ return { data: files };
5002
+ },
5003
+ enabled: open && !!onFetchFiles,
5004
+ });
5005
+ const files = (filesData?.data || []);
5006
+ const filteredFiles = filterImageOnly
5007
+ ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
5008
+ : files;
5009
+ const handleSelect = () => {
5010
+ if (selectedFileId) {
5011
+ onSelect(selectedFileId);
5012
+ onClose();
5013
+ setSelectedFileId('');
5014
+ setSearchTerm('');
5015
+ }
5016
+ };
5017
+ const handleClose = () => {
5018
+ onClose();
5019
+ setSelectedFileId('');
5020
+ setSearchTerm('');
5021
+ setFailedImageIds(new Set());
5022
+ };
5023
+ const handleImageError = (fileId) => {
5024
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
5025
+ };
5026
+ if (!onFetchFiles)
5027
+ return null;
5028
+ return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ??
5029
+ translate(removeIndex(`${colLabel}.search_placeholder`)) ??
5030
+ 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
5031
+ borderColor: 'colorPalette.500',
5032
+ _dark: {
5033
+ borderColor: 'colorPalette.400',
5034
+ },
5035
+ boxShadow: {
5036
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
5037
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
5038
+ },
5039
+ }, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
5040
+ translate(removeIndex(`${colLabel}.loading`)) ??
5041
+ 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
5042
+ base: 'colorPalette.200',
5043
+ _dark: 'colorPalette.800',
5044
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
5045
+ base: 'colorPalette.600',
5046
+ _dark: 'colorPalette.300',
5047
+ }, children: labels?.loadingFailed ??
5048
+ translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
5049
+ 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ??
5050
+ translate(removeIndex(`${colLabel}.no_files_found`)) ??
5051
+ 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
5052
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
5053
+ const isSelected = selectedFileId === file.id;
5054
+ const imageFailed = failedImageIds.has(file.id);
5055
+ return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
5056
+ ? {
5057
+ base: 'colorPalette.500',
5058
+ _dark: 'colorPalette.400',
5059
+ }
5060
+ : 'border.default', borderRadius: "md", bg: isSelected
5061
+ ? {
5062
+ base: 'colorPalette.50',
5063
+ _dark: 'colorPalette.900/20',
5064
+ }
5065
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
5066
+ borderColor: isSelected
5067
+ ? {
5068
+ base: 'colorPalette.600',
5069
+ _dark: 'colorPalette.400',
5070
+ }
5071
+ : {
5072
+ base: 'colorPalette.300',
5073
+ _dark: 'colorPalette.400',
5074
+ },
5075
+ bg: isSelected
5076
+ ? {
5077
+ base: 'colorPalette.100',
5078
+ _dark: 'colorPalette.800/30',
5079
+ }
5080
+ : 'bg.muted',
5081
+ }, transition: "all 0.2s", children: jsxs(HStack, { gap: 3, children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsx(Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxs(HStack, { gap: 2, children: [file.size && (jsx(Fragment, { children: jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
5082
+ ? formatBytes(file.size)
5083
+ : file.size }) })), file.comment && (jsxs(Fragment, { children: [file.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsx(Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsx(Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
5084
+ base: 'colorPalette.500',
5085
+ _dark: 'colorPalette.400',
5086
+ }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
5087
+ }) })) }))] }) }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
5088
+ translate(removeIndex(`${colLabel}.cancel`)) ??
5089
+ 'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
5090
+ translate(removeIndex(`${colLabel}.select`)) ??
5091
+ 'Select' })] }) })] }) }));
5092
+ }
4215
5093
  const FilePicker = ({ column, schema, prefix }) => {
4216
5094
  const { setValue, formState: { errors }, watch, } = useFormContext();
4217
- const { translate } = useSchemaContext();
4218
- const { required, gridColumn, gridRow } = schema;
5095
+ const { filePickerLabels } = useSchemaContext();
5096
+ const formI18n = useFormI18n(column, prefix);
5097
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4219
5098
  const isRequired = required?.some((columnId) => columnId === column);
4220
- const currentFiles = (watch(column) ?? []);
4221
- const colLabel = `${prefix}${column}`;
4222
- return (jsxs(Field, { label: `${translate.t(`${colLabel}.fieldLabel`)}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", display: "grid", gridTemplateRows: "auto 1fr auto", alignItems: "stretch", children: [jsx(FileDropzone, { onDrop: ({ files }) => {
4223
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4224
- setValue(colLabel, [...currentFiles, ...newFiles]);
4225
- }, placeholder: translate.t(removeIndex(`${colLabel}.fileDropzone`)) }), jsx(Flex, { flexFlow: "column", gap: 1, children: currentFiles.map((file) => {
4226
- return (jsx(Card.Root, { variant: "subtle", children: jsxs(Card.Body, { gap: "2", cursor: "pointer", onClick: () => {
4227
- setValue(column, currentFiles.filter(({ name }) => {
4228
- return name !== file.name;
4229
- }));
4230
- }, display: "flex", flexFlow: "row", alignItems: "center", padding: "2", children: [jsx(Box, { children: file.name }), jsx(TiDeleteOutline, {})] }) }, file.name));
4231
- }) }), errors[`${colLabel}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
5099
+ const currentValue = watch(column) ?? [];
5100
+ const currentFiles = Array.isArray(currentValue)
5101
+ ? currentValue
5102
+ : [];
5103
+ const colLabel = formI18n.colLabel;
5104
+ const [dialogOpen, setDialogOpen] = useState(false);
5105
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
5106
+ const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
5107
+ const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
5108
+ const handleImageError = (fileIdentifier) => {
5109
+ setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
5110
+ };
5111
+ const handleMediaLibrarySelect = (fileId) => {
5112
+ const newFiles = [...currentFiles, fileId];
5113
+ setValue(colLabel, newFiles);
5114
+ };
5115
+ const handleRemove = (index) => {
5116
+ const newFiles = currentFiles.filter((_, i) => i !== index);
5117
+ setValue(colLabel, newFiles);
5118
+ };
5119
+ const isFileObject = (value) => {
5120
+ return value instanceof File;
5121
+ };
5122
+ const getFileIdentifier = (file, index) => {
5123
+ if (isFileObject(file)) {
5124
+ return `${file.name}-${file.size}-${index}`;
5125
+ }
5126
+ return file;
5127
+ };
5128
+ const getFileName = (file) => {
5129
+ if (isFileObject(file)) {
5130
+ return file.name;
5131
+ }
5132
+ return typeof file === 'string' ? file : 'Unknown file';
5133
+ };
5134
+ const getFileSize = (file) => {
5135
+ if (isFileObject(file)) {
5136
+ return file.size;
5137
+ }
5138
+ return undefined;
5139
+ };
5140
+ const isImageFile = (file) => {
5141
+ if (isFileObject(file)) {
5142
+ return file.type.startsWith('image/');
5143
+ }
5144
+ if (typeof file === 'string') {
5145
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
5146
+ }
5147
+ return false;
5148
+ };
5149
+ const getImageUrl = (file) => {
5150
+ if (isFileObject(file)) {
5151
+ return URL.createObjectURL(file);
5152
+ }
5153
+ return undefined;
5154
+ };
5155
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5156
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxs(VStack, { align: "stretch", gap: 2, children: [jsx(FileDropzone, { onDrop: ({ files }) => {
5157
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
5158
+ if (isFileObject(cur)) {
5159
+ return cur.name === name;
5160
+ }
5161
+ return false;
5162
+ }));
5163
+ setValue(colLabel, [...currentFiles, ...newFiles]);
5164
+ }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
5165
+ formI18n.t('browse_library') ??
5166
+ 'Browse from Library' }))] }), showMediaLibrary && (jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5167
+ formI18n.t('dialog_title') ??
5168
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5169
+ const fileIdentifier = getFileIdentifier(file, index);
5170
+ const fileName = getFileName(file);
5171
+ const fileSize = getFileSize(file);
5172
+ const isImage = isImageFile(file);
5173
+ const imageUrl = getImageUrl(file);
5174
+ const imageFailed = failedImageIds.has(fileIdentifier);
5175
+ return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
5176
+ borderColor: 'colorPalette.300',
5177
+ bg: 'bg.muted',
5178
+ }, transition: "all 0.2s", children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", children: isImage && imageUrl && !imageFailed ? (jsx(Image, { src: imageUrl, alt: fileName, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(fileIdentifier) })) : isImage && (imageFailed || !imageUrl) ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: fileName }), fileSize !== undefined && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: formatBytes(fileSize) }))] }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, fileIdentifier));
5179
+ }) })] }));
4232
5180
  };
4233
5181
 
5182
+ const ToggleTip = React.forwardRef(function ToggleTip(props, ref) {
5183
+ const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
5184
+ return (jsxs(Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsx(Popover.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Popover.Positioner, { children: jsxs(Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsx(Popover.Arrow, { children: jsx(Popover.ArrowTip, {}) })), content] }) }) })] }));
5185
+ });
5186
+ const InfoTip = React.forwardRef(function InfoTip(props, ref) {
5187
+ const { children, ...rest } = props;
5188
+ return (jsx(ToggleTip, { content: children, ...rest, ref: ref, children: jsx(IconButton, { variant: "ghost", "aria-label": "info", size: "2xs", colorPalette: "colorPalette", children: jsx(HiOutlineInformationCircle, {}) }) }));
5189
+ });
5190
+
4234
5191
  const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
4235
5192
  if (serverUrl === undefined || serverUrl.length == 0) {
4236
5193
  throw new Error("The serverUrl is missing");
@@ -4261,25 +5218,40 @@ const getTableData = async ({ serverUrl, in_table, searching = "", where = [], l
4261
5218
 
4262
5219
  const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
4263
5220
  const { watch, formState: { errors }, setValue, } = useFormContext();
4264
- const { serverUrl, idMap, setIdMap, translate, schema: parentSchema, } = useSchemaContext();
4265
- const { required, gridColumn, gridRow, renderDisplay, foreign_key } = schema;
5221
+ const { serverUrl, idMap, setIdMap, schema: parentSchema, idPickerLabels, } = useSchemaContext();
5222
+ const formI18n = useFormI18n(column, prefix);
5223
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, foreign_key, } = schema;
4266
5224
  const isRequired = required?.some((columnId) => columnId === column);
4267
- const { table, column: column_ref, display_column, } = foreign_key;
4268
- const [searchText, setSearchText] = useState();
5225
+ const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
5226
+ const [searchText, setSearchText] = useState('');
4269
5227
  const [limit, setLimit] = useState(10);
4270
5228
  const [openSearchResult, setOpenSearchResult] = useState();
4271
5229
  const [page, setPage] = useState(0);
4272
5230
  const ref = useRef(null);
4273
- const colLabel = `${prefix}${column}`;
5231
+ const colLabel = formI18n.colLabel;
5232
+ const watchId = watch(colLabel);
5233
+ const watchIds = isMultiple ? (watch(colLabel) ?? []) : [];
5234
+ // Query for search results
4274
5235
  const query = useQuery({
4275
5236
  queryKey: [`idpicker`, { column, searchText, limit, page }],
4276
5237
  queryFn: async () => {
5238
+ if (customQueryFn) {
5239
+ const { data, idMap } = await customQueryFn({
5240
+ searching: searchText ?? '',
5241
+ limit: limit,
5242
+ offset: page * limit,
5243
+ });
5244
+ setIdMap((state) => {
5245
+ return { ...state, ...idMap };
5246
+ });
5247
+ return data;
5248
+ }
4277
5249
  const data = await getTableData({
4278
5250
  serverUrl,
4279
- searching: searchText ?? "",
5251
+ searching: searchText ?? '',
4280
5252
  in_table: table,
4281
5253
  limit: limit,
4282
- offset: page * 10,
5254
+ offset: page * limit,
4283
5255
  });
4284
5256
  const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
4285
5257
  return [
@@ -4294,27 +5266,38 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
4294
5266
  });
4295
5267
  return data;
4296
5268
  },
4297
- enabled: (searchText ?? "")?.length > 0,
5269
+ enabled: openSearchResult === true,
4298
5270
  staleTime: 300000,
4299
5271
  });
4300
- const { isLoading, isFetching, data, isPending, isError } = query;
4301
- const dataList = data?.data ?? [];
4302
- const count = data?.count ?? 0;
4303
- const isDirty = (searchText?.length ?? 0) > 0;
4304
- const watchId = watch(colLabel);
4305
- const watchIds = (watch(colLabel) ?? []);
4306
- useQuery({
5272
+ // Query for currently selected items (to display them properly)
5273
+ const queryDefault = useQuery({
4307
5274
  queryKey: [
4308
- `idpicker`,
4309
- { form: parentSchema.title, column, searchText, limit, page },
5275
+ `idpicker-default`,
5276
+ { form: parentSchema.title, column, id: isMultiple ? watchIds : watchId },
4310
5277
  ],
4311
5278
  queryFn: async () => {
5279
+ if (customQueryFn) {
5280
+ const { data, idMap } = await customQueryFn({
5281
+ searching: watchIds.join(','),
5282
+ limit: isMultiple ? watchIds.length : 1,
5283
+ offset: 0,
5284
+ });
5285
+ setIdMap((state) => {
5286
+ return { ...state, ...idMap };
5287
+ });
5288
+ return data;
5289
+ }
5290
+ if (!watchId && (!watchIds || watchIds.length === 0)) {
5291
+ return { data: [] };
5292
+ }
5293
+ const searchValue = isMultiple ? watchIds.join(',') : watchId;
4312
5294
  const data = await getTableData({
4313
5295
  serverUrl,
4314
- searching: watchId,
5296
+ searching: searchValue,
4315
5297
  in_table: table,
4316
- limit: limit,
4317
- offset: page * 10,
5298
+ where: [{ id: column_ref, value: isMultiple ? watchIds : watchId }],
5299
+ limit: isMultiple ? watchIds.length : 1,
5300
+ offset: 0,
4318
5301
  });
4319
5302
  const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
4320
5303
  return [
@@ -4329,85 +5312,199 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
4329
5312
  });
4330
5313
  return data;
4331
5314
  },
5315
+ enabled: isMultiple
5316
+ ? Array.isArray(watchIds) && watchIds.length > 0
5317
+ : !!watchId,
4332
5318
  });
5319
+ // Effect to load selected values when component mounts
5320
+ useEffect(() => {
5321
+ if (isMultiple ? watchIds.length > 0 : !!watchId) {
5322
+ queryDefault.refetch();
5323
+ }
5324
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5325
+ }, []);
5326
+ // Effect to trigger initial data fetch when popover opens
5327
+ useEffect(() => {
5328
+ if (openSearchResult) {
5329
+ // Reset search text when opening the popover
5330
+ setSearchText('');
5331
+ // Reset page to first page
5332
+ setPage(0);
5333
+ // Fetch initial data
5334
+ query.refetch();
5335
+ }
5336
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5337
+ }, [openSearchResult]);
4333
5338
  const onSearchChange = async (event) => {
4334
5339
  setSearchText(event.target.value);
4335
5340
  setPage(0);
4336
- setLimit(10);
5341
+ query.refetch();
5342
+ };
5343
+ const handleLimitChange = (event) => {
5344
+ const newLimit = Number(event.target.value);
5345
+ setLimit(newLimit);
5346
+ // Reset to first page when changing limit
5347
+ setPage(0);
5348
+ // Trigger a new search with the updated limit
5349
+ query.refetch();
4337
5350
  };
5351
+ const { isLoading, isFetching, data, isPending, isError } = query;
5352
+ const dataList = data?.data ?? [];
5353
+ const count = data?.count ?? 0;
4338
5354
  const getPickedValue = () => {
4339
5355
  if (Object.keys(idMap).length <= 0) {
4340
- return "";
5356
+ return '';
4341
5357
  }
4342
5358
  const record = idMap[watchId];
4343
5359
  if (record === undefined) {
4344
- return "";
5360
+ return '';
5361
+ }
5362
+ if (!!renderDisplay === true) {
5363
+ return renderDisplay(record);
4345
5364
  }
4346
5365
  return record[display_column];
4347
5366
  };
4348
- return (jsxs(Field, { label: `${translate.t(removeIndex(removeIndex(`${column}.fieldLabel`)))}`, required: isRequired, alignItems: "stretch", gridColumn,
4349
- gridRow, children: [isMultiple && (jsxs(Flex, { flexFlow: "wrap", gap: 1, children: [watchIds.map((id) => {
5367
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5368
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxs(Flex, { flexFlow: 'wrap', gap: 1, children: [watchIds.map((id) => {
4350
5369
  const item = idMap[id];
4351
5370
  if (item === undefined) {
4352
- return (jsx(Text, { children: translate.t(removeIndex(`${colLabel}.undefined`)) }, id));
5371
+ return (jsx(Text, { children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
4353
5372
  }
4354
5373
  return (jsx(Tag, { closable: true, onClick: () => {
4355
- setValue(column, watchIds.filter((id) => id != item[column_ref]));
5374
+ setValue(colLabel, watchIds.filter((itemId) => itemId !== item[column_ref]));
4356
5375
  }, children: !!renderDisplay === true
4357
5376
  ? renderDisplay(item)
4358
5377
  : item[display_column] }, id));
4359
- }), jsx(Tag, { cursor: "pointer", onClick: () => {
5378
+ }), jsx(Tag, { cursor: 'pointer', onClick: () => {
4360
5379
  setOpenSearchResult(true);
4361
- }, children: translate.t(removeIndex(`${colLabel}.addMore`)) })] })), !isMultiple && (jsx(Button, { variant: "outline", onClick: () => {
5380
+ }, children: idPickerLabels?.addMore ?? formI18n.t('add_more') })] })), !isMultiple && (jsx(Button, { variant: 'outline', onClick: () => {
4362
5381
  setOpenSearchResult(true);
4363
- }, children: getPickedValue() })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: "bottom-start", strategy: "fixed" }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { children: jsxs(PopoverBody, { display: "grid", gap: 1, children: [jsx(Input, { placeholder: translate.t(removeIndex(`${colLabel}.typeToSearch`)), onChange: (event) => {
4364
- onSearchChange(event);
4365
- setOpenSearchResult(true);
4366
- }, autoComplete: "off", ref: ref }), jsx(PopoverTitle, {}), (searchText?.length ?? 0) > 0 && (jsxs(Fragment, { children: [isFetching && jsx(Fragment, { children: "isFetching" }), isLoading && jsx(Fragment, { children: "isLoading" }), isPending && jsx(Fragment, { children: "isPending" }), (isFetching || isLoading || isPending) && jsx(Spinner, {}), isError && (jsx(Icon, { color: "red.400", children: jsx(BiError, {}) })), jsx(Text, { justifySelf: "center", children: `${translate.t(removeIndex(`${colLabel}.total`))} ${count}, ${translate.t(removeIndex(`${colLabel}.showing`))} ${limit}` }), jsxs(Grid, { gridTemplateColumns: "repeat(auto-fit, minmax(15rem, 1fr))", overflow: "auto", maxHeight: "50vh", children: [jsx(Flex, { flexFlow: "column wrap", children:
4367
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4368
- dataList.map((item) => {
4369
- const selected = isMultiple
4370
- ? watchIds.some((id) => item[column_ref] === id)
4371
- : watchId === item[column_ref];
4372
- return (jsx(Box, { cursor: "pointer", onClick: () => {
4373
- if (!isMultiple) {
4374
- setOpenSearchResult(false);
4375
- setValue(colLabel, item[column_ref]);
4376
- return;
4377
- }
4378
- const newSet = new Set([
4379
- ...(watchIds ?? []),
4380
- item[column_ref],
4381
- ]);
4382
- setValue(colLabel, [...newSet]);
4383
- }, opacity: 0.7, _hover: { opacity: 1 }, ...(selected ? { color: "gray.400/50" } : {}), children: !!renderDisplay === true
4384
- ? renderDisplay(item)
4385
- : item[display_column] }, item[column_ref]));
4386
- }) }), isDirty && (jsx(Fragment, { children: dataList.length <= 0 && (jsx(Text, { children: translate.t(removeIndex(`${colLabel}.emptySearchResult`)) })) }))] }), jsx(PaginationRoot, { justifySelf: "center", count: count, pageSize: 10, defaultPage: 1, page: page + 1, onPageChange: (e) => setPage(e.page - 1), children: jsxs(HStack, { gap: "4", children: [jsx(PaginationPrevTrigger, {}), count > 0 && jsx(PaginationPageText, {}), jsx(PaginationNextTrigger, {})] }) })] }))] }) })] }), errors[`${colLabel}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
5382
+ }, justifyContent: 'start', children: queryDefault.isLoading ? jsx(Spinner, { size: "sm" }) : getPickedValue() })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start', strategy: 'fixed' }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { portalled: false, children: jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsx(Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: onSearchChange, autoComplete: "off", ref: ref, value: searchText }), jsx(PopoverTitle, {}), openSearchResult && (jsxs(Fragment, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, {}), isError && (jsx(Icon, { color: 'red.400', children: jsx(BiError, {}) })), jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxs(Flex, { alignItems: "center", gap: "2", children: [jsx(InfoTip, { children: `${idPickerLabels?.total ?? formI18n.t('total')} ${count}, ${idPickerLabels?.showing ?? formI18n.t('showing')} ${limit} ${idPickerLabels?.perPage ?? formI18n.t('per_page', { defaultValue: 'per page' })}` }), jsxs(Text, { fontSize: "sm", fontWeight: "bold", children: [count, jsxs(Text, { as: "span", fontSize: "xs", ml: "1", color: "gray.500", children: ["/", ' ', count > 0
5383
+ ? `${page * limit + 1}-${Math.min((page + 1) * limit, count)}`
5384
+ : '0'] })] })] }), jsx(Box, { children: jsxs("select", { value: limit, onChange: handleLimitChange, style: {
5385
+ padding: '4px 8px',
5386
+ borderRadius: '4px',
5387
+ border: '1px solid #ccc',
5388
+ fontSize: '14px',
5389
+ }, children: [jsx("option", { value: "5", children: "5" }), jsx("option", { value: "10", children: "10" }), jsx("option", { value: "20", children: "20" }), jsx("option", { value: "30", children: "30" })] }) })] }), jsx(Grid, { overflowY: 'auto', children: dataList.length > 0 ? (jsx(Flex, { flexFlow: 'column wrap', gap: 1, children: dataList.map((item) => {
5390
+ const selected = isMultiple
5391
+ ? watchIds.some((id) => item[column_ref] === id)
5392
+ : watchId === item[column_ref];
5393
+ return (jsx(Box, { cursor: 'pointer', onClick: () => {
5394
+ if (!isMultiple) {
5395
+ setOpenSearchResult(false);
5396
+ setValue(colLabel, item[column_ref]);
5397
+ return;
5398
+ }
5399
+ // For multiple selection, don't add if already selected
5400
+ if (selected)
5401
+ return;
5402
+ const newSet = new Set([
5403
+ ...(watchIds ?? []),
5404
+ item[column_ref],
5405
+ ]);
5406
+ setValue(colLabel, [...newSet]);
5407
+ }, opacity: 0.7, _hover: { opacity: 1 }, ...(selected
5408
+ ? {
5409
+ color: 'colorPalette.400/50',
5410
+ fontWeight: 'bold',
5411
+ }
5412
+ : {}), children: !!renderDisplay === true
5413
+ ? renderDisplay(item)
5414
+ : item[display_column] }, item[column_ref]));
5415
+ }) })) : (jsx(Text, { children: searchText
5416
+ ? idPickerLabels?.emptySearchResult ??
5417
+ formI18n.t('empty_search_result')
5418
+ : idPickerLabels?.initialResults ??
5419
+ formI18n.t('initial_results') })) }), jsx(PaginationRoot, { justifySelf: 'center', count: count, pageSize: limit, defaultPage: 1, page: page + 1, onPageChange: (e) => setPage(e.page - 1), children: jsxs(HStack, { gap: "4", children: [jsx(PaginationPrevTrigger, {}), count > 0 && jsx(PaginationPageText, {}), jsx(PaginationNextTrigger, {})] }) })] }))] }) })] })] }));
4387
5420
  };
4388
5421
 
4389
5422
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
4390
5423
  const { children, ...rest } = props;
4391
- return (jsxs(NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: [children, jsxs(NumberInput.Control, { children: [jsx(NumberInput.IncrementTrigger, {}), jsx(NumberInput.DecrementTrigger, {})] })] }));
5424
+ return (jsx(NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: children }));
4392
5425
  });
4393
5426
  const NumberInputField$1 = NumberInput.Input;
4394
5427
  NumberInput.Scrubber;
4395
5428
  NumberInput.Label;
4396
5429
 
5430
+ /**
5431
+ * Gets the error message for a specific field from react-hook-form errors
5432
+ * Prioritizes required errors (#.required) over field-specific validation errors
5433
+ */
5434
+ const getFieldError = (errors, fieldName) => {
5435
+ // Check for form-level required errors first (highest priority)
5436
+ const requiredError = errors['#.required'];
5437
+ if (requiredError) {
5438
+ const requiredErrorMessage = extractErrorMessage(requiredError);
5439
+ if (requiredErrorMessage) {
5440
+ return requiredErrorMessage;
5441
+ }
5442
+ }
5443
+ // If no required errors, return field-specific error
5444
+ const fieldError = errors[fieldName];
5445
+ if (fieldError) {
5446
+ const fieldErrorMessage = extractErrorMessage(fieldError);
5447
+ if (fieldErrorMessage) {
5448
+ return fieldErrorMessage;
5449
+ }
5450
+ }
5451
+ return undefined;
5452
+ };
5453
+ /**
5454
+ * Helper function to extract error message from various error formats
5455
+ * Only returns message if explicitly provided, no fallback text
5456
+ */
5457
+ const extractErrorMessage = (error) => {
5458
+ if (!error) {
5459
+ return undefined;
5460
+ }
5461
+ // If it's a simple string error
5462
+ if (typeof error === 'string') {
5463
+ return error;
5464
+ }
5465
+ // If it's an error object with a message property
5466
+ if (error && typeof error === 'object' && 'message' in error) {
5467
+ return error.message;
5468
+ }
5469
+ // If it's an array of errors, get the first one
5470
+ if (Array.isArray(error) && error.length > 0) {
5471
+ const firstError = error[0];
5472
+ if (typeof firstError === 'string') {
5473
+ return firstError;
5474
+ }
5475
+ if (firstError &&
5476
+ typeof firstError === 'object' &&
5477
+ 'message' in firstError) {
5478
+ return firstError.message;
5479
+ }
5480
+ }
5481
+ // No fallback - return undefined if no message provided
5482
+ return undefined;
5483
+ };
5484
+
4397
5485
  const NumberInputField = ({ schema, column, prefix, }) => {
4398
5486
  const { setValue, formState: { errors }, watch, } = useFormContext();
4399
5487
  const { translate } = useSchemaContext();
4400
- const { required, gridColumn, gridRow } = schema;
5488
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', numberStorageType = 'number', } = schema;
4401
5489
  const isRequired = required?.some((columnId) => columnId === column);
4402
5490
  const colLabel = `${prefix}${column}`;
4403
5491
  const value = watch(`${colLabel}`);
4404
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, gridColumn, gridRow, children: [jsx(NumberInputRoot, { children: jsx(NumberInputField$1, { required: isRequired, value: value, onChange: (event) => {
4405
- setValue(`${colLabel}`, Number(event.target.value));
4406
- } }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
5492
+ const fieldError = getFieldError(errors, colLabel);
5493
+ return (jsx(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn, gridRow, errorText: fieldError
5494
+ ? fieldError.includes('required')
5495
+ ? translate.t(removeIndex(`${colLabel}.field_required`))
5496
+ : fieldError
5497
+ : undefined, invalid: !!fieldError, children: jsx(NumberInputRoot, { value: value, onValueChange: (details) => {
5498
+ // Store as string or number based on configuration, default to number
5499
+ const value = numberStorageType === 'string'
5500
+ ? details.value
5501
+ : details.valueAsNumber;
5502
+ setValue(`${colLabel}`, value);
5503
+ }, min: schema.minimum, max: schema.maximum, step: schema.multipleOf || 0.01, allowOverflow: false, clampValueOnBlur: false, inputMode: "decimal", formatOptions: schema.formatOptions, children: jsx(NumberInputField$1, { required: isRequired }) }) }));
4407
5504
  };
4408
5505
 
4409
5506
  const ObjectInput = ({ schema, column, prefix }) => {
4410
- const { properties, gridRow, gridColumn = "1/span 12", required } = schema;
5507
+ const { properties, gridColumn = 'span 12', gridRow = 'span 1', required, showLabel = true, } = schema;
4411
5508
  const { translate } = useSchemaContext();
4412
5509
  const colLabel = `${prefix}${column}`;
4413
5510
  const isRequired = required?.some((columnId) => columnId === column);
@@ -4415,26 +5512,32 @@ const ObjectInput = ({ schema, column, prefix }) => {
4415
5512
  if (properties === undefined) {
4416
5513
  throw new Error(`properties is undefined when using ObjectInput`);
4417
5514
  }
4418
- return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: "1/span12", children: [`${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, isRequired && jsx("span", { children: "*" })] }), jsx(Grid, { gap: "4", padding: "4", gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat("auto-fit", auto)`, children: Object.keys(properties ?? {}).map((key) => {
5515
+ return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [`${translate.t(removeIndex(`${colLabel}.field_label`))}`, isRequired && jsx("span", { children: "*" })] })), jsx(Grid, { bgColor: { base: 'colorPalette.100', _dark: 'colorPalette.900' }, p: 2, borderRadius: 4, borderWidth: 1, borderColor: {
5516
+ base: 'colorPalette.200',
5517
+ _dark: 'colorPalette.800',
5518
+ }, gap: "4", padding: '4', gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: Object.keys(properties ?? {}).map((key) => {
4419
5519
  return (
4420
5520
  // @ts-expect-error find suitable types
4421
5521
  jsx(ColumnRenderer, { column: `${key}`,
4422
5522
  prefix: `${prefix}${column}.`,
4423
- properties }, `form-${colLabel}-${key}`));
4424
- }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
5523
+ properties,
5524
+ parentRequired: required }, `form-${colLabel}-${key}`));
5525
+ }) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4425
5526
  };
4426
5527
 
4427
5528
  const RecordInput$1 = ({ column, schema, prefix }) => {
4428
5529
  const { formState: { errors }, setValue, getValues, } = useFormContext();
4429
5530
  const { translate } = useSchemaContext();
4430
- const { required, gridColumn, gridRow } = schema;
5531
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
4431
5532
  const isRequired = required?.some((columnId) => columnId === column);
4432
5533
  const entries = Object.entries(getValues(column) ?? {});
4433
5534
  const [showNewEntries, setShowNewEntries] = useState(false);
4434
5535
  const [newKey, setNewKey] = useState();
4435
5536
  const [newValue, setNewValue] = useState();
4436
- return (jsxs(Field, { label: `${translate.t(`${column}.fieldLabel`)}`, required: isRequired, alignItems: "stretch", gridColumn, gridRow, children: [entries.map(([key, value]) => {
4437
- return (jsxs(Grid, { templateColumns: "1fr 1fr auto", gap: 1, children: [jsx(Input, { value: key, onChange: (e) => {
5537
+ return (jsxs(Field, { label: `${translate.t(`${column}.field_label`)}`, required: isRequired, alignItems: 'stretch', gridColumn, gridRow, errorText: errors[`${column}`]
5538
+ ? translate.t(`${column}.field_required`)
5539
+ : undefined, invalid: !!errors[column], children: [entries.map(([key, value]) => {
5540
+ return (jsxs(Grid, { templateColumns: '1fr 1fr auto', gap: 1, children: [jsx(Input, { value: key, onChange: (e) => {
4438
5541
  const filtered = entries.filter(([target]) => {
4439
5542
  return target !== key;
4440
5543
  });
@@ -4444,17 +5547,17 @@ const RecordInput$1 = ({ column, schema, prefix }) => {
4444
5547
  ...getValues(column),
4445
5548
  [key]: e.target.value,
4446
5549
  });
4447
- }, autoComplete: "off" }), jsx(IconButton, { variant: "ghost", onClick: () => {
5550
+ }, autoComplete: "off" }), jsx(IconButton, { variant: 'ghost', onClick: () => {
4448
5551
  const filtered = entries.filter(([target]) => {
4449
5552
  return target !== key;
4450
5553
  });
4451
5554
  setValue(column, Object.fromEntries([...filtered]));
4452
5555
  }, children: jsx(CgClose, {}) })] }));
4453
- }), jsx(Show, { when: showNewEntries, children: jsxs(Card.Root, { children: [jsx(Card.Body, { gap: "2", children: jsxs(Grid, { templateColumns: "1fr 1fr auto", gap: 1, children: [jsx(Input, { value: newKey, onChange: (e) => {
5556
+ }), jsx(Show, { when: showNewEntries, children: jsxs(Card.Root, { children: [jsx(Card.Body, { gap: "2", children: jsxs(Grid, { templateColumns: '1fr 1fr auto', gap: 1, children: [jsx(Input, { value: newKey, onChange: (e) => {
4454
5557
  setNewKey(e.target.value);
4455
5558
  }, autoComplete: "off" }), jsx(Input, { value: newValue, onChange: (e) => {
4456
5559
  setNewValue(e.target.value);
4457
- }, autoComplete: "off" })] }) }), jsxs(Card.Footer, { justifyContent: "flex-end", children: [jsx(IconButton, { variant: "subtle", onClick: () => {
5560
+ }, autoComplete: "off" })] }) }), jsxs(Card.Footer, { justifyContent: "flex-end", children: [jsx(IconButton, { variant: 'subtle', onClick: () => {
4458
5561
  setShowNewEntries(false);
4459
5562
  setNewKey(undefined);
4460
5563
  setNewValue(undefined);
@@ -4473,16 +5576,17 @@ const RecordInput$1 = ({ column, schema, prefix }) => {
4473
5576
  setShowNewEntries(true);
4474
5577
  setNewKey(undefined);
4475
5578
  setNewValue(undefined);
4476
- }, children: translate.t(`${column}.addNew`) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.fieldRequired`) }))] }));
5579
+ }, children: translate.t(`${column}.addNew`) })] }));
4477
5580
  };
4478
5581
 
4479
5582
  const StringInputField = ({ column, schema, prefix, }) => {
4480
5583
  const { register, formState: { errors }, } = useFormContext();
4481
5584
  const { translate } = useSchemaContext();
4482
- const { required, gridColumn, gridRow } = schema;
5585
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
4483
5586
  const isRequired = required?.some((columnId) => columnId === column);
4484
5587
  const colLabel = `${prefix}${column}`;
4485
- return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", children: [jsx(Input, { ...register(`${colLabel}`, { required: isRequired }), autoComplete: "off" }), errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }) }));
5588
+ const fieldError = getFieldError(errors, colLabel);
5589
+ return (jsx(Fragment, { children: jsx(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn, gridRow: gridRow, errorText: fieldError, invalid: !!fieldError, children: jsx(Input, { ...register(`${colLabel}`, { required: isRequired }), autoComplete: "off" }) }) }));
4486
5590
  };
4487
5591
 
4488
5592
  const RadioCardItem = React.forwardRef(function RadioCardItem(props, ref) {
@@ -4580,145 +5684,796 @@ const TagPicker = ({ column, schema, prefix }) => {
4580
5684
  }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
4581
5685
  };
4582
5686
 
5687
+ const Textarea = forwardRef(({ value, defaultValue, placeholder, onChange, onFocus, onBlur, disabled = false, readOnly = false, className, rows = 4, maxLength, autoFocus = false, invalid = false, required = false, label, helperText, errorText, ...props }, ref) => {
5688
+ const contentEditableRef = useRef(null);
5689
+ const isControlled = value !== undefined;
5690
+ // Handle input changes
5691
+ const handleInput = (e) => {
5692
+ const text = e.currentTarget.textContent || "";
5693
+ // Check maxLength if specified
5694
+ if (maxLength && text.length > maxLength) {
5695
+ e.currentTarget.textContent = text.slice(0, maxLength);
5696
+ // Move cursor to end
5697
+ const selection = window.getSelection();
5698
+ if (selection) {
5699
+ selection.selectAllChildren(e.currentTarget);
5700
+ selection.collapseToEnd();
5701
+ }
5702
+ return;
5703
+ }
5704
+ onChange?.(text);
5705
+ };
5706
+ // Handle paste events to strip formatting and respect maxLength
5707
+ const handlePaste = (e) => {
5708
+ e.preventDefault();
5709
+ const text = e.clipboardData.getData('text/plain');
5710
+ const currentText = e.currentTarget.textContent || "";
5711
+ let pasteText = text;
5712
+ if (maxLength) {
5713
+ const remainingLength = maxLength - currentText.length;
5714
+ pasteText = text.slice(0, remainingLength);
5715
+ }
5716
+ document.execCommand('insertText', false, pasteText);
5717
+ };
5718
+ // Set initial content
5719
+ useEffect(() => {
5720
+ if (contentEditableRef.current && !isControlled) {
5721
+ const initialValue = defaultValue || "";
5722
+ if (contentEditableRef.current.textContent !== initialValue) {
5723
+ contentEditableRef.current.textContent = initialValue;
5724
+ }
5725
+ }
5726
+ }, [defaultValue, isControlled]);
5727
+ // Update content when value changes (controlled mode)
5728
+ useEffect(() => {
5729
+ if (contentEditableRef.current && isControlled && value !== undefined) {
5730
+ if (contentEditableRef.current.textContent !== value) {
5731
+ contentEditableRef.current.textContent = value;
5732
+ }
5733
+ }
5734
+ }, [value, isControlled]);
5735
+ // Auto focus
5736
+ useEffect(() => {
5737
+ if (autoFocus && contentEditableRef.current) {
5738
+ contentEditableRef.current.focus();
5739
+ }
5740
+ }, [autoFocus]);
5741
+ // Forward ref
5742
+ useEffect(() => {
5743
+ if (typeof ref === 'function') {
5744
+ ref(contentEditableRef.current);
5745
+ }
5746
+ else if (ref) {
5747
+ ref.current = contentEditableRef.current;
5748
+ }
5749
+ }, [ref]);
5750
+ const textareaElement = (jsx(Box, { ref: contentEditableRef, contentEditable: !disabled && !readOnly, onInput: handleInput, onPaste: handlePaste, onFocus: onFocus, onBlur: onBlur, className: className, minHeight: `${rows * 1.5}em`, padding: "2", border: "1px solid", borderColor: invalid ? "red.500" : "gray.200", borderRadius: "md", outline: "none", _focus: {
5751
+ borderColor: invalid ? "red.500" : "blue.500",
5752
+ boxShadow: `0 0 0 1px ${invalid ? "red.500" : "blue.500"}`,
5753
+ }, _disabled: {
5754
+ opacity: 0.6,
5755
+ cursor: "not-allowed",
5756
+ bg: "gray.50",
5757
+ }, _empty: {
5758
+ _before: {
5759
+ content: placeholder ? `"${placeholder}"` : undefined,
5760
+ color: "gray.400",
5761
+ pointerEvents: "none",
5762
+ }
5763
+ }, whiteSpace: "pre-wrap", overflowWrap: "break-word", overflow: "auto", maxHeight: `${rows * 4}em`, suppressContentEditableWarning: true, ...props }));
5764
+ // If we have additional field props, wrap in Field component
5765
+ if (label || helperText || errorText || required) {
5766
+ return (jsxs(Field$1.Root, { invalid: invalid, required: required, children: [label && (jsxs(Field$1.Label, { children: [label, required && jsx(Field$1.RequiredIndicator, {})] })), textareaElement, helperText && jsx(Field$1.HelperText, { children: helperText }), errorText && jsx(Field$1.ErrorText, { children: errorText })] }));
5767
+ }
5768
+ return textareaElement;
5769
+ });
5770
+ Textarea.displayName = "Textarea";
5771
+
5772
+ const TextAreaInput = ({ column, schema, prefix, }) => {
5773
+ const { register, formState: { errors }, } = useFormContext();
5774
+ const { translate } = useSchemaContext();
5775
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5776
+ const isRequired = required?.some((columnId) => columnId === column);
5777
+ const colLabel = `${prefix}${column}`;
5778
+ const form = useFormContext();
5779
+ const { setValue, watch } = form;
5780
+ const fieldError = getFieldError(errors, colLabel);
5781
+ const watchValue = watch(colLabel);
5782
+ return (jsx(Fragment, { children: jsx(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn ?? 'span 4', gridRow: gridRow ?? 'span 1', display: "grid", errorText: fieldError
5783
+ ? fieldError.includes('required')
5784
+ ? translate.t(removeIndex(`${colLabel}.field_required`))
5785
+ : fieldError
5786
+ : undefined, invalid: !!fieldError, children: jsx(Textarea, { value: watchValue, onChange: (value) => setValue(colLabel, value) }) }) }));
5787
+ };
5788
+
5789
+ function TimePicker$1({ hour, setHour, minute, setMinute, meridiem, setMeridiem, meridiemLabel = {
5790
+ am: "am",
5791
+ pm: "pm",
5792
+ }, onChange = (_newValue) => { }, timezone = "Asia/Hong_Kong", }) {
5793
+ const handleClear = () => {
5794
+ setHour(null);
5795
+ setMinute(null);
5796
+ setMeridiem(null);
5797
+ setInputValue("");
5798
+ setShowInput(false);
5799
+ onChange({ hour: null, minute: null, meridiem: null });
5800
+ };
5801
+ const getTimeString = (hour, minute, meridiem) => {
5802
+ if (hour === null || minute === null || meridiem === null) {
5803
+ return "";
5804
+ }
5805
+ // if the hour is 24, set the hour to 0
5806
+ if (hour === 24) {
5807
+ return dayjs().tz(timezone).hour(0).minute(minute).format("HH:mmZ");
5808
+ }
5809
+ // use dayjs to format the time at current timezone
5810
+ // if meridiem is pm, add 12 hours
5811
+ let newHour = hour;
5812
+ if (meridiem === "pm" && hour !== 12) {
5813
+ newHour = hour + 12;
5814
+ }
5815
+ // if the hour is 12, set the meridiem to am, and set the hour to 0
5816
+ else if (meridiem === "am" && hour === 12) {
5817
+ newHour = 0;
5818
+ }
5819
+ return dayjs().tz(timezone).hour(newHour).minute(minute).format("HH:mmZ");
5820
+ };
5821
+ const stringTime = getTimeString(hour, minute, meridiem);
5822
+ const [inputValue, setInputValue] = useState("");
5823
+ const [showInput, setShowInput] = useState(false);
5824
+ const handleBlur = (text) => {
5825
+ // ignore all non-numeric characters
5826
+ if (!text) {
5827
+ return;
5828
+ }
5829
+ const value = text.replace(/[^0-9apm]/g, "");
5830
+ if (value === "") {
5831
+ handleClear();
5832
+ return;
5833
+ }
5834
+ // if the value is a valid time, parse it and set the hour, minute, and meridiem
5835
+ // if the value is not a valid time, set the stringTime to the value
5836
+ // first two characters are the hour
5837
+ // next two characters are the minute
5838
+ // final two characters are the meridiem
5839
+ const hour = parseInt(value.slice(0, 2));
5840
+ const minute = parseInt(value.slice(2, 4));
5841
+ const meridiem = value.slice(4, 6);
5842
+ // validate the hour and minute
5843
+ if (isNaN(hour) || isNaN(minute)) {
5844
+ setInputValue("");
5845
+ return;
5846
+ }
5847
+ // if the hour is larger than 24, set the hour to 24
5848
+ if (hour > 24) {
5849
+ setInputValue("");
5850
+ return;
5851
+ }
5852
+ let newHour = hour;
5853
+ let newMinute = minute;
5854
+ let newMeridiem = meridiem;
5855
+ // if the hour is 24, set the meridiem to am, and set the hour to 0
5856
+ if (hour === 24) {
5857
+ newMeridiem = "am";
5858
+ newHour = 0;
5859
+ }
5860
+ // if the hour is greater than 12, set the meridiem to pm, and subtract 12 from the hour
5861
+ else if (hour > 12) {
5862
+ newMeridiem = "pm";
5863
+ newHour = hour - 12;
5864
+ }
5865
+ // if the hour is 12, set the meridiem to pm, and set the hour to 12
5866
+ else if (hour === 12) {
5867
+ newMeridiem = "pm";
5868
+ newHour = 12;
5869
+ }
5870
+ // if the hour is 0, set the meridiem to am, and set the hour to 12
5871
+ else if (hour === 0) {
5872
+ newMeridiem = "am";
5873
+ newHour = 12;
5874
+ }
5875
+ else {
5876
+ newMeridiem = meridiem ?? "am";
5877
+ newHour = hour;
5878
+ }
5879
+ if (minute > 59) {
5880
+ newMinute = 0;
5881
+ }
5882
+ else {
5883
+ newMinute = minute;
5884
+ }
5885
+ onChange({
5886
+ hour: newHour,
5887
+ minute: newMinute,
5888
+ meridiem: newMeridiem,
5889
+ });
5890
+ setShowInput(false);
5891
+ };
5892
+ const handleKeyDown = (e) => {
5893
+ if (e.key === "Enter") {
5894
+ handleBlur(e.currentTarget.value);
5895
+ }
5896
+ };
5897
+ const inputRef = useRef(null);
5898
+ return (jsxs(Grid, { justifyContent: "center", alignItems: "center", templateColumns: "200px auto", gap: "2", width: "auto", minWidth: "250px", children: [jsx(Input, { onKeyDown: handleKeyDown, onChange: (e) => {
5899
+ setInputValue(e.currentTarget.value);
5900
+ }, onBlur: (e) => {
5901
+ handleBlur(e.currentTarget.value);
5902
+ }, onFocus: (e) => {
5903
+ e.currentTarget.select();
5904
+ }, value: inputValue, display: showInput ? undefined : "none", ref: inputRef }), jsxs(Button$1, { onClick: () => {
5905
+ setShowInput(true);
5906
+ setInputValue(dayjs(`1970-01-01T${getTimeString(hour, minute, meridiem)}`, "hh:mmZ").format("HH:mm"));
5907
+ inputRef.current?.focus();
5908
+ }, display: showInput ? "none" : "flex", alignItems: "center", justifyContent: "start", variant: "outline", gap: 2, children: [jsx(Icon, { size: "sm", children: jsx(BsClock, {}) }), jsx(Text, { fontSize: "sm", children: stringTime
5909
+ ? dayjs(`1970-01-01T${stringTime}`, "hh:mmZ").format("hh:mm a")
5910
+ : "" })] }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(MdCancel, {}) })] }));
5911
+ }
5912
+
5913
+ dayjs.extend(timezone);
5914
+ const TimePicker = ({ column, schema, prefix }) => {
5915
+ const { watch, formState: { errors }, setValue, } = useFormContext();
5916
+ const { translate, timezone } = useSchemaContext();
5917
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
5918
+ const isRequired = required?.some((columnId) => columnId === column);
5919
+ const colLabel = `${prefix}${column}`;
5920
+ const [open, setOpen] = useState(false);
5921
+ const value = watch(colLabel);
5922
+ const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
5923
+ ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
5924
+ : '';
5925
+ // Parse the initial time parts from the time string (HH:mm:ssZ)
5926
+ const parseTime = (time) => {
5927
+ if (!time)
5928
+ return { hour: 12, minute: 0, meridiem: 'am' };
5929
+ const parsed = dayjs(`1970-01-01T${time}`).tz(timezone);
5930
+ if (!parsed.isValid()) {
5931
+ return { hour: 12, minute: 0, meridiem: 'am' };
5932
+ }
5933
+ let hour = parsed.hour();
5934
+ const minute = parsed.minute();
5935
+ const meridiem = hour >= 12 ? 'pm' : 'am';
5936
+ if (hour === 0)
5937
+ hour = 12;
5938
+ else if (hour > 12)
5939
+ hour -= 12;
5940
+ return { hour, minute, meridiem };
5941
+ };
5942
+ const initialTime = parseTime(value);
5943
+ const [hour, setHour] = useState(initialTime.hour);
5944
+ const [minute, setMinute] = useState(initialTime.minute);
5945
+ const [meridiem, setMeridiem] = useState(initialTime.meridiem);
5946
+ useEffect(() => {
5947
+ const { hour, minute, meridiem } = parseTime(value);
5948
+ setHour(hour);
5949
+ setMinute(minute);
5950
+ setMeridiem(meridiem);
5951
+ }, [value]);
5952
+ const getTimeString = (hour, minute, meridiem) => {
5953
+ if (hour === null || minute === null || meridiem === null)
5954
+ return null;
5955
+ let newHour = hour;
5956
+ if (meridiem === 'pm' && hour !== 12) {
5957
+ newHour = hour + 12;
5958
+ }
5959
+ return dayjs()
5960
+ .tz(timezone)
5961
+ .hour(newHour)
5962
+ .minute(minute)
5963
+ .second(0)
5964
+ .format(timeFormat);
5965
+ };
5966
+ // Handle changes to time parts
5967
+ const handleTimeChange = ({ hour: newHour, minute: newMinute, meridiem: newMeridiem, }) => {
5968
+ setHour(newHour);
5969
+ setMinute(newMinute);
5970
+ setMeridiem(newMeridiem);
5971
+ const timeString = getTimeString(newHour, newMinute, newMeridiem);
5972
+ setValue(colLabel, timeString, { shouldValidate: true, shouldDirty: true });
5973
+ };
5974
+ return (jsx(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, alignItems: 'stretch', gridColumn,
5975
+ gridRow, errorText: errors[`${colLabel}`]
5976
+ ? translate.t(removeIndex(`${colLabel}.field_required`))
5977
+ : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
5978
+ setOpen(true);
5979
+ }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, meridiemLabel: {
5980
+ am: translate.t(`common.am`, { defaultValue: 'AM' }),
5981
+ pm: translate.t(`common.pm`, { defaultValue: 'PM' }),
5982
+ } }) }) }) })] }) }));
5983
+ };
5984
+
5985
+ function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond, onChange = (_newValue) => { }, }) {
5986
+ // Refs for focus management
5987
+ const hourInputRef = useRef(null);
5988
+ const minuteInputRef = useRef(null);
5989
+ const secondInputRef = useRef(null);
5990
+ // Centralized handler for key events, value changes, and focus management
5991
+ const handleKeyDown = (e, field) => {
5992
+ const input = e.target;
5993
+ const value = input.value;
5994
+ // Handle navigation between fields
5995
+ if (e.key === "Tab") {
5996
+ return;
5997
+ }
5998
+ if (e.key === ":" && field === "hour") {
5999
+ e.preventDefault();
6000
+ minuteInputRef.current?.focus();
6001
+ return;
6002
+ }
6003
+ if (e.key === ":" && field === "minute") {
6004
+ e.preventDefault();
6005
+ secondInputRef.current?.focus();
6006
+ return;
6007
+ }
6008
+ if (e.key === "Backspace" && value === "") {
6009
+ e.preventDefault();
6010
+ if (field === "minute") {
6011
+ hourInputRef.current?.focus();
6012
+ }
6013
+ else if (field === "second") {
6014
+ minuteInputRef.current?.focus();
6015
+ }
6016
+ return;
6017
+ }
6018
+ // Handle number inputs
6019
+ if (field === "hour") {
6020
+ if (e.key.match(/^[0-9]$/)) {
6021
+ const newValue = value + e.key;
6022
+ const numValue = parseInt(newValue, 10);
6023
+ if (numValue > 23) {
6024
+ const digitValue = parseInt(e.key, 10);
6025
+ setHour(digitValue);
6026
+ onChange({ hour: digitValue, minute, second });
6027
+ return;
6028
+ }
6029
+ if (numValue >= 0 && numValue <= 23) {
6030
+ setHour(numValue);
6031
+ onChange({ hour: numValue, minute, second });
6032
+ e.preventDefault();
6033
+ minuteInputRef.current?.focus();
6034
+ }
6035
+ }
6036
+ }
6037
+ else if (field === "minute") {
6038
+ if (e.key.match(/^[0-9]$/)) {
6039
+ const newValue = value + e.key;
6040
+ const numValue = parseInt(newValue, 10);
6041
+ if (numValue > 59) {
6042
+ const digitValue = parseInt(e.key, 10);
6043
+ setMinute(digitValue);
6044
+ onChange({ hour, minute: digitValue, second });
6045
+ return;
6046
+ }
6047
+ if (numValue >= 0 && numValue <= 59) {
6048
+ setMinute(numValue);
6049
+ onChange({ hour, minute: numValue, second });
6050
+ e.preventDefault();
6051
+ secondInputRef.current?.focus();
6052
+ }
6053
+ }
6054
+ }
6055
+ else if (field === "second") {
6056
+ if (e.key.match(/^[0-9]$/)) {
6057
+ const newValue = value + e.key;
6058
+ const numValue = parseInt(newValue, 10);
6059
+ if (numValue > 59) {
6060
+ const digitValue = parseInt(e.key, 10);
6061
+ setSecond(digitValue);
6062
+ onChange({ hour, minute, second: digitValue });
6063
+ return;
6064
+ }
6065
+ if (numValue >= 0 && numValue <= 59) {
6066
+ setSecond(numValue);
6067
+ onChange({ hour, minute, second: numValue });
6068
+ }
6069
+ }
6070
+ }
6071
+ };
6072
+ const handleClear = () => {
6073
+ setHour(null);
6074
+ setMinute(null);
6075
+ setSecond(null);
6076
+ onChange({ hour: null, minute: null, second: null });
6077
+ hourInputRef.current?.focus();
6078
+ };
6079
+ return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Grid, { justifyContent: "center", alignItems: "center", templateColumns: "60px 10px 60px 10px 60px auto", gap: "2", width: "auto", minWidth: "300px", children: [jsx(Input, { ref: hourInputRef, type: "text", value: hour === null ? "" : hour.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "hour"), placeholder: "HH", maxLength: 2, textAlign: "center" }), jsx(Text, { children: ":" }), jsx(Input, { ref: minuteInputRef, type: "text", value: minute === null ? "" : minute.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "minute"), placeholder: "MM", maxLength: 2, textAlign: "center" }), jsx(Text, { children: ":" }), jsx(Input, { ref: secondInputRef, type: "text", value: second === null ? "" : second.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "second"), placeholder: "SS", maxLength: 2, textAlign: "center" }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(MdCancel, {}) })] }) }));
6080
+ }
6081
+
6082
+ function DateTimePicker$1({ value, onChange, format = "date-time", showSeconds = false, labels = {
6083
+ monthNamesShort: [
6084
+ "Jan",
6085
+ "Feb",
6086
+ "Mar",
6087
+ "Apr",
6088
+ "May",
6089
+ "Jun",
6090
+ "Jul",
6091
+ "Aug",
6092
+ "Sep",
6093
+ "Oct",
6094
+ "Nov",
6095
+ "Dec",
6096
+ ],
6097
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
6098
+ backButtonLabel: "Back",
6099
+ forwardButtonLabel: "Next",
6100
+ }, timezone = "Asia/Hong_Kong", }) {
6101
+ const [selectedDate, setSelectedDate] = useState(value || "");
6102
+ // Time state for 12-hour format
6103
+ const [hour12, setHour12] = useState(value ? dayjs(value).hour() % 12 || 12 : null);
6104
+ const [minute, setMinute] = useState(value ? dayjs(value).minute() : null);
6105
+ const [meridiem, setMeridiem] = useState(value ? (dayjs(value).hour() >= 12 ? "pm" : "am") : null);
6106
+ // Time state for 24-hour format
6107
+ const [hour24, setHour24] = useState(value ? dayjs(value).hour() : null);
6108
+ const [second, setSecond] = useState(value ? dayjs(value).second() : null);
6109
+ const handleDateChange = (date) => {
6110
+ setSelectedDate(date);
6111
+ updateDateTime(dayjs(date).tz(timezone).toISOString());
6112
+ };
6113
+ const handleTimeChange = (timeData) => {
6114
+ if (format === "iso-date-time") {
6115
+ setHour24(timeData.hour);
6116
+ setMinute(timeData.minute);
6117
+ if (showSeconds)
6118
+ setSecond(timeData.second);
6119
+ }
6120
+ else {
6121
+ setHour12(timeData.hour);
6122
+ setMinute(timeData.minute);
6123
+ setMeridiem(timeData.meridiem);
6124
+ }
6125
+ updateDateTime(dayjs(selectedDate).tz(timezone).toISOString(), timeData);
6126
+ };
6127
+ const updateDateTime = (date, timeData) => {
6128
+ if (!date) {
6129
+ onChange?.(undefined);
6130
+ return;
6131
+ }
6132
+ // use dayjs to convert the date to the timezone
6133
+ const newDate = dayjs(date).tz(timezone).toDate();
6134
+ if (format === "iso-date-time") {
6135
+ const h = timeData?.hour ?? hour24;
6136
+ const m = timeData?.minute ?? minute;
6137
+ const s = showSeconds ? timeData?.second ?? second : 0;
6138
+ if (h !== null)
6139
+ newDate.setHours(h);
6140
+ if (m !== null)
6141
+ newDate.setMinutes(m);
6142
+ if (s !== null)
6143
+ newDate.setSeconds(s);
6144
+ }
6145
+ else {
6146
+ const h = timeData?.hour ?? hour12;
6147
+ const m = timeData?.minute ?? minute;
6148
+ const mer = timeData?.meridiem ?? meridiem;
6149
+ if (h !== null && mer !== null) {
6150
+ let hour24 = h;
6151
+ if (mer === "am" && h === 12)
6152
+ hour24 = 0;
6153
+ else if (mer === "pm" && h < 12)
6154
+ hour24 = h + 12;
6155
+ newDate.setHours(hour24);
6156
+ }
6157
+ if (m !== null)
6158
+ newDate.setMinutes(m);
6159
+ newDate.setSeconds(0);
6160
+ }
6161
+ onChange?.(dayjs(newDate).tz(timezone).toISOString());
6162
+ };
6163
+ const handleClear = () => {
6164
+ setSelectedDate("");
6165
+ setHour12(null);
6166
+ setHour24(null);
6167
+ setMinute(null);
6168
+ setSecond(null);
6169
+ setMeridiem(null);
6170
+ onChange?.(undefined);
6171
+ };
6172
+ const isISO = format === "iso-date-time";
6173
+ return (jsxs(Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsx(DatePicker$1, { selected: selectedDate
6174
+ ? dayjs(selectedDate).tz(timezone).toDate()
6175
+ : new Date(), onDateSelected: ({ date }) => handleDateChange(dayjs(date).tz(timezone).toISOString()), monthsToDisplay: 1, labels: labels }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: second, setSecond: setSecond, onChange: handleTimeChange })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), selectedDate && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).format(isISO
6176
+ ? showSeconds
6177
+ ? "YYYY-MM-DD HH:mm:ss"
6178
+ : "YYYY-MM-DD HH:mm"
6179
+ : "YYYY-MM-DD hh:mm A ") }), jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).tz(timezone).format("Z") }), jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: timezone })] }))] }));
6180
+ }
6181
+
6182
+ dayjs.extend(utc);
6183
+ dayjs.extend(timezone);
6184
+ const DateTimePicker = ({ column, schema, prefix, }) => {
6185
+ const { watch, formState: { errors }, setValue, } = useFormContext();
6186
+ const { timezone, dateTimePickerLabels } = useSchemaContext();
6187
+ const formI18n = useFormI18n(column, prefix);
6188
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
6189
+ // with timezone
6190
+ dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
6191
+ const isRequired = required?.some((columnId) => columnId === column);
6192
+ const colLabel = formI18n.colLabel;
6193
+ const [open, setOpen] = useState(false);
6194
+ const selectedDate = watch(colLabel);
6195
+ const displayDate = dayjs(selectedDate)
6196
+ .tz(timezone)
6197
+ .format(displayDateFormat);
6198
+ useEffect(() => {
6199
+ try {
6200
+ if (selectedDate) {
6201
+ // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
6202
+ // For example, parse as UTC:
6203
+ const parsedDate = dayjs(selectedDate).tz(timezone);
6204
+ if (!parsedDate.isValid())
6205
+ return;
6206
+ // Format according to dateFormat from schema
6207
+ const formatted = parsedDate.format(dateFormat);
6208
+ // Update the form value only if different to avoid loops
6209
+ if (formatted !== selectedDate) {
6210
+ setValue(colLabel, formatted, {
6211
+ shouldValidate: true,
6212
+ shouldDirty: true,
6213
+ });
6214
+ }
6215
+ }
6216
+ }
6217
+ catch (e) {
6218
+ console.error(e);
6219
+ }
6220
+ }, [selectedDate, dateFormat, colLabel, setValue]);
6221
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6222
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
6223
+ setOpen(true);
6224
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), jsx(PopoverContent, { minW: '450px', children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
6225
+ setValue(colLabel, dayjs(date).tz(timezone).format(dateFormat));
6226
+ }, timezone: timezone, labels: {
6227
+ monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
6228
+ formI18n.translate.t(`common.month_1`, {
6229
+ defaultValue: 'January',
6230
+ }),
6231
+ formI18n.translate.t(`common.month_2`, {
6232
+ defaultValue: 'February',
6233
+ }),
6234
+ formI18n.translate.t(`common.month_3`, {
6235
+ defaultValue: 'March',
6236
+ }),
6237
+ formI18n.translate.t(`common.month_4`, {
6238
+ defaultValue: 'April',
6239
+ }),
6240
+ formI18n.translate.t(`common.month_5`, {
6241
+ defaultValue: 'May',
6242
+ }),
6243
+ formI18n.translate.t(`common.month_6`, {
6244
+ defaultValue: 'June',
6245
+ }),
6246
+ formI18n.translate.t(`common.month_7`, {
6247
+ defaultValue: 'July',
6248
+ }),
6249
+ formI18n.translate.t(`common.month_8`, {
6250
+ defaultValue: 'August',
6251
+ }),
6252
+ formI18n.translate.t(`common.month_9`, {
6253
+ defaultValue: 'September',
6254
+ }),
6255
+ formI18n.translate.t(`common.month_10`, {
6256
+ defaultValue: 'October',
6257
+ }),
6258
+ formI18n.translate.t(`common.month_11`, {
6259
+ defaultValue: 'November',
6260
+ }),
6261
+ formI18n.translate.t(`common.month_12`, {
6262
+ defaultValue: 'December',
6263
+ }),
6264
+ ],
6265
+ weekdayNamesShort: dateTimePickerLabels?.weekdayNamesShort ?? [
6266
+ formI18n.translate.t(`common.weekday_1`, {
6267
+ defaultValue: 'Sun',
6268
+ }),
6269
+ formI18n.translate.t(`common.weekday_2`, {
6270
+ defaultValue: 'Mon',
6271
+ }),
6272
+ formI18n.translate.t(`common.weekday_3`, {
6273
+ defaultValue: 'Tue',
6274
+ }),
6275
+ formI18n.translate.t(`common.weekday_4`, {
6276
+ defaultValue: 'Wed',
6277
+ }),
6278
+ formI18n.translate.t(`common.weekday_5`, {
6279
+ defaultValue: 'Thu',
6280
+ }),
6281
+ formI18n.translate.t(`common.weekday_6`, {
6282
+ defaultValue: 'Fri',
6283
+ }),
6284
+ formI18n.translate.t(`common.weekday_7`, {
6285
+ defaultValue: 'Sat',
6286
+ }),
6287
+ ],
6288
+ backButtonLabel: dateTimePickerLabels?.backButtonLabel ??
6289
+ formI18n.translate.t(`common.back_button`, {
6290
+ defaultValue: 'Back',
6291
+ }),
6292
+ forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ??
6293
+ formI18n.translate.t(`common.forward_button`, {
6294
+ defaultValue: 'Forward',
6295
+ }),
6296
+ } })] }) })] }) }));
6297
+ };
6298
+
4583
6299
  const SchemaRenderer = ({ schema, prefix, column, }) => {
4584
6300
  const colSchema = schema;
4585
- const { type, variant, properties: innerProperties, foreign_key, items, } = schema;
4586
- if (type === "string") {
6301
+ const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
6302
+ if (variant === 'custom-input') {
6303
+ return jsx(CustomInput, { schema: colSchema, prefix, column });
6304
+ }
6305
+ if (type === 'string') {
4587
6306
  if ((schema.enum ?? []).length > 0) {
4588
6307
  return jsx(EnumPicker, { schema: colSchema, prefix, column });
4589
6308
  }
4590
- if (variant === "id-picker") {
6309
+ if (variant === 'id-picker') {
4591
6310
  idPickerSanityCheck(column, foreign_key);
4592
6311
  return jsx(IdPicker, { schema: colSchema, prefix, column });
4593
6312
  }
4594
- if (variant === "date-picker") {
6313
+ if (format === 'date') {
4595
6314
  return jsx(DatePicker, { schema: colSchema, prefix, column });
4596
6315
  }
6316
+ if (format === 'time') {
6317
+ return jsx(TimePicker, { schema: colSchema, prefix, column });
6318
+ }
6319
+ if (format === 'date-time') {
6320
+ return jsx(DateTimePicker, { schema: colSchema, prefix, column });
6321
+ }
6322
+ if (variant === 'text-area') {
6323
+ return jsx(TextAreaInput, { schema: colSchema, prefix, column });
6324
+ }
4597
6325
  return jsx(StringInputField, { schema: colSchema, prefix, column });
4598
6326
  }
4599
- if (type === "number" || type === "integer") {
6327
+ if (type === 'number' || type === 'integer') {
4600
6328
  return jsx(NumberInputField, { schema: colSchema, prefix, column });
4601
6329
  }
4602
- if (type === "boolean") {
6330
+ if (type === 'boolean') {
4603
6331
  return jsx(BooleanPicker, { schema: colSchema, prefix, column });
4604
6332
  }
4605
- if (type === "object") {
6333
+ if (type === 'object') {
4606
6334
  if (innerProperties) {
4607
6335
  return jsx(ObjectInput, { schema: colSchema, prefix, column });
4608
6336
  }
4609
6337
  return jsx(RecordInput$1, { schema: colSchema, prefix, column });
4610
6338
  }
4611
- if (type === "array") {
4612
- if (variant === "id-picker") {
6339
+ if (type === 'array') {
6340
+ if (variant === 'id-picker') {
4613
6341
  idPickerSanityCheck(column, foreign_key);
4614
6342
  return (jsx(IdPicker, { schema: colSchema, prefix, column, isMultiple: true }));
4615
6343
  }
4616
- if (variant === "tag-picker") {
6344
+ if (variant === 'tag-picker') {
4617
6345
  return jsx(TagPicker, { schema: colSchema, prefix, column });
4618
6346
  }
4619
- if (variant === "file-picker") {
6347
+ if (variant === 'file-picker') {
4620
6348
  return jsx(FilePicker, { schema: colSchema, prefix, column });
4621
6349
  }
6350
+ if (variant === 'date-range') {
6351
+ return jsx(DateRangePicker, { schema: colSchema, prefix, column });
6352
+ }
6353
+ if (variant === 'enum-picker') {
6354
+ const { items } = colSchema;
6355
+ const { enum: enumItems } = items;
6356
+ const enumSchema = {
6357
+ type: 'string',
6358
+ enum: enumItems,
6359
+ };
6360
+ return (jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
6361
+ }
4622
6362
  if (items) {
4623
6363
  return jsx(ArrayRenderer, { schema: colSchema, prefix, column });
4624
6364
  }
4625
6365
  return jsx(Text, { children: `array ${column}` });
4626
6366
  }
4627
- if (type === "null") {
6367
+ if (type === 'null') {
4628
6368
  return jsx(Text, { children: `null ${column}` });
4629
6369
  }
4630
6370
  return jsx(Text, { children: "missing type" });
4631
6371
  };
4632
6372
 
4633
- const ColumnRenderer = ({ column, properties, prefix, }) => {
6373
+ const ColumnRenderer = ({ column, properties, prefix, parentRequired, }) => {
4634
6374
  const colSchema = properties[column];
4635
6375
  const colLabel = `${prefix}${column}`;
4636
6376
  if (colSchema === undefined) {
4637
6377
  throw new Error(`${colLabel} does not exist when using ColumnRenderer`);
4638
6378
  }
4639
- return jsx(SchemaRenderer, { schema: colSchema, prefix, column });
6379
+ // Merge parent's required array with the schema's required array
6380
+ const schemaWithRequired = {
6381
+ ...colSchema,
6382
+ required: parentRequired || colSchema.required,
6383
+ };
6384
+ return jsx(SchemaRenderer, { schema: schemaWithRequired, prefix, column });
4640
6385
  };
4641
6386
 
4642
6387
  const ArrayViewer = ({ schema, column, prefix }) => {
4643
- const { gridRow, gridColumn = "1/span 12", required, items } = schema;
6388
+ const { gridColumn = "span 12", gridRow = "span 1", required, items, } = schema;
4644
6389
  const { translate } = useSchemaContext();
4645
6390
  const colLabel = `${prefix}${column}`;
4646
6391
  const isRequired = required?.some((columnId) => columnId === column);
4647
6392
  const { watch, formState: { errors }, } = useFormContext();
4648
6393
  const values = watch(colLabel) ?? [];
4649
- return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: "1/span12", children: [`${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, isRequired && jsx("span", { children: "*" })] }), values.map((field, index) => (jsx(Flex, { flexFlow: "column", children: jsx(Grid, { gap: "4", padding: "4", gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat("auto-fit", auto)`, children: jsx(SchemaViewer, { column: `${index}`,
4650
- prefix: `${colLabel}.`,
4651
- schema: items }) }) }, `form-${prefix}${column}.${index}`))), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6394
+ return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: "1/span12", children: [`${translate.t(removeIndex(`${colLabel}.field_label`))}`, isRequired && jsx("span", { children: "*" })] }), jsx(Flex, { flexFlow: "column", gap: 1, children: values.map((field, index) => (jsx(Flex, { flexFlow: "column", bgColor: { base: "colorPalette.100", _dark: "colorPalette.900" }, p: "2", borderRadius: "md", borderWidth: "thin", borderColor: {
6395
+ base: "colorPalette.200",
6396
+ _dark: "colorPalette.800",
6397
+ }, children: jsx(Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: jsx(SchemaViewer, { column: `${index}`,
6398
+ prefix: `${colLabel}.`,
6399
+ // @ts-expect-error find suitable types
6400
+ schema: { showLabel: false, ...(items ?? {}) } }) }) }, `form-${prefix}${column}.${index}`))) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4652
6401
  };
4653
6402
 
4654
6403
  const BooleanViewer = ({ schema, column, prefix, }) => {
4655
6404
  const { watch, formState: { errors }, } = useFormContext();
4656
6405
  const { translate } = useSchemaContext();
4657
- const { required, gridColumn, gridRow } = schema;
6406
+ const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
4658
6407
  const isRequired = required?.some((columnId) => columnId === column);
4659
6408
  const colLabel = `${prefix}${column}`;
4660
6409
  const value = watch(colLabel);
4661
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6410
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
4662
6411
  gridRow, children: [jsx(Text, { children: value
4663
6412
  ? translate.t(removeIndex(`${colLabel}.true`))
4664
- : translate.t(removeIndex(`${colLabel}.false`)) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6413
+ : translate.t(removeIndex(`${colLabel}.false`)) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
6414
+ };
6415
+
6416
+ const CustomViewer = ({ column, schema, prefix }) => {
6417
+ const formContext = useFormContext();
6418
+ const { inputViewerRender } = schema;
6419
+ return (inputViewerRender &&
6420
+ inputViewerRender({
6421
+ column,
6422
+ schema,
6423
+ prefix,
6424
+ formContext,
6425
+ }));
4665
6426
  };
4666
6427
 
4667
6428
  const DateViewer = ({ column, schema, prefix }) => {
4668
6429
  const { watch, formState: { errors }, } = useFormContext();
4669
- const { translate } = useSchemaContext();
4670
- const { required, gridColumn, gridRow } = schema;
6430
+ const { translate, timezone } = useSchemaContext();
6431
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", } = schema;
4671
6432
  const isRequired = required?.some((columnId) => columnId === column);
4672
6433
  const colLabel = `${prefix}${column}`;
4673
6434
  const selectedDate = watch(colLabel);
4674
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
4675
- gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? selectedDate : ""] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.fieldRequired`) }))] }));
6435
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
6436
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6437
+ gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
4676
6438
  };
4677
6439
 
4678
6440
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
4679
6441
  const { watch, formState: { errors }, } = useFormContext();
4680
- const { translate } = useSchemaContext();
6442
+ const formI18n = useFormI18n(column, prefix);
4681
6443
  const { required } = schema;
4682
6444
  const isRequired = required?.some((columnId) => columnId === column);
4683
- const { gridColumn, gridRow, renderDisplay } = schema;
4684
- const colLabel = `${prefix}${column}`;
6445
+ const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
6446
+ const colLabel = formI18n.colLabel;
4685
6447
  const watchEnum = watch(colLabel);
4686
6448
  const watchEnums = (watch(colLabel) ?? []);
4687
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6449
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
4688
6450
  gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: "wrap", gap: 1, children: watchEnums.map((enumValue) => {
4689
6451
  const item = enumValue;
4690
6452
  if (item === undefined) {
4691
6453
  return jsx(Fragment, { children: "undefined" });
4692
6454
  }
4693
- return (jsx(Tag, { closable: true, children: !!renderDisplay === true
6455
+ return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
4694
6456
  ? renderDisplay(item)
4695
- : translate.t(removeIndex(`${colLabel}.${item}`)) }));
4696
- }) })), !isMultiple && (jsx(Text, { children: translate.t(removeIndex(`${colLabel}.${watchEnum}`)) })), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6457
+ : formI18n.t(item) }, item));
6458
+ }) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: formI18n.required() }))] }));
4697
6459
  };
4698
6460
 
4699
6461
  const FileViewer = ({ column, schema, prefix }) => {
4700
- const { setValue, formState: { errors }, watch, } = useFormContext();
6462
+ const { watch } = useFormContext();
4701
6463
  const { translate } = useSchemaContext();
4702
- const { required, gridColumn, gridRow } = schema;
6464
+ const { required, gridColumn = "span 12", gridRow = "span 1", } = schema;
4703
6465
  const isRequired = required?.some((columnId) => columnId === column);
4704
6466
  const currentFiles = (watch(column) ?? []);
4705
6467
  const colLabel = `${prefix}${column}`;
4706
- return (jsxs(Field, { label: `${translate.t(`${colLabel}.fieldLabel`)}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", display: "grid", gridTemplateRows: "auto 1fr auto", alignItems: "stretch", children: [jsx(FileDropzone, { onDrop: ({ files }) => {
4707
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4708
- setValue(colLabel, [...currentFiles, ...newFiles]);
4709
- }, placeholder: translate.t(`${colLabel}.fileDropzone`) }), jsx(Flex, { flexFlow: "column", gap: 1, children: currentFiles.map((file) => {
4710
- return (jsx(Card.Root, { variant: "subtle", children: jsxs(Card.Body, { gap: "2", cursor: "pointer", onClick: () => {
4711
- setValue(column, currentFiles.filter(({ name }) => {
4712
- return name !== file.name;
4713
- }));
4714
- }, display: "flex", flexFlow: "row", alignItems: "center", padding: "2", children: [jsx(Box, { children: file.name }), jsx(TiDeleteOutline, {})] }) }, file.name));
4715
- }) }), errors[`${colLabel}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6468
+ return (jsx(Field, { label: `${translate.t(`${colLabel}.field_label`)}`, required: isRequired, gridColumn: gridColumn, gridRow: gridRow, display: "grid", gridTemplateRows: "auto 1fr auto", alignItems: "stretch", children: jsx(Flex, { flexFlow: "column", gap: 1, children: currentFiles.map((file) => {
6469
+ return (jsx(Card.Root, { variant: "subtle", children: jsxs(Card.Body, { gap: "2", display: "flex", flexFlow: "row", alignItems: "center", padding: "2", children: [file.type.startsWith("image/") && (jsx(Image, { src: URL.createObjectURL(file), alt: file.name, boxSize: "50px", objectFit: "cover", borderRadius: "md", marginRight: "2" })), jsx(Box, { children: file.name })] }) }, file.name));
6470
+ }) }) }));
4716
6471
  };
4717
6472
 
4718
6473
  const IdViewer = ({ column, schema, prefix, isMultiple = false, }) => {
4719
6474
  const { watch, formState: { errors }, } = useFormContext();
4720
6475
  const { idMap, translate } = useSchemaContext();
4721
- const { required, gridColumn, gridRow, renderDisplay, foreign_key } = schema;
6476
+ const { required, gridColumn = "span 12", gridRow = "span 1", renderDisplay, foreign_key, } = schema;
4722
6477
  const isRequired = required?.some((columnId) => columnId === column);
4723
6478
  const { display_column } = foreign_key;
4724
6479
  const colLabel = `${prefix}${column}`;
@@ -4734,7 +6489,7 @@ const IdViewer = ({ column, schema, prefix, isMultiple = false, }) => {
4734
6489
  }
4735
6490
  return record[display_column];
4736
6491
  };
4737
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.fieldLabel`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6492
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
4738
6493
  gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: "wrap", gap: 1, children: watchIds.map((id) => {
4739
6494
  const item = idMap[id];
4740
6495
  if (item === undefined) {
@@ -4743,21 +6498,39 @@ const IdViewer = ({ column, schema, prefix, isMultiple = false, }) => {
4743
6498
  return (jsx(Tag, { closable: true, children: !!renderDisplay === true
4744
6499
  ? renderDisplay(item)
4745
6500
  : item[display_column] }, id));
4746
- }) })), !isMultiple && jsx(Text, { children: getPickedValue() }), errors[`${colLabel}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6501
+ }) })), !isMultiple && jsx(Text, { children: getPickedValue() }), errors[`${colLabel}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4747
6502
  };
4748
6503
 
4749
6504
  const NumberViewer = ({ schema, column, prefix, }) => {
4750
6505
  const { watch, formState: { errors }, } = useFormContext();
4751
6506
  const { translate } = useSchemaContext();
4752
- const { required, gridColumn, gridRow } = schema;
6507
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
4753
6508
  const isRequired = required?.some((columnId) => columnId === column);
4754
6509
  const colLabel = `${prefix}${column}`;
4755
6510
  const value = watch(colLabel);
4756
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, gridColumn, gridRow, children: [jsx(Text, { children: value }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6511
+ // Format the value for display if formatOptions are provided
6512
+ const formatValue = (val) => {
6513
+ if (val === undefined || val === null || val === '')
6514
+ return '';
6515
+ const numValue = typeof val === 'string' ? parseFloat(val) : val;
6516
+ if (isNaN(numValue))
6517
+ return String(val);
6518
+ // Use formatOptions if available, otherwise display as-is
6519
+ if (schema.formatOptions) {
6520
+ try {
6521
+ return new Intl.NumberFormat(undefined, schema.formatOptions).format(numValue);
6522
+ }
6523
+ catch {
6524
+ return String(val);
6525
+ }
6526
+ }
6527
+ return String(val);
6528
+ };
6529
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn, gridRow, children: [jsx(Text, { children: formatValue(value) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4757
6530
  };
4758
6531
 
4759
6532
  const ObjectViewer = ({ schema, column, prefix }) => {
4760
- const { properties, gridRow, gridColumn = "1/span 12", required } = schema;
6533
+ const { properties, gridColumn = "span 12", gridRow = "span 1", required, showLabel = true, } = schema;
4761
6534
  const { translate } = useSchemaContext();
4762
6535
  const colLabel = `${prefix}${column}`;
4763
6536
  const isRequired = required?.some((columnId) => columnId === column);
@@ -4765,25 +6538,28 @@ const ObjectViewer = ({ schema, column, prefix }) => {
4765
6538
  if (properties === undefined) {
4766
6539
  throw new Error(`properties is undefined when using ObjectInput`);
4767
6540
  }
4768
- return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: "1/span12", children: [`${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, isRequired && jsx("span", { children: "*" })] }), jsx(Grid, { gap: "4", padding: "4", gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat("auto-fit", auto)`, children: Object.keys(properties ?? {}).map((key) => {
6541
+ return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [`${translate.t(removeIndex(`${colLabel}.field_label`))}`, isRequired && jsx("span", { children: "*" })] })), jsx(Grid, { gap: "4", padding: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", bgColor: { base: "colorPalette.100", _dark: "colorPalette.900" }, p: "1", borderRadius: "md", borderWidth: "thin", borderColor: {
6542
+ base: "colorPalette.200",
6543
+ _dark: "colorPalette.800",
6544
+ }, children: Object.keys(properties ?? {}).map((key) => {
4769
6545
  return (
4770
6546
  // @ts-expect-error find suitable types
4771
6547
  jsx(ColumnViewer, { column: `${key}`,
4772
6548
  prefix: `${prefix}${column}.`,
4773
6549
  properties }, `form-objectviewer-${colLabel}-${key}`));
4774
- }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }));
6550
+ }) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4775
6551
  };
4776
6552
 
4777
6553
  const RecordInput = ({ column, schema, prefix }) => {
4778
6554
  const { formState: { errors }, setValue, getValues, } = useFormContext();
4779
6555
  const { translate } = useSchemaContext();
4780
- const { required, gridColumn, gridRow } = schema;
6556
+ const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
4781
6557
  const isRequired = required?.some((columnId) => columnId === column);
4782
6558
  const entries = Object.entries(getValues(column) ?? {});
4783
6559
  const [showNewEntries, setShowNewEntries] = useState(false);
4784
6560
  const [newKey, setNewKey] = useState();
4785
6561
  const [newValue, setNewValue] = useState();
4786
- return (jsxs(Field, { label: `${translate.t(`${column}.fieldLabel`)}`, required: isRequired, alignItems: "stretch", gridColumn, gridRow, children: [entries.map(([key, value]) => {
6562
+ return (jsxs(Field, { label: `${translate.t(`${column}.field_label`)}`, required: isRequired, alignItems: "stretch", gridColumn, gridRow, children: [entries.map(([key, value]) => {
4787
6563
  return (jsxs(Grid, { templateColumns: "1fr 1fr auto", gap: 1, children: [jsx(Input, { value: key, onChange: (e) => {
4788
6564
  const filtered = entries.filter(([target]) => {
4789
6565
  return target !== key;
@@ -4823,7 +6599,17 @@ const RecordInput = ({ column, schema, prefix }) => {
4823
6599
  setShowNewEntries(true);
4824
6600
  setNewKey(undefined);
4825
6601
  setNewValue(undefined);
4826
- }, children: translate.t(`${column}.addNew`) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.fieldRequired`) }))] }));
6602
+ }, children: translate.t(`${column}.addNew`) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
6603
+ };
6604
+
6605
+ const StringViewer = ({ column, schema, prefix, }) => {
6606
+ const { watch, formState: { errors }, } = useFormContext();
6607
+ const { translate } = useSchemaContext();
6608
+ const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
6609
+ const isRequired = required?.some((columnId) => columnId === column);
6610
+ const colLabel = `${prefix}${column}`;
6611
+ const value = watch(colLabel);
6612
+ return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", children: [jsx(Text, { children: value }), errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
4827
6613
  };
4828
6614
 
4829
6615
  const TagViewer = ({ column, schema, prefix }) => {
@@ -4911,19 +6697,50 @@ const TagViewer = ({ column, schema, prefix }) => {
4911
6697
  }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
4912
6698
  };
4913
6699
 
4914
- const StringViewer = ({ column, schema, prefix, }) => {
6700
+ const TextAreaViewer = ({ column, schema, prefix, }) => {
4915
6701
  const { watch, formState: { errors }, } = useFormContext();
4916
6702
  const { translate } = useSchemaContext();
4917
- const { required, gridColumn, gridRow } = schema;
6703
+ const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
4918
6704
  const isRequired = required?.some((columnId) => columnId === column);
4919
6705
  const colLabel = `${prefix}${column}`;
4920
6706
  const value = watch(colLabel);
4921
- return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.fieldLabel`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", children: [jsx(Text, { children: value }), errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.fieldRequired`)) }))] }) }));
6707
+ return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn, gridRow: gridRow, children: [jsx(Text, { whiteSpace: "pre-wrap", children: value }), " ", errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
6708
+ };
6709
+
6710
+ const TimeViewer = ({ column, schema, prefix }) => {
6711
+ const { watch, formState: { errors }, } = useFormContext();
6712
+ const { translate, timezone } = useSchemaContext();
6713
+ const { required, gridColumn = "span 12", gridRow = "span 1", displayTimeFormat = "hh:mm A", } = schema;
6714
+ const isRequired = required?.some((columnId) => columnId === column);
6715
+ const colLabel = `${prefix}${column}`;
6716
+ const selectedDate = watch(colLabel);
6717
+ const displayedTime = dayjs(`1970-01-01T${selectedDate}`)
6718
+ .tz(timezone)
6719
+ .isValid()
6720
+ ? dayjs(`1970-01-01T${selectedDate}`).tz(timezone).format(displayTimeFormat)
6721
+ : "";
6722
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6723
+ gridRow, children: [jsx(Text, { children: displayedTime }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
6724
+ };
6725
+
6726
+ const DateTimeViewer = ({ column, schema, prefix }) => {
6727
+ const { watch, formState: { errors }, } = useFormContext();
6728
+ const { translate, timezone } = useSchemaContext();
6729
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD HH:mm:ss", } = schema;
6730
+ const isRequired = required?.some((columnId) => columnId === column);
6731
+ const colLabel = `${prefix}${column}`;
6732
+ const selectedDate = watch(colLabel);
6733
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
6734
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
6735
+ gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
4922
6736
  };
4923
6737
 
4924
6738
  const SchemaViewer = ({ schema, prefix, column, }) => {
4925
6739
  const colSchema = schema;
4926
- const { type, variant, properties: innerProperties, foreign_key, items, } = schema;
6740
+ const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
6741
+ if (variant === "custom-input") {
6742
+ return jsx(CustomViewer, { schema: colSchema, prefix, column });
6743
+ }
4927
6744
  if (type === "string") {
4928
6745
  if ((schema.enum ?? []).length > 0) {
4929
6746
  return jsx(EnumViewer, { schema: colSchema, prefix, column });
@@ -4932,9 +6749,18 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
4932
6749
  idPickerSanityCheck(column, foreign_key);
4933
6750
  return jsx(IdViewer, { schema: colSchema, prefix, column });
4934
6751
  }
4935
- if (variant === "date-picker") {
6752
+ if (format === "time") {
6753
+ return jsx(TimeViewer, { schema: colSchema, prefix, column });
6754
+ }
6755
+ if (format === "date") {
4936
6756
  return jsx(DateViewer, { schema: colSchema, prefix, column });
4937
6757
  }
6758
+ if (format === "date-time") {
6759
+ return jsx(DateTimeViewer, { schema: colSchema, prefix, column });
6760
+ }
6761
+ if (variant === "text-area") {
6762
+ return jsx(TextAreaViewer, { schema: colSchema, prefix, column });
6763
+ }
4938
6764
  return jsx(StringViewer, { schema: colSchema, prefix, column });
4939
6765
  }
4940
6766
  if (type === "number" || type === "integer") {
@@ -4960,6 +6786,15 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
4960
6786
  if (variant === "file-picker") {
4961
6787
  return jsx(FileViewer, { schema: colSchema, prefix, column });
4962
6788
  }
6789
+ if (variant === "enum-picker") {
6790
+ const { items } = schema;
6791
+ const { enum: enumItems } = items;
6792
+ const enumSchema = {
6793
+ type: "string",
6794
+ enum: enumItems,
6795
+ };
6796
+ return (jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
6797
+ }
4963
6798
  if (items) {
4964
6799
  return jsx(ArrayViewer, { schema: colSchema, prefix, column });
4965
6800
  }
@@ -4983,66 +6818,43 @@ const ColumnViewer = ({ column, properties, prefix, }) => {
4983
6818
  };
4984
6819
 
4985
6820
  const SubmitButton = () => {
4986
- const { translate, setValidatedData, setIsError, setIsConfirming } = useSchemaContext();
6821
+ const { translate, setValidatedData, setIsError, setIsConfirming, setError, schema, requireConfirmation, onFormSubmit, } = useSchemaContext();
4987
6822
  const methods = useFormContext();
4988
6823
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4989
6824
  const onValid = (data) => {
4990
- setValidatedData(data);
4991
- setIsError(false);
4992
- setIsConfirming(true);
6825
+ // const { isValid, errors } = validateData(data, schema);
6826
+ // if (!isValid) {
6827
+ // setError({
6828
+ // type: 'validation',
6829
+ // errors,
6830
+ // });
6831
+ // setIsError(true);
6832
+ // return;
6833
+ // }
6834
+ // If validation passes, check if confirmation is required
6835
+ if (requireConfirmation) {
6836
+ // Show confirmation (existing behavior)
6837
+ setValidatedData(data);
6838
+ setIsError(false);
6839
+ setIsConfirming(true);
6840
+ }
6841
+ else {
6842
+ // Skip confirmation and submit directly
6843
+ setValidatedData(data);
6844
+ setIsError(false);
6845
+ onFormSubmit(data);
6846
+ }
4993
6847
  };
4994
6848
  return (jsx(Button$1, { onClick: () => {
4995
6849
  methods.handleSubmit(onValid)();
4996
- }, formNoValidate: true, children: translate.t("submit") }));
6850
+ }, formNoValidate: true, children: translate.t('submit') }));
4997
6851
  };
4998
6852
 
4999
6853
  const FormBody = () => {
5000
- const { schema, requestUrl, order, ignore, include, onSubmit, rowNumber, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, } = useSchemaContext();
6854
+ const { schema, order, ignore, include, translate, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, onFormSubmit, } = useSchemaContext();
6855
+ const { showSubmitButton, showResetButton } = displayConfig;
5001
6856
  const methods = useFormContext();
5002
6857
  const { properties } = schema;
5003
- const onBeforeSubmit = () => {
5004
- setIsSubmiting(true);
5005
- };
5006
- const onAfterSubmit = () => {
5007
- setIsSubmiting(false);
5008
- };
5009
- const onSubmitError = (error) => {
5010
- setIsError(true);
5011
- setError(error);
5012
- };
5013
- const onSubmitSuccess = () => {
5014
- setIsSuccess(true);
5015
- };
5016
- const defaultOnSubmit = async (promise) => {
5017
- try {
5018
- onBeforeSubmit();
5019
- await promise;
5020
- onSubmitSuccess();
5021
- }
5022
- catch (error) {
5023
- onSubmitError(error);
5024
- }
5025
- finally {
5026
- onAfterSubmit();
5027
- }
5028
- };
5029
- const defaultSubmitPromise = (data) => {
5030
- const options = {
5031
- method: "POST",
5032
- url: `${requestUrl}`,
5033
- data: clearEmptyString(data),
5034
- ...requestOptions,
5035
- };
5036
- return axios.request(options);
5037
- };
5038
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5039
- const onFormSubmit = async (data) => {
5040
- if (onSubmit === undefined) {
5041
- await defaultOnSubmit(defaultSubmitPromise(data));
5042
- return;
5043
- }
5044
- await defaultOnSubmit(onSubmit(data));
5045
- };
5046
6858
  const renderColumns = ({ order, keys, ignore, include, }) => {
5047
6859
  const included = include.length > 0 ? include : keys;
5048
6860
  const not_exist = included.filter((columnA) => !order.some((columnB) => columnA === columnB));
@@ -5057,38 +6869,42 @@ const FormBody = () => {
5057
6869
  include,
5058
6870
  });
5059
6871
  if (isSuccess) {
5060
- return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Title, { children: translate.t("submitSuccess") })] }), jsx(Flex, { justifyContent: "end", children: jsx(Button$1, { onClick: async () => {
5061
- setIsError(false);
5062
- setIsSubmiting(false);
5063
- setIsSuccess(false);
5064
- setIsConfirming(false);
5065
- setValidatedData(undefined);
5066
- const data = await getUpdatedData();
5067
- methods.reset(data);
5068
- }, formNoValidate: true, children: translate.t("submitAgain") }) })] }));
6872
+ const resetHandler = async () => {
6873
+ setIsError(false);
6874
+ setIsSubmiting(false);
6875
+ setIsSuccess(false);
6876
+ setIsConfirming(false);
6877
+ setValidatedData(undefined);
6878
+ const data = await getUpdatedData();
6879
+ methods.reset(data);
6880
+ };
6881
+ if (customSuccessRenderer) {
6882
+ return customSuccessRenderer(resetHandler);
6883
+ }
6884
+ return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Content, { children: jsx(Alert.Title, { children: translate.t('submit_success') }) })] }), jsx(Flex, { justifyContent: 'end', children: jsx(Button$1, { onClick: resetHandler, formNoValidate: true, children: translate.t('submit_again') }) })] }));
5069
6885
  }
5070
6886
  if (isConfirming) {
5071
- return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsx(Grid, { gap: 4, gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat(${rowNumber ?? "auto-fit"}, auto)`, children: ordered.map((column) => {
6887
+ return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsx(Grid, { gap: 4, gridTemplateColumns: 'repeat(12, 1fr)', gridTemplateRows: 'repeat(12, max-content)', autoFlow: 'row', children: ordered.map((column) => {
5072
6888
  return (jsx(ColumnViewer
5073
6889
  // @ts-expect-error find suitable types
5074
6890
  , {
5075
6891
  // @ts-expect-error find suitable types
5076
6892
  properties: properties, prefix: ``, column }, `form-viewer-${column}`));
5077
- }) }), jsxs(Flex, { justifyContent: "end", gap: "2", children: [jsx(Button$1, { onClick: () => {
6893
+ }) }), jsxs(Flex, { justifyContent: 'end', gap: '2', children: [jsx(Button$1, { onClick: () => {
5078
6894
  setIsConfirming(false);
5079
- }, variant: "subtle", children: translate.t("cancel") }), jsx(Button$1, { onClick: () => {
6895
+ }, variant: 'subtle', children: translate.t('cancel') }), jsx(Button$1, { onClick: () => {
5080
6896
  onFormSubmit(validatedData);
5081
- }, children: translate.t("confirm") })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && (jsx(Fragment, { children: jsx(Alert.Root, { status: "error", children: jsx(Alert.Title, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsxs(AccordionItemTrigger, { children: [jsx(Alert.Indicator, {}), `${error}`] }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) }) }))] }));
6897
+ }, children: translate.t('confirm') })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && customErrorRenderer && customErrorRenderer(error)] }));
5082
6898
  }
5083
- return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", gridTemplateRows: `repeat(${rowNumber ?? "auto-fit"}, auto)`, children: ordered.map((column) => {
6899
+ return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
5084
6900
  return (jsx(ColumnRenderer
5085
6901
  // @ts-expect-error find suitable types
5086
6902
  , {
5087
6903
  // @ts-expect-error find suitable types
5088
- properties: properties, prefix: ``, column }, `form-input-${column}`));
5089
- }) }), jsxs(Flex, { justifyContent: "end", gap: "2", children: [jsx(Button$1, { onClick: () => {
6904
+ properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
6905
+ }) }), jsxs(Flex, { justifyContent: 'end', gap: "2", children: [showResetButton && (jsx(Button$1, { onClick: () => {
5090
6906
  methods.reset();
5091
- }, variant: "subtle", children: translate.t("reset") }), jsx(SubmitButton, {})] })] }));
6907
+ }, variant: 'subtle', children: translate.t('reset') })), showSubmitButton && jsx(SubmitButton, {})] }), isError && customErrorRenderer && customErrorRenderer(error)] }));
5092
6908
  };
5093
6909
 
5094
6910
  const FormTitle = () => {
@@ -5097,15 +6913,19 @@ const FormTitle = () => {
5097
6913
  };
5098
6914
 
5099
6915
  const DefaultForm = ({ formConfig, }) => {
5100
- return (jsx(FormRoot, { ...formConfig, children: jsxs(Grid, { gap: "2", children: [jsx(FormTitle, {}), jsx(FormBody, {})] }) }));
6916
+ const { showTitle } = formConfig.displayConfig ?? {};
6917
+ return (jsx(FormRoot, { ...formConfig, children: jsxs(Grid, { gap: "2", children: [showTitle && jsx(FormTitle, {}), jsx(FormBody, {})] }) }));
5101
6918
  };
5102
6919
 
5103
- const useForm = ({ preLoadedValues, keyPrefix }) => {
6920
+ const useForm = ({ preLoadedValues, keyPrefix, namespace, schema, }) => {
5104
6921
  const form = useForm$1({
5105
6922
  values: preLoadedValues,
6923
+ resolver: schema ? ajvResolver(schema) : undefined,
6924
+ mode: 'onBlur',
6925
+ reValidateMode: 'onBlur',
5106
6926
  });
5107
6927
  const [idMap, setIdMap] = useState({});
5108
- const translate = useTranslation("", { keyPrefix });
6928
+ const translate = useTranslation(namespace || '', { keyPrefix });
5109
6929
  return {
5110
6930
  form,
5111
6931
  idMap,
@@ -5114,6 +6934,250 @@ const useForm = ({ preLoadedValues, keyPrefix }) => {
5114
6934
  };
5115
6935
  };
5116
6936
 
6937
+ /**
6938
+ * Type definitions for error message configuration
6939
+ */
6940
+ /**
6941
+ * Schema-level error message builder
6942
+ *
6943
+ * Builds a complete errorMessage object compatible with ajv-errors plugin.
6944
+ * Supports both i18n translation keys and plain string messages.
6945
+ *
6946
+ * @param config - Error message configuration
6947
+ * @returns Complete errorMessage object for JSON Schema
6948
+ *
6949
+ * @example
6950
+ * ```typescript
6951
+ * // Simple required field errors
6952
+ * const errorMessage = buildErrorMessages({
6953
+ * required: {
6954
+ * username: "Username is required",
6955
+ * email: "user.email.field_required" // i18n key
6956
+ * }
6957
+ * });
6958
+ *
6959
+ * // With validation rules
6960
+ * const errorMessage = buildErrorMessages({
6961
+ * required: {
6962
+ * password: "Password is required"
6963
+ * },
6964
+ * properties: {
6965
+ * password: {
6966
+ * minLength: "Password must be at least 8 characters",
6967
+ * pattern: "Password must contain letters and numbers"
6968
+ * },
6969
+ * age: {
6970
+ * minimum: "Must be 18 or older",
6971
+ * maximum: "Must be under 120"
6972
+ * }
6973
+ * }
6974
+ * });
6975
+ *
6976
+ * // With global fallbacks
6977
+ * const errorMessage = buildErrorMessages({
6978
+ * required: {
6979
+ * email: "Email is required"
6980
+ * },
6981
+ * minLength: "This field is too short", // applies to all fields
6982
+ * minimum: "Value is too small"
6983
+ * });
6984
+ * ```
6985
+ */
6986
+ const buildErrorMessages = (config) => {
6987
+ const result = {};
6988
+ // Add required field errors
6989
+ if (config.required && Object.keys(config.required).length > 0) {
6990
+ result.required = config.required;
6991
+ }
6992
+ // Add field-specific validation errors
6993
+ if (config.properties && Object.keys(config.properties).length > 0) {
6994
+ result.properties = config.properties;
6995
+ }
6996
+ // Add global fallback error messages
6997
+ const globalKeys = [
6998
+ 'minLength',
6999
+ 'maxLength',
7000
+ 'pattern',
7001
+ 'minimum',
7002
+ 'maximum',
7003
+ 'multipleOf',
7004
+ 'format',
7005
+ 'type',
7006
+ 'enum',
7007
+ ];
7008
+ globalKeys.forEach((key) => {
7009
+ if (config[key]) {
7010
+ result[key] = config[key];
7011
+ }
7012
+ });
7013
+ return result;
7014
+ };
7015
+ /**
7016
+ * Converts buildErrorMessages result to ajv-errors compatible format
7017
+ */
7018
+ const convertToAjvErrorsFormat = (errorMessages) => {
7019
+ const result = {};
7020
+ // Convert required field errors
7021
+ if (errorMessages.required) {
7022
+ result.required = errorMessages.required;
7023
+ }
7024
+ // Convert properties errors to ajv-errors format
7025
+ if (errorMessages.properties) {
7026
+ result.properties = {};
7027
+ Object.keys(errorMessages.properties).forEach((fieldName) => {
7028
+ const fieldErrors = errorMessages.properties[fieldName];
7029
+ result.properties[fieldName] = {};
7030
+ Object.keys(fieldErrors).forEach((keyword) => {
7031
+ result.properties[fieldName][keyword] =
7032
+ fieldErrors[keyword];
7033
+ });
7034
+ });
7035
+ }
7036
+ // Add global fallback errors
7037
+ const globalKeys = [
7038
+ 'minLength',
7039
+ 'maxLength',
7040
+ 'pattern',
7041
+ 'minimum',
7042
+ 'maximum',
7043
+ 'multipleOf',
7044
+ 'format',
7045
+ 'type',
7046
+ 'enum',
7047
+ ];
7048
+ globalKeys.forEach((key) => {
7049
+ if (errorMessages[key]) {
7050
+ result[key] = errorMessages[key];
7051
+ }
7052
+ });
7053
+ return result;
7054
+ };
7055
+ /**
7056
+ * Helper function to build required field errors
7057
+ *
7058
+ * Simplifies creating required field error messages, especially useful
7059
+ * for generating i18n translation keys following a pattern.
7060
+ *
7061
+ * @param fields - Array of required field names
7062
+ * @param messageOrGenerator - Either a string template or function to generate messages
7063
+ * @returns Required field error configuration
7064
+ *
7065
+ * @example
7066
+ * ```typescript
7067
+ * // Plain string messages
7068
+ * const required = buildRequiredErrors(
7069
+ * ["username", "email", "password"],
7070
+ * (field) => `${field} is required`
7071
+ * );
7072
+ * // Result: { username: "username is required", email: "email is required", ... }
7073
+ *
7074
+ * // i18n translation keys
7075
+ * const required = buildRequiredErrors(
7076
+ * ["username", "email"],
7077
+ * (field) => `user.${field}.field_required`
7078
+ * );
7079
+ * // Result: { username: "user.username.field_required", email: "user.email.field_required" }
7080
+ *
7081
+ * // Same message for all fields
7082
+ * const required = buildRequiredErrors(
7083
+ * ["username", "email"],
7084
+ * "This field is required"
7085
+ * );
7086
+ * // Result: { username: "This field is required", email: "This field is required" }
7087
+ *
7088
+ * // With keyPrefix for i18n
7089
+ * const required = buildRequiredErrors(
7090
+ * ["username", "email"],
7091
+ * (field) => `${field}.field_required`,
7092
+ * "user"
7093
+ * );
7094
+ * // Result: { username: "user.username.field_required", email: "user.email.field_required" }
7095
+ * ```
7096
+ */
7097
+ const buildRequiredErrors = (fields, messageOrGenerator, keyPrefix = '') => {
7098
+ const result = {};
7099
+ fields.forEach((field) => {
7100
+ if (typeof messageOrGenerator === 'function') {
7101
+ const message = messageOrGenerator(field);
7102
+ result[field] = keyPrefix ? `${keyPrefix}.${message}` : message;
7103
+ }
7104
+ else {
7105
+ result[field] = messageOrGenerator;
7106
+ }
7107
+ });
7108
+ return result;
7109
+ };
7110
+ /**
7111
+ * Helper function to build field-specific validation errors
7112
+ *
7113
+ * Creates property-specific error messages for multiple fields at once.
7114
+ *
7115
+ * @param config - Maps field names to their validation error configurations
7116
+ * @returns Properties error configuration
7117
+ *
7118
+ * @example
7119
+ * ```typescript
7120
+ * const properties = buildFieldErrors({
7121
+ * username: {
7122
+ * minLength: "Username must be at least 3 characters",
7123
+ * pattern: "Username can only contain letters and numbers"
7124
+ * },
7125
+ * age: {
7126
+ * minimum: "Must be 18 or older",
7127
+ * maximum: "Must be under 120"
7128
+ * },
7129
+ * email: {
7130
+ * format: "Please enter a valid email address"
7131
+ * }
7132
+ * });
7133
+ * ```
7134
+ */
7135
+ const buildFieldErrors = (config) => {
7136
+ return config;
7137
+ };
7138
+ /**
7139
+ * Helper function to create a complete error message configuration in one call
7140
+ *
7141
+ * Convenient wrapper that combines required and validation errors.
7142
+ *
7143
+ * @param required - Required field error messages
7144
+ * @param properties - Field-specific validation error messages
7145
+ * @param globalFallbacks - Global fallback error messages
7146
+ * @returns Complete error message configuration
7147
+ *
7148
+ * @example
7149
+ * ```typescript
7150
+ * const errorMessage = createErrorMessage(
7151
+ * {
7152
+ * username: "Username is required",
7153
+ * email: "Email is required"
7154
+ * },
7155
+ * {
7156
+ * username: {
7157
+ * minLength: "Username must be at least 3 characters"
7158
+ * },
7159
+ * email: {
7160
+ * format: "Please enter a valid email"
7161
+ * }
7162
+ * },
7163
+ * {
7164
+ * minLength: "This field is too short",
7165
+ * format: "Invalid format"
7166
+ * }
7167
+ * );
7168
+ * ```
7169
+ */
7170
+ const createErrorMessage = (required, properties, globalFallbacks) => {
7171
+ const config = {
7172
+ required,
7173
+ properties,
7174
+ };
7175
+ if (globalFallbacks) {
7176
+ Object.assign(config, globalFallbacks);
7177
+ }
7178
+ return buildErrorMessages(config);
7179
+ };
7180
+
5117
7181
  const getMultiDates = ({ selected, selectedDate, selectedDates, selectable, }) => {
5118
7182
  if (!selectable) {
5119
7183
  return [...selectedDates];
@@ -5130,4 +7194,4 @@ const getMultiDates = ({ selected, selectedDate, selectedDates, selectable, }) =
5130
7194
  }
5131
7195
  };
5132
7196
 
5133
- export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DensityToggleButton, EditSortingButton, EmptyState$1 as EmptyState, ErrorAlert, FilterDialog, FilterOptions, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
7197
+ export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState$1 as EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };