@controleonline/ui-default 1.0.270 → 1.0.273

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 (100) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/.github/workflows/publish-npm.yml +91 -0
  4. package/AGENTS.md +46 -50
  5. package/docs/technical/Contrato-de-colunas.md +67 -0
  6. package/docs/technical/Default-Components.md +76 -0
  7. package/docs/technical/DefaultForm.md +80 -0
  8. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  9. package/docs/technical/DefaultTable.md +272 -0
  10. package/docs/wiki.md +1 -0
  11. package/package.json +4 -3
  12. package/scripts/validate-wiki-pointer.cjs +13 -0
  13. package/src/react/components/address/DefaultAddress.js +446 -0
  14. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  15. package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
  16. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  17. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  18. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  19. package/src/react/components/address/defaultAddressStyles.js +148 -0
  20. package/src/react/components/address/defaultAddressUtils.js +129 -0
  21. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  22. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  23. package/src/react/components/files/DefaultFile.js +231 -46
  24. package/src/react/components/files/DefaultFileColumn.js +123 -0
  25. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  26. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  27. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  28. package/src/react/components/filters/DefaultColumnFilter.js +60 -48
  29. package/src/react/components/filters/DefaultExternalFilters.js +324 -265
  30. package/src/react/components/filters/filterValue.js +65 -0
  31. package/src/react/components/form/DefaultForm.js +99 -79
  32. package/src/react/components/help/DefaultTooltip.js +48 -12
  33. package/src/react/components/help/DefaultTooltip.styles.js +20 -0
  34. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  35. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  36. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  37. package/src/react/components/inputs/DefaultInput.js +296 -141
  38. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  39. package/src/react/components/inputs/DefaultSelect.js +134 -123
  40. package/src/react/components/inputs/defaultInputUtils.js +262 -213
  41. package/src/react/components/map/DefaultMap.native.js +73 -73
  42. package/src/react/components/map/DefaultMap.shared.js +911 -762
  43. package/src/react/components/map/DefaultMap.web.js +124 -68
  44. package/src/react/components/table/DefaultColumnMenu.js +22 -3
  45. package/src/react/components/table/DefaultDebug.js +44 -37
  46. package/src/react/components/table/DefaultSearchModal.js +9 -1
  47. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  48. package/src/react/components/table/DefaultTable.js +217 -111
  49. package/src/react/components/table/DefaultTable.styles.js +369 -310
  50. package/src/react/components/table/DefaultTable.utils.js +38 -4
  51. package/src/react/components/table/DefaultTableBody.js +13 -8
  52. package/src/react/components/table/DefaultTableCards.js +20 -1
  53. package/src/react/components/table/DefaultTableControls.js +180 -205
  54. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  55. package/src/react/components/table/DefaultTableInput.js +1 -0
  56. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  57. package/src/react/components/table/DefaultTableRows.js +357 -325
  58. package/src/react/components/table/DefaultTableSearch.js +28 -5
  59. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  60. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  61. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  62. package/src/react/components/upload/DefaultUpload.js +406 -0
  63. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  64. package/src/react/components/upload/DefaultUploadAttachmentsList.js +146 -0
  65. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  66. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  67. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  68. package/src/react/components/upload/fileUpload.js +93 -0
  69. package/src/react/hooks/usePostalCodeLookup.js +159 -0
  70. package/src/react/index.js +6 -0
  71. package/src/react/services/addressFormUtils.js +262 -0
  72. package/src/react/services/addressGeo.js +230 -0
  73. package/src/react/utils/tableVisibleColumnsPreferences.js +113 -15
  74. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  75. package/src/tests/react/components/files/DefaultFile.test.js +22 -6
  76. package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
  77. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  78. package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
  79. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  80. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  81. package/src/tests/react/components/table/DefaultTable.test.js +53 -2
  82. package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
  83. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  84. package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
  85. package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
  86. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  87. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  88. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  89. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +104 -0
  90. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  91. package/src/tests/react/filters/filterValue.test.js +46 -0
  92. package/src/tests/react/hooks/usePostalCodeLookup.test.js +107 -0
  93. package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
  94. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  95. package/src/tests/react/services/addressFormUtils.test.js +179 -0
  96. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  97. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  98. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  99. package/src/tests/react/services/smoke-637/index.html +132 -0
  100. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +96 -49
@@ -1,68 +1,124 @@
1
- import React, {useMemo} from 'react';
2
- import {View} from 'react-native';
3
-
4
- import DefaultGoogleMap from './DefaultGoogleMap.web';
5
-
6
- import styles from './DefaultMap.styles';
7
- import {
8
- buildOpenStreetMapHtml,
9
- resolveDefaultMapPayload,
10
- } from './DefaultMap.shared';
11
-
12
- export default function DefaultMap({
13
- config = null,
14
- addresses = null,
15
- markerPayloads = [],
16
- paths = [],
17
- userCoordinates = null,
18
- apiKey = '',
19
- popupTheme = null,
20
- }) {
21
- const resolvedPayload = useMemo(
22
- () =>
23
- resolveDefaultMapPayload({
24
- config,
25
- addresses,
26
- markerPayloads,
27
- paths,
28
- userCoordinates,
29
- apiKey,
30
- }),
31
- [apiKey, addresses, config, markerPayloads, paths, userCoordinates],
32
- );
33
-
34
- if (
35
- resolvedPayload.markerPayloads.length === 0 &&
36
- !resolvedPayload.userCoordinates &&
37
- resolvedPayload.paths.length === 0
38
- ) {
39
- return null;
40
- }
41
-
42
- if (resolvedPayload.apiKey) {
43
- return (
44
- <DefaultGoogleMap
45
- apiKey={resolvedPayload.apiKey}
46
- markerPayloads={resolvedPayload.markerPayloads}
47
- userCoordinates={resolvedPayload.userCoordinates}
48
- paths={resolvedPayload.paths}
49
- popupTheme={popupTheme}
50
- />
51
- );
52
- }
53
-
54
- return (
55
- <View style={styles.mapContainer}>
56
- <iframe
57
- title="Mapa"
58
- srcDoc={buildOpenStreetMapHtml({
59
- markerPayloads: resolvedPayload.markerPayloads,
60
- paths: resolvedPayload.paths,
61
- userCoordinates: resolvedPayload.userCoordinates,
62
- routeColor: '#0EA5E9',
63
- })}
64
- style={{width: '100%', height: '100%', border: 0}}
65
- />
66
- </View>
67
- );
68
- }
1
+ import React, {useEffect, useMemo, useRef} from 'react';
2
+ import {View} from 'react-native';
3
+
4
+ import DefaultGoogleMap from './DefaultGoogleMap.web';
5
+
6
+ import styles from './DefaultMap.styles';
7
+ import {
8
+ buildOpenStreetMapHtml,
9
+ resolveDefaultMapPayload,
10
+ } from './DefaultMap.shared';
11
+
12
+ export default function DefaultMap({
13
+ config = null,
14
+ addresses = null,
15
+ markerPayloads = [],
16
+ paths = [],
17
+ userCoordinates = null,
18
+ apiKey = '',
19
+ popupTheme = null,
20
+ }) {
21
+ const iframeRef = useRef(null);
22
+ const resolvedPayload = useMemo(
23
+ () =>
24
+ resolveDefaultMapPayload({
25
+ config,
26
+ addresses,
27
+ markerPayloads,
28
+ paths,
29
+ userCoordinates,
30
+ apiKey,
31
+ }),
32
+ [apiKey, addresses, config, markerPayloads, paths, userCoordinates],
33
+ );
34
+
35
+ const mapKey = useMemo(() => {
36
+ const markers = resolvedPayload.markerPayloads || [];
37
+ const first = markers[0] || {};
38
+ const user = resolvedPayload.userCoordinates || {};
39
+ return [
40
+ first.latitude,
41
+ first.longitude,
42
+ first.geocodeQuery || first.addressLine || '',
43
+ user.latitude,
44
+ user.longitude,
45
+ markers.length,
46
+ (resolvedPayload.paths || []).length,
47
+ ].join('|');
48
+ }, [resolvedPayload]);
49
+
50
+ useEffect(() => {
51
+ const el = iframeRef.current;
52
+ if (!el) {
53
+ return undefined;
54
+ }
55
+
56
+ const bump = () => {
57
+ try {
58
+ const win = el.contentWindow;
59
+ if (win && typeof win.dispatchEvent === 'function') {
60
+ win.dispatchEvent(new Event('resize'));
61
+ }
62
+ } catch (e) {
63
+ // empty srcDoc during mount
64
+ }
65
+ };
66
+
67
+ const onLoad = () => {
68
+ bump();
69
+ setTimeout(bump, 50);
70
+ setTimeout(bump, 250);
71
+ setTimeout(bump, 800);
72
+ };
73
+
74
+ el.addEventListener('load', onLoad);
75
+ return () => el.removeEventListener('load', onLoad);
76
+ }, [mapKey]);
77
+
78
+ if (
79
+ resolvedPayload.markerPayloads.length === 0 &&
80
+ !resolvedPayload.userCoordinates &&
81
+ resolvedPayload.paths.length === 0
82
+ ) {
83
+ return null;
84
+ }
85
+
86
+ if (resolvedPayload.apiKey) {
87
+ return (
88
+ <DefaultGoogleMap
89
+ apiKey={resolvedPayload.apiKey}
90
+ markerPayloads={resolvedPayload.markerPayloads}
91
+ userCoordinates={resolvedPayload.userCoordinates}
92
+ paths={resolvedPayload.paths}
93
+ popupTheme={popupTheme}
94
+ />
95
+ );
96
+ }
97
+
98
+ const html = buildOpenStreetMapHtml({
99
+ markerPayloads: resolvedPayload.markerPayloads,
100
+ paths: resolvedPayload.paths,
101
+ userCoordinates: resolvedPayload.userCoordinates,
102
+ routeColor: '#0EA5E9',
103
+ });
104
+
105
+ return (
106
+ <View style={[styles.mapContainer, {height: '100%', minHeight: 220, width: '100%', flex: 1}]}>
107
+ <iframe
108
+ ref={iframeRef}
109
+ key={mapKey}
110
+ title="Mapa"
111
+ srcDoc={html}
112
+ style={{
113
+ width: '100%',
114
+ height: '100%',
115
+ minHeight: 220,
116
+ border: 0,
117
+ display: 'block',
118
+ }}
119
+ loading="eager"
120
+ referrerPolicy="no-referrer-when-downgrade"
121
+ />
122
+ </View>
123
+ );
124
+ }
@@ -5,6 +5,8 @@ import { useStore } from '@store';
5
5
  import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
6
  import { getColumnKey } from '../inputs/defaultInputUtils';
7
7
  import {
8
+ canHideVisibleColumn,
9
+ isRequiredVisibleColumn,
8
10
  persistVisibleColumnsPreference,
9
11
  resolveDefaultTablePreferenceScope,
10
12
  sanitizeVisibleColumnsPreference,
@@ -26,6 +28,7 @@ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
26
28
  checkboxBorderColor: resolvedCheckboxBorderColor,
27
29
  checkboxSelectedMarkColor: resolvedCheckboxSelectedMarkColor,
28
30
  modalColors,
31
+ themeColors,
29
32
  } = useDefaultTableTheme();
30
33
  if (!visible) return null;
31
34
 
@@ -51,7 +54,7 @@ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
51
54
  activeOpacity={0.82}
52
55
  onPress={onClose}
53
56
  >
54
- <Icon name="x" size={16} color={closeIconColor} />
57
+ <Icon name="x" size={16} color={themeColors.iconWarning || closeIconColor} />
55
58
  </TouchableOpacity>
56
59
  </View>
57
60
  <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
@@ -64,7 +67,20 @@ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
64
67
  storeName,
65
68
  });
66
69
  const checked = visibleColumns[fieldName] !== false;
70
+ const required = isRequiredVisibleColumn(column);
71
+ const locked =
72
+ required ||
73
+ (checked &&
74
+ !canHideVisibleColumn({
75
+ columns,
76
+ fieldName,
77
+ visibleColumns,
78
+ }));
67
79
  const toggleColumn = () => {
80
+ if (locked && checked) {
81
+ return;
82
+ }
83
+
68
84
  const nextVisibleColumns = sanitizeVisibleColumnsPreference({
69
85
  columns,
70
86
  visibleColumns: {
@@ -86,7 +102,8 @@ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
86
102
  <TouchableOpacity
87
103
  key={fieldName}
88
104
  style={styles.columnMenuItem}
89
- activeOpacity={0.82}
105
+ activeOpacity={locked ? 1 : 0.82}
106
+ disabled={locked && checked}
90
107
  onPress={toggleColumn}
91
108
  >
92
109
  <Icon
@@ -94,7 +111,9 @@ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
94
111
  size={16}
95
112
  color={checked ? resolvedCheckboxSelectedMarkColor : resolvedCheckboxBorderColor}
96
113
  />
97
- <Text style={[styles.columnMenuText, { color: textColor }]} numberOfLines={1}>{label}</Text>
114
+ <Text style={[styles.columnMenuText, { color: textColor }]} numberOfLines={1}>
115
+ {required ? `${label} *` : label}
116
+ </Text>
98
117
  </TouchableOpacity>
99
118
  );
100
119
  })}
@@ -82,11 +82,12 @@ const DefaultDebug = ({
82
82
  const debugParameters = isObject(resolvedDebug?.parameters)
83
83
  ? resolvedDebug.parameters
84
84
  : configs.debugFallbackParameters || null;
85
- const debugParametersText = useMemo(
86
- () => formatDebugValue(debugParameters),
87
- [debugParameters],
88
- );
89
- const debugCopyText = useMemo(
85
+ const debugParametersText = useMemo(
86
+ () => formatDebugValue(debugParameters),
87
+ [debugParameters],
88
+ );
89
+ const hasDebugParameters = debugParametersText !== '';
90
+ const debugCopyText = useMemo(
90
91
  () =>
91
92
  [
92
93
  'Query:',
@@ -118,9 +119,9 @@ const DefaultDebug = ({
118
119
  [showError, showSuccess],
119
120
  );
120
121
 
121
- if (!hasDebugQuery) {
122
- return null;
123
- }
122
+ if (!hasDebugQuery && !hasDebugParameters && !debugFilledQuery) {
123
+ return null;
124
+ }
124
125
 
125
126
  const {
126
127
  backgroundColor,
@@ -176,35 +177,41 @@ const DefaultDebug = ({
176
177
  </View>
177
178
 
178
179
  <ScrollView style={styles.debugModalBody} contentContainerStyle={styles.debugModalContent}>
179
- <DebugSection
180
- borderColor={borderColor}
181
- copyText={debugQuery}
182
- onCopy={requestCopyText}
183
- textColor={textColor}
184
- title="Query"
185
- >
186
- {debugQuery}
187
- </DebugSection>
188
-
189
- <DebugSection
190
- borderColor={borderColor}
191
- copyText={debugParametersText}
192
- onCopy={requestCopyText}
193
- textColor={textColor}
194
- title="Parametros"
195
- >
196
- {debugParametersText}
197
- </DebugSection>
198
-
199
- <DebugSection
200
- borderColor={borderColor}
201
- copyText={debugFilledQuery}
202
- onCopy={requestCopyText}
203
- textColor={textColor}
204
- title="Query preenchida"
205
- >
206
- {debugFilledQuery}
207
- </DebugSection>
180
+ {hasDebugQuery ? (
181
+ <DebugSection
182
+ borderColor={borderColor}
183
+ copyText={debugQuery}
184
+ onCopy={requestCopyText}
185
+ textColor={textColor}
186
+ title="Query"
187
+ >
188
+ {debugQuery}
189
+ </DebugSection>
190
+ ) : null}
191
+
192
+ {hasDebugParameters ? (
193
+ <DebugSection
194
+ borderColor={borderColor}
195
+ copyText={debugParametersText}
196
+ onCopy={requestCopyText}
197
+ textColor={textColor}
198
+ title="Parametros"
199
+ >
200
+ {debugParametersText}
201
+ </DebugSection>
202
+ ) : null}
203
+
204
+ {debugFilledQuery ? (
205
+ <DebugSection
206
+ borderColor={borderColor}
207
+ copyText={debugFilledQuery}
208
+ onCopy={requestCopyText}
209
+ textColor={textColor}
210
+ title="Query preenchida"
211
+ >
212
+ {debugFilledQuery}
213
+ </DebugSection>
214
+ ) : null}
208
215
  </ScrollView>
209
216
 
210
217
  <View style={[styles.modalActions, { borderTopColor: borderColor }]}>
@@ -5,7 +5,13 @@ import DefaultSearch from '../filters/DefaultSearch';
5
5
  import styles from './DefaultTable.styles';
6
6
  import useDefaultTableTheme from './useDefaultTableTheme';
7
7
 
8
- const DefaultSearchModal = ({ storeName, visible = false, onClose }) => {
8
+ const DefaultSearchModal = ({
9
+ searchKey = 'search',
10
+ searchPlaceholder = '',
11
+ storeName,
12
+ visible = false,
13
+ onClose,
14
+ }) => {
9
15
  const { modalColors } = useDefaultTableTheme();
10
16
  if (!visible) return null;
11
17
 
@@ -36,6 +42,8 @@ const DefaultSearchModal = ({ storeName, visible = false, onClose }) => {
36
42
  <View style={styles.searchModalBody}>
37
43
  <DefaultSearch
38
44
  autoFocus
45
+ placeholder={searchPlaceholder}
46
+ searchKey={searchKey}
39
47
  storeName={storeName}
40
48
  onSearch={onClose}
41
49
  />
@@ -0,0 +1,98 @@
1
+ import { getColumnKey } from '../inputs/defaultInputUtils';
2
+ import { getRowKey } from './DefaultTable.utils';
3
+
4
+ export const isGroupingColumn = column => column?.grouping === true;
5
+
6
+ export const getGroupingColumns = columns =>
7
+ (Array.isArray(columns) ? columns : []).filter(isGroupingColumn);
8
+
9
+ export const getRowFieldValue = (row, column) => {
10
+ const fieldName = typeof column === 'string' ? column : getColumnKey(column);
11
+ if (!fieldName) return '';
12
+ const value = row?.[fieldName];
13
+ if (value && typeof value === 'object') {
14
+ return String(value.label ?? value.name ?? value.alias ?? value.id ?? '').trim();
15
+ }
16
+ return String(value ?? '').trim();
17
+ };
18
+
19
+ export const buildGroupKey = (row, groupingColumns) =>
20
+ getGroupingColumns(groupingColumns)
21
+ .map(column => getRowFieldValue(row, column) || 'none')
22
+ .join(':');
23
+
24
+ export const buildGroupLabel = (row, groupingColumns) =>
25
+ getGroupingColumns(groupingColumns)
26
+ .map(column => getRowFieldValue(row, column) || column?.label || getColumnKey(column))
27
+ .filter(Boolean)
28
+ .join(' · ');
29
+
30
+ export const groupTableRows = (rows, columns) => {
31
+ const groupingColumns = getGroupingColumns(columns);
32
+ const items = Array.isArray(rows) ? rows : [];
33
+ if (!groupingColumns.length) {
34
+ return [{ id: 'all', key: 'all', label: '', rows: items, count: items.length }];
35
+ }
36
+ const groups = new Map();
37
+ items.forEach(row => {
38
+ const key = buildGroupKey(row, groupingColumns);
39
+ if (!groups.has(key)) {
40
+ groups.set(key, { id: key, key, label: buildGroupLabel(row, groupingColumns), rows: [], count: 0 });
41
+ }
42
+ const group = groups.get(key);
43
+ group.rows.push(row);
44
+ group.count += 1;
45
+ });
46
+ return Array.from(groups.values());
47
+ };
48
+
49
+ export const flattenGroupedTableItems = (rows, columns) => {
50
+ const groupingColumns = getGroupingColumns(columns);
51
+ const items = Array.isArray(rows) ? rows : [];
52
+ if (!groupingColumns.length) {
53
+ return items.map((row, index) => ({ type: 'row', id: `row:${getRowKey(row, index)}`, row }));
54
+ }
55
+ return groupTableRows(items, groupingColumns).flatMap(group => [
56
+ { type: 'group', id: `group:${group.id}`, group },
57
+ ...group.rows.map((row, index) => ({ type: 'row', id: `row:${group.id}:${getRowKey(row, index)}`, group, row })),
58
+ ]);
59
+ };
60
+
61
+ export const normalizeSelectedIds = selectedIds =>
62
+ Array.from(new Set((Array.isArray(selectedIds) ? selectedIds : []).map(id => String(id))));
63
+
64
+ export const resolveStoreSelectedIds = store =>
65
+ normalizeSelectedIds(store?.getters?.selected);
66
+
67
+ export const isRowSelected = (row, selectedIds, index = 0) =>
68
+ normalizeSelectedIds(selectedIds).includes(String(getRowKey(row, index)));
69
+
70
+ export const toggleSelectedId = (selectedIds, row, index = 0) => {
71
+ const current = normalizeSelectedIds(selectedIds);
72
+ const id = String(getRowKey(row, index));
73
+ return current.includes(id) ? current.filter(item => item !== id) : [...current, id];
74
+ };
75
+
76
+ export const toggleGroupSelection = (selectedIds, groupRows) => {
77
+ const current = normalizeSelectedIds(selectedIds);
78
+ const ids = (Array.isArray(groupRows) ? groupRows : []).map(row => String(getRowKey(row)));
79
+ const allSelected = ids.length > 0 && ids.every(id => current.includes(id));
80
+ if (allSelected) return current.filter(id => !ids.includes(id));
81
+ return normalizeSelectedIds([...current, ...ids]);
82
+ };
83
+
84
+ export const persistStoreSelection = (store, nextIds, rows, columns) => {
85
+ const selected = normalizeSelectedIds(nextIds);
86
+ store?.actions?.setSelected?.(selected);
87
+ const items = (Array.isArray(rows) ? rows : []).filter(row => selected.includes(String(getRowKey(row))));
88
+ const groups = groupTableRows(items, columns).filter(group => group.rows.length > 0);
89
+ const totalValue = items.reduce((sum, row) => {
90
+ const amount = Number(row?.invoiceTotal ?? row?.total ?? row?.value ?? 0);
91
+ return sum + (Number.isFinite(amount) ? amount : 0);
92
+ }, 0);
93
+ store?.actions?.setSummary?.({
94
+ count: { invoices: items.length, groups: groups.length },
95
+ sum: { invoiceTotal: Number(totalValue.toFixed(2)) },
96
+ });
97
+ return selected;
98
+ };