@visns-studio/visns-components 6.6.1 → 6.6.3

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.
package/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "react-dom": "^17.0.0 || ^18.0.0"
92
92
  },
93
93
  "name": "@visns-studio/visns-components",
94
- "version": "6.6.1",
94
+ "version": "6.6.3",
95
95
  "description": "Various packages to assist in the development of our Custom Applications.",
96
96
  "main": "src/index.js",
97
97
  "files": [
@@ -711,7 +711,9 @@ const DataGrid = forwardRef(
711
711
  }
712
712
 
713
713
  if (j - i > 1) {
714
- const children = rows.slice(i, j);
714
+ const children = rows
715
+ .slice(i, j)
716
+ .map((child) => ({ ...child }));
715
717
  const parent = {
716
718
  __treeParent: true,
717
719
  id: `tree-group-${value}`,
@@ -2511,6 +2513,14 @@ const DataGrid = forwardRef(
2511
2513
  timestamp: new Date().toISOString(),
2512
2514
  });
2513
2515
 
2516
+ // Synthetic tree parent rows are group summaries with a
2517
+ // fabricated string id — they must never open the update modal
2518
+ // or follow links. Their own controls (chevron, group action
2519
+ // icons) stopPropagation before this handler fires.
2520
+ if (rowProps.data?.__treeParent) {
2521
+ return;
2522
+ }
2523
+
2514
2524
  const cellElement = event.target.closest(
2515
2525
  '.InovuaReactDataGrid__cell'
2516
2526
  );
@@ -4648,6 +4658,7 @@ const DataGrid = forwardRef(
4648
4658
  newColumns.push({
4649
4659
  name: 'setting',
4650
4660
  header: 'Action',
4661
+ sortable: false,
4651
4662
  defaultWidth: actionColumnWidth,
4652
4663
  minWidth: actionColumnWidth,
4653
4664
  textAlign: 'center',
@@ -1310,6 +1310,26 @@ function Form({
1310
1310
  }
1311
1311
  }
1312
1312
 
1313
+ // A form that never fetched its record (see the columnId > 0
1314
+ // gate on the record GET) has no primary key — submitting
1315
+ // would PUT/DELETE against `${url}/undefined`. Abort loudly
1316
+ // instead.
1317
+ if (
1318
+ ['update', 'delete'].includes(formType) &&
1319
+ !bulkEditMode &&
1320
+ !formSettings.json &&
1321
+ !formSettings.customUrl
1322
+ ) {
1323
+ const pk = formData?.[formSettings.primaryKey];
1324
+
1325
+ if (pk == null || pk === '' || Number.isNaN(Number(pk))) {
1326
+ toast.error(
1327
+ 'This record could not be loaded, so it cannot be saved. Please close the form and reopen it from its row.'
1328
+ );
1329
+ return;
1330
+ }
1331
+ }
1332
+
1313
1333
  let fields = formSettings.fields;
1314
1334
  let _inputClass = inputClass;
1315
1335
  let validation = '';
@@ -2199,32 +2219,67 @@ function Form({
2199
2219
  fetchTable(createdItem);
2200
2220
  }
2201
2221
  } else {
2202
- const saveAnotherUrl = `${
2203
- saveAnother.url
2204
- }/${formData[saveAnother.dataKey]}`;
2205
- const resSaveAnother =
2206
- await CustomFetch(
2207
- saveAnotherUrl,
2208
- saveAnother.method,
2209
- processedData
2222
+ // Without a record id the follow-up
2223
+ // would hit `${url}/undefined`, so
2224
+ // skip the request and report it
2225
+ // the same way a failed one is
2226
+ const saveAnotherId =
2227
+ formData?.[saveAnother.dataKey];
2228
+
2229
+ if (
2230
+ saveAnotherId == null ||
2231
+ saveAnotherId === '' ||
2232
+ Number.isNaN(
2233
+ Number(saveAnotherId)
2234
+ )
2235
+ ) {
2236
+ saveAnotherFailed = true;
2237
+ toast.error(
2238
+ 'Unable to run the follow-up action: the record id is missing.'
2210
2239
  );
2211
2240
 
2212
- if (resSaveAnother.data.error === '') {
2213
- toast.success(saveAnother.message);
2214
2241
  if (fetchTable) {
2215
2242
  const createdItem =
2216
- res.data.data || res.data;
2243
+ res.data.data ||
2244
+ res.data;
2217
2245
  fetchTable(createdItem);
2218
2246
  }
2219
2247
  } else {
2220
- saveAnotherFailed = true;
2221
- toast.error(
2222
- resSaveAnother.data.error
2223
- );
2224
- if (fetchTable) {
2225
- const createdItem =
2226
- res.data.data || res.data;
2227
- fetchTable(createdItem);
2248
+ const saveAnotherUrl = `${
2249
+ saveAnother.url
2250
+ }/${saveAnotherId}`;
2251
+ const resSaveAnother =
2252
+ await CustomFetch(
2253
+ saveAnotherUrl,
2254
+ saveAnother.method,
2255
+ processedData
2256
+ );
2257
+
2258
+ if (
2259
+ resSaveAnother.data
2260
+ .error === ''
2261
+ ) {
2262
+ toast.success(
2263
+ saveAnother.message
2264
+ );
2265
+ if (fetchTable) {
2266
+ const createdItem =
2267
+ res.data.data ||
2268
+ res.data;
2269
+ fetchTable(createdItem);
2270
+ }
2271
+ } else {
2272
+ saveAnotherFailed = true;
2273
+ toast.error(
2274
+ resSaveAnother.data
2275
+ .error
2276
+ );
2277
+ if (fetchTable) {
2278
+ const createdItem =
2279
+ res.data.data ||
2280
+ res.data;
2281
+ fetchTable(createdItem);
2282
+ }
2228
2283
  }
2229
2284
  }
2230
2285
  }
@@ -36,7 +36,7 @@ const CustomOption = (props) => {
36
36
 
37
37
  // Custom MultiValue component to display description for selected values
38
38
  const CustomMultiValue = (props) => {
39
- const { children, data, removeProps, settings, ...rest } = props;
39
+ const { children, data, settings, ...rest } = props;
40
40
 
41
41
  return (
42
42
  <components.MultiValue {...rest} data={data}>
@@ -62,61 +62,18 @@ const CustomMultiValue = (props) => {
62
62
  );
63
63
  };
64
64
 
65
- // Custom MultiValueRemove component with proper removal handling
65
+ // Custom MultiValueRemove: fixed values cannot be deselected — render no
66
+ // × at all (the module SCSS forces `display` on the remove button with
67
+ // !important, so hiding it from the styles function is not enough).
68
+ // Removal itself is react-select's own behaviour: CustomMultiValue
69
+ // forwards removeProps, so innerProps already carries working
70
+ // onClick/onTouchEnd handlers.
66
71
  const CustomMultiValueRemove = (props) => {
67
- // Fixed values cannot be deselected: render no × at all — the module
68
- // SCSS forces `display` on the remove button with !important, so
69
- // hiding it from the styles function is not enough.
70
72
  if (props.data?.isFixed) {
71
73
  return null;
72
74
  }
73
75
 
74
- const handleRemove = (e) => {
75
- e.stopPropagation();
76
-
77
- // Try to find the data by looking at the DOM structure
78
- const removeButton = e.currentTarget;
79
- const multiValueContainer = removeButton.closest('.visns-select__multi-value');
80
-
81
- if (multiValueContainer) {
82
- // Get the index of this multi-value item
83
- const allMultiValues = removeButton.closest('.visns-select__value-container').querySelectorAll('.visns-select__multi-value');
84
- const index = Array.from(allMultiValues).indexOf(multiValueContainer);
85
-
86
- console.log('MultiSelect: Found index:', index);
87
-
88
- // Get current value and remove the item at this index
89
- const currentValue = props.selectProps?.value || [];
90
- console.log('MultiSelect: Current value:', currentValue);
91
-
92
- if (index >= 0 && index < currentValue.length) {
93
- const itemToRemove = currentValue[index];
94
- const newValue = currentValue.filter((_, i) => i !== index);
95
-
96
- console.log('MultiSelect: Removing item at index', index, ':', itemToRemove);
97
- console.log('MultiSelect: New value:', newValue);
98
-
99
- // Call onChange with the filtered value and remove action
100
- if (props.selectProps?.onChange) {
101
- props.selectProps.onChange(newValue, {
102
- action: 'remove-value',
103
- removedValue: itemToRemove
104
- });
105
- }
106
- }
107
- }
108
- };
109
-
110
- return (
111
- <components.MultiValueRemove
112
- {...props}
113
- innerProps={{
114
- ...props.innerProps,
115
- onClick: handleRemove,
116
- onMouseDown: (e) => e.stopPropagation()
117
- }}
118
- />
119
- );
76
+ return <components.MultiValueRemove {...props} />;
120
77
  };
121
78
 
122
79
  // Custom NoOptionsMessage component to guide users about creation capability
@@ -333,15 +290,6 @@ function MultiSelect({
333
290
  menuPlacement={menuPlacement}
334
291
  onMenuOpen={handleMenuOpen}
335
292
  onChange={(inputValue, action) => {
336
- // Debug logging for delete actions
337
- if (action.action === 'remove-value') {
338
- console.log('MultiSelect: Individual item delete:', {
339
- removedValue: action.removedValue,
340
- newValue: inputValue,
341
- action: action
342
- });
343
- }
344
-
345
293
  // Values seeded with isFixed represent memberships this
346
294
  // field is not allowed to take away (e.g. a tag's existing
347
295
  // headers in an add-only group modal): ignore their removal