@visns-studio/visns-components 6.6.4 → 6.15.1
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/README.md +1623 -18
- package/package.json +10 -4
- package/src/components/DataGrid.jsx +181 -38
- package/src/components/Fetch.jsx +80 -3
- package/src/components/Form.jsx +9 -0
- package/src/components/Navigation.jsx +109 -50
- package/src/components/Notification.jsx +279 -9
- package/src/components/TableFilter.jsx +14 -1
- package/src/components/auth/AuthBrandPanel.jsx +43 -0
- package/src/components/auth/AuthLoading.jsx +78 -0
- package/src/components/auth/AuthShell.jsx +59 -0
- package/src/components/auth/ClientAuth.jsx +161 -0
- package/src/components/auth/ClientAuthFrame.jsx +59 -0
- package/src/components/auth/ClientLogin.jsx +266 -55
- package/src/components/auth/ClientOTPVerify.jsx +587 -115
- package/src/components/auth/ImpersonateGate.jsx +254 -0
- package/src/components/auth/Login.jsx +134 -41
- package/src/components/auth/LogoutScreen.jsx +112 -0
- package/src/components/auth/Reset.jsx +134 -77
- package/src/components/auth/TwoFactorAuth.jsx +475 -297
- package/src/components/auth/Verify.jsx +237 -126
- package/src/components/auth/authEndpoints.js +105 -0
- package/src/components/auth/authFont.js +23 -0
- package/src/components/auth/authHelpers.js +465 -0
- package/src/components/auth/clientAuthProtocols.js +240 -0
- package/src/components/auth/useOptionalRouter.js +49 -0
- package/src/components/callQueue/CallQueuePop.jsx +1502 -0
- package/src/components/callQueue/CallQueueSettings.jsx +508 -0
- package/src/components/callQueue/callQueueHelpers.js +280 -0
- package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
- package/src/components/columns/ColumnRenderers.jsx +53 -7
- package/src/components/generic/GenericAuth.jsx +163 -96
- package/src/components/generic/GenericDetail.jsx +215 -90
- package/src/components/generic/GenericIndex.jsx +20 -47
- package/src/components/generic/GenericMain.jsx +5 -0
- package/src/components/generic/GroupedReportRenderer.jsx +1 -5
- package/src/components/generic/StandardModal.jsx +17 -5
- package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
- package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
- package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
- package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
- package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
- package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
- package/src/components/navActive.js +60 -0
- package/src/components/notify/desktopNotifications.js +256 -0
- package/src/components/sketch/SketchField.jsx +12 -2
- package/src/components/sms/SmsComposeModal.jsx +495 -0
- package/src/components/sms/SmsInbox.jsx +596 -0
- package/src/components/sms/SmsInboxBadge.jsx +468 -0
- package/src/components/sms/SmsLineSettings.jsx +854 -0
- package/src/components/sms/SmsThreadPanel.jsx +1184 -0
- package/src/components/sms/smsEndpoints.js +56 -0
- package/src/components/sms/smsHelpers.js +1075 -0
- package/src/components/sms/smsLiveState.js +132 -0
- package/src/components/sms/useSmsLive.js +307 -0
- package/src/components/styles/CallQueuePop.module.scss +646 -0
- package/src/components/styles/CallQueueSettings.module.scss +460 -0
- package/src/components/styles/ClientAuth.module.scss +229 -0
- package/src/components/styles/GenericDetail.module.scss +153 -26
- package/src/components/styles/GenericIndex.module.scss +13 -0
- package/src/components/styles/GenericMain.module.scss +19 -2
- package/src/components/styles/ImpersonateGate.module.scss +85 -0
- package/src/components/styles/Login.module.scss +13 -122
- package/src/components/styles/LogoutScreen.module.scss +89 -0
- package/src/components/styles/Navigation.module.scss +509 -223
- package/src/components/styles/Notification.module.scss +103 -0
- package/src/components/styles/Reset.module.scss +52 -186
- package/src/components/styles/Sms.module.scss +2382 -0
- package/src/components/styles/TableFilter.module.scss +118 -98
- package/src/components/styles/TwoFactorAuth.module.scss +115 -176
- package/src/components/styles/Vault.module.scss +1983 -0
- package/src/components/styles/Verify.module.scss +57 -180
- package/src/components/styles/_authBrandPanel.scss +163 -0
- package/src/components/styles/_authShell.scss +369 -0
- package/src/components/styles/global-datagrid.css +27 -2
- package/src/components/utils/buildEnv.js +67 -0
- package/src/components/utils/displayValue.js +94 -0
- package/src/components/utils/useDensity.js +345 -9
- package/src/components/vault/OtpChip.jsx +208 -0
- package/src/components/vault/PasswordGenerator.jsx +145 -0
- package/src/components/vault/QrScanner.jsx +326 -0
- package/src/components/vault/VaultAccessLog.jsx +225 -0
- package/src/components/vault/VaultConfirmPanel.jsx +113 -0
- package/src/components/vault/VaultEntryForm.jsx +740 -0
- package/src/components/vault/VaultManager.jsx +1000 -0
- package/src/components/vault/VaultQuickSearch.jsx +681 -0
- package/src/components/vault/qrDecode.js +257 -0
- package/src/components/vault/useDebouncedValue.js +22 -0
- package/src/components/vault/useVaultReveal.js +160 -0
- package/src/components/vault/vaultClipboard.js +33 -0
- package/src/components/vault/vaultEndpoints.js +40 -0
- package/src/components/vault/vaultFit.js +116 -0
- package/src/components/vault/vaultHelpers.js +547 -0
- package/src/components/vault/vaultNavigation.jsx +58 -0
- package/src/components/vault/vaultOtp.js +105 -0
- package/src/index.js +233 -1
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"framer-motion": "^12.38.0",
|
|
29
29
|
"html-react-parser": "^5.2.17",
|
|
30
30
|
"html2canvas": "^1.4.1",
|
|
31
|
+
"jsqr": "^1.4.0",
|
|
31
32
|
"lodash": "^4.18.1",
|
|
32
33
|
"lodash.debounce": "^4.0.8",
|
|
33
34
|
"lucide-react": "^0.544.0",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"motion": "^12.38.0",
|
|
37
38
|
"numeral": "^2.0.6",
|
|
38
39
|
"pluralize": "^8.0.0",
|
|
40
|
+
"prop-types": "^15.8.1",
|
|
39
41
|
"qrcode.react": "^4.2.0",
|
|
40
42
|
"quill-image-uploader": "^1.3.0",
|
|
41
43
|
"react-big-calendar": "^1.19.4",
|
|
@@ -81,17 +83,19 @@
|
|
|
81
83
|
"css-loader": "^7.1.4",
|
|
82
84
|
"css-minimizer-webpack-plugin": "^7.0.4",
|
|
83
85
|
"mini-css-extract-plugin": "^2.10.2",
|
|
86
|
+
"pngjs": "^7.0.0",
|
|
87
|
+
"qrcode": "^1.5.4",
|
|
84
88
|
"react": "^18.3.1",
|
|
85
89
|
"react-dom": "^18.3.1",
|
|
86
90
|
"sass": "^1.99.0",
|
|
87
91
|
"sass-loader": "^16.0.7"
|
|
88
92
|
},
|
|
89
93
|
"peerDependencies": {
|
|
90
|
-
"react": "^17.0.0 || ^18.0.0",
|
|
91
|
-
"react-dom": "^17.0.0 || ^18.0.0"
|
|
94
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
95
|
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
92
96
|
},
|
|
93
97
|
"name": "@visns-studio/visns-components",
|
|
94
|
-
"version": "6.
|
|
98
|
+
"version": "6.15.1",
|
|
95
99
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
100
|
"main": "src/index.js",
|
|
97
101
|
"files": [
|
|
@@ -100,7 +104,9 @@
|
|
|
100
104
|
],
|
|
101
105
|
"scripts": {
|
|
102
106
|
"build": "vite build",
|
|
103
|
-
"test": "
|
|
107
|
+
"test": "node --test tests/*.test.mjs",
|
|
108
|
+
"dev:vault": "vite --config dev/vite.config.js",
|
|
109
|
+
"dev:sms": "FIXTURE=sms vite --config dev/vite.config.js"
|
|
104
110
|
},
|
|
105
111
|
"repository": {
|
|
106
112
|
"type": "git",
|
|
@@ -30,6 +30,7 @@ import React, {
|
|
|
30
30
|
useState,
|
|
31
31
|
} from 'react';
|
|
32
32
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
33
|
+
import { buildEnv } from './utils/buildEnv';
|
|
33
34
|
import debounce from 'lodash.debounce';
|
|
34
35
|
import moment from 'moment';
|
|
35
36
|
import 'reactjs-popup/dist/index.css';
|
|
@@ -43,7 +44,11 @@ import NativeDateFilterEditor from './columns/NativeDateFilterEditor';
|
|
|
43
44
|
import { toast } from 'react-toastify';
|
|
44
45
|
import fetchUtil from '../utils/fetchUtil';
|
|
45
46
|
import { confirmDialog } from './utils/ConfirmDialog';
|
|
46
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
useDensity,
|
|
49
|
+
useAvailableHeight,
|
|
50
|
+
rowsForHeight,
|
|
51
|
+
} from './utils/useDensity';
|
|
47
52
|
import { DEFAULT_INTELLIGENT_SORTING_CONFIG } from '../utils/relationshipSortingUtils';
|
|
48
53
|
import {
|
|
49
54
|
extractColumnsMetadata,
|
|
@@ -873,6 +878,22 @@ const DataGrid = forwardRef(
|
|
|
873
878
|
debugLog('[DataGrid] Initial limit set to:', initialLimit);
|
|
874
879
|
return initialLimit;
|
|
875
880
|
});
|
|
881
|
+
|
|
882
|
+
// Set the moment the user picks a size from the pager's "Results per
|
|
883
|
+
// page" control. From then on nothing auto-sizes the page behind their
|
|
884
|
+
// back — not the profile default, not a window resize. Deliberately a
|
|
885
|
+
// ref rather than state: it must not itself trigger a re-render, and
|
|
886
|
+
// the sizing effect reads it without wanting to re-run when it flips.
|
|
887
|
+
const userChoseLimitRef = useRef(false);
|
|
888
|
+
// The current limit, readable from the sizing effect without listing
|
|
889
|
+
// `limit` as a dependency (which would re-run it on every page change).
|
|
890
|
+
const limitRef = useRef(limit);
|
|
891
|
+
limitRef.current = limit;
|
|
892
|
+
|
|
893
|
+
const handleLimitChange = useCallback((next) => {
|
|
894
|
+
userChoseLimitRef.current = true;
|
|
895
|
+
setLimit(next);
|
|
896
|
+
}, []);
|
|
876
897
|
const [search, setSearch] = useState('');
|
|
877
898
|
|
|
878
899
|
// This component instance survives client-side route changes — React Router
|
|
@@ -907,8 +928,12 @@ const DataGrid = forwardRef(
|
|
|
907
928
|
userProfile_settings_rows_per_page: userProfile?.settings?.rows_per_page,
|
|
908
929
|
currentLimit: limit,
|
|
909
930
|
});
|
|
910
|
-
// Only apply the user's preference once, when userProfile first
|
|
911
|
-
|
|
931
|
+
// Only apply the user's preference once, when userProfile first
|
|
932
|
+
// becomes available — and never over a size the user has picked
|
|
933
|
+
// from the pager since. This seeds the limit for the first fetch;
|
|
934
|
+
// the sizing effect below replaces it as soon as the grid has
|
|
935
|
+
// measured itself against the window.
|
|
936
|
+
if (!initialLimitApplied.current && !userChoseLimitRef.current) {
|
|
912
937
|
const userLimit = userProfile?.rows_per_page || userProfile?.settings?.rows_per_page;
|
|
913
938
|
debugLog('[DataGrid] Checking userLimit:', userLimit);
|
|
914
939
|
if (userLimit && userLimit > 0) {
|
|
@@ -3099,10 +3124,10 @@ const DataGrid = forwardRef(
|
|
|
3099
3124
|
|
|
3100
3125
|
const handleCoding = (c, data) => {
|
|
3101
3126
|
const icons = [];
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3127
|
+
// Read through buildEnv, not import.meta.env directly: the
|
|
3128
|
+
// latter is Vite's interface and throws a TypeError under
|
|
3129
|
+
// webpack, which took the whole grid down in the Next.js portal.
|
|
3130
|
+
const assetPath = buildEnv('REACT_APP_ASSET_PATH', '');
|
|
3106
3131
|
|
|
3107
3132
|
const addIcon = (src, icon, alt, tooltipContent, key) => {
|
|
3108
3133
|
if (src && src !== '') {
|
|
@@ -3920,7 +3945,16 @@ const DataGrid = forwardRef(
|
|
|
3920
3945
|
// clamped columns exceed the container (narrow
|
|
3921
3946
|
// screens) — columns then render a few px wide
|
|
3922
3947
|
// with no horizontal scrollbar ever appearing.
|
|
3923
|
-
|
|
3948
|
+
// A date filter needs room for its locale mask
|
|
3949
|
+
// ("dd/mm/yyyy" + the picker glyph); below ~140px
|
|
3950
|
+
// the native input clips its own value.
|
|
3951
|
+
minWidth:
|
|
3952
|
+
column.minWidth ??
|
|
3953
|
+
(['date', 'datetime', 'datetime-local'].includes(
|
|
3954
|
+
column.type
|
|
3955
|
+
)
|
|
3956
|
+
? Math.max(grid.columnMinWidth, 140)
|
|
3957
|
+
: grid.columnMinWidth),
|
|
3924
3958
|
maxWidth: column.maxWidth ?? undefined,
|
|
3925
3959
|
textAlign: column.textAlign ?? undefined,
|
|
3926
3960
|
textVerticalAlign:
|
|
@@ -5193,11 +5227,42 @@ const DataGrid = forwardRef(
|
|
|
5193
5227
|
// Data Grid Styling
|
|
5194
5228
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
5195
5229
|
const [dataCount, setDataCount] = useState(0);
|
|
5230
|
+
|
|
5231
|
+
// Pages that mount the grid directly (rather than through GenericIndex)
|
|
5232
|
+
// pass no gridHeight, and fitToRows deliberately never grows past the
|
|
5233
|
+
// rows - so on a tall screen the grid sat at a few rows' height and
|
|
5234
|
+
// scrolled inside itself. The grid now measures what is left between
|
|
5235
|
+
// its own top edge and the bottom of the viewport; fitToRows remains
|
|
5236
|
+
// the first-paint fallback.
|
|
5237
|
+
//
|
|
5238
|
+
// Measured from `.dataGridContainer` rather than from the whole
|
|
5239
|
+
// component, so the search/create bar and the legend above it are part
|
|
5240
|
+
// of `top` instead of being asked for a second time below.
|
|
5241
|
+
// `measureChrome` subtracts what the *page* needs under the grid — the
|
|
5242
|
+
// card's padding and border, the page gutter, any section rendered
|
|
5243
|
+
// after it — which used to be a flat 28px guess that was wrong for any
|
|
5244
|
+
// page nesting its grid differently.
|
|
5245
|
+
const containerRef = useRef(null);
|
|
5246
|
+
const measuredHeight = useAvailableHeight(containerRef, {
|
|
5247
|
+
bottomGutter: 0,
|
|
5248
|
+
measureChrome: true,
|
|
5249
|
+
recomputeKey: windowHeight,
|
|
5250
|
+
});
|
|
5251
|
+
const resolvedHeight =
|
|
5252
|
+
gridHeight && gridHeight > 0
|
|
5253
|
+
? gridHeight
|
|
5254
|
+
: measuredHeight != null && measuredHeight > 0
|
|
5255
|
+
? measuredHeight
|
|
5256
|
+
: null;
|
|
5257
|
+
|
|
5258
|
+
// Both minHeight AND height: with minHeight alone the grid settles at
|
|
5259
|
+
// its content height on a tall screen. An explicit height makes it
|
|
5260
|
+
// take the room measured for it.
|
|
5261
|
+
const initialHeight =
|
|
5262
|
+
resolvedHeight ?? fitToRows(dataCount, windowHeight);
|
|
5196
5263
|
const [gridStyle, setGridStyle] = useState({
|
|
5197
|
-
minHeight:
|
|
5198
|
-
|
|
5199
|
-
? gridHeight
|
|
5200
|
-
: fitToRows(dataCount, windowHeight),
|
|
5264
|
+
minHeight: initialHeight,
|
|
5265
|
+
height: initialHeight,
|
|
5201
5266
|
boxShadow: 'none',
|
|
5202
5267
|
...(hoverColor ? { '--hover-color': hoverColor } : {}),
|
|
5203
5268
|
});
|
|
@@ -5205,39 +5270,116 @@ const DataGrid = forwardRef(
|
|
|
5205
5270
|
style: style ? style : {},
|
|
5206
5271
|
};
|
|
5207
5272
|
|
|
5273
|
+
/**
|
|
5274
|
+
* The part of the grid's height that is not data rows: the column
|
|
5275
|
+
* header, the filter row (only some pages have one), the footer rows
|
|
5276
|
+
* and the pagination toolbar.
|
|
5277
|
+
*
|
|
5278
|
+
* Read off the rendered grid rather than assumed, because it changes
|
|
5279
|
+
* with density, with `enableFiltering`, and with whether the page shows
|
|
5280
|
+
* a footer — and because the pager *is* inside the grid element, so
|
|
5281
|
+
* getting this wrong is exactly what leaves the last row hidden behind
|
|
5282
|
+
* it or the pager stranded under a band of empty body.
|
|
5283
|
+
*
|
|
5284
|
+
* Returns null on the first pass, before the grid is in the DOM; the
|
|
5285
|
+
* caller falls back to GRID_CHROME.
|
|
5286
|
+
*/
|
|
5287
|
+
const measureGridChrome = () => {
|
|
5288
|
+
const root = containerRef.current;
|
|
5289
|
+
|
|
5290
|
+
if (!root || typeof root.querySelector !== 'function') {
|
|
5291
|
+
return null;
|
|
5292
|
+
}
|
|
5293
|
+
|
|
5294
|
+
const gridEl = root.querySelector('.InovuaReactDataGrid');
|
|
5295
|
+
const body = root.querySelector('.InovuaReactDataGrid__body');
|
|
5296
|
+
|
|
5297
|
+
if (!gridEl || !body) {
|
|
5298
|
+
return null;
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
const chrome =
|
|
5302
|
+
gridEl.getBoundingClientRect().height -
|
|
5303
|
+
body.getBoundingClientRect().height;
|
|
5304
|
+
|
|
5305
|
+
return chrome > 0 ? chrome : null;
|
|
5306
|
+
};
|
|
5307
|
+
|
|
5208
5308
|
useEffect(() => {
|
|
5209
5309
|
const getMinimumHeight = () => {
|
|
5210
|
-
if (
|
|
5310
|
+
if (resolvedHeight > 0) return resolvedHeight;
|
|
5211
5311
|
return fitToRows(dataCount, window.innerHeight);
|
|
5212
5312
|
};
|
|
5213
5313
|
|
|
5214
5314
|
const minHeight = getMinimumHeight();
|
|
5215
|
-
setGridStyle((prevState) => ({
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
// 3. Calculated rows based on height (fallback)
|
|
5221
|
-
|
|
5222
|
-
// Only auto-calculate limit if user hasn't set a preference
|
|
5223
|
-
const userPreferredLimit = userProfile?.rows_per_page || userProfile?.settings?.rows_per_page;
|
|
5315
|
+
setGridStyle((prevState) => ({
|
|
5316
|
+
...prevState,
|
|
5317
|
+
minHeight,
|
|
5318
|
+
height: minHeight,
|
|
5319
|
+
}));
|
|
5224
5320
|
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5321
|
+
// How many rows to ask for.
|
|
5322
|
+
//
|
|
5323
|
+
// The rule used to be "if the profile has a rows_per_page, never
|
|
5324
|
+
// auto-calculate". That is a stored default, not a decision anyone
|
|
5325
|
+
// made — it is 25 for most staff — so every index page on a 1080p
|
|
5326
|
+
// screen drew 25 rows and then several hundred px of empty grid
|
|
5327
|
+
// body under them, with the pager stranded at the bottom of it.
|
|
5328
|
+
//
|
|
5329
|
+
// The profile value now *seeds* the limit (see the initial state
|
|
5330
|
+
// and the effect that applies it once the profile arrives) and
|
|
5331
|
+
// nothing more. Once the grid has measured itself against the
|
|
5332
|
+
// window, the measurement wins — unless the user has picked a size
|
|
5333
|
+
// from the pager's "Results per page" control in this session, in
|
|
5334
|
+
// which case their choice is final for as long as the grid is
|
|
5335
|
+
// mounted. Whatever the pager does about persisting that choice to
|
|
5336
|
+
// the profile is unchanged.
|
|
5337
|
+
//
|
|
5338
|
+
// A page that passes an explicit `gridHeight` is not sized to the
|
|
5339
|
+
// window at all (a grid in a modal, a secondary grid on a detail
|
|
5340
|
+
// page), so it keeps the old order: profile preference, then a
|
|
5341
|
+
// configured `take`, then the arithmetic.
|
|
5342
|
+
const autoSizedToWindow = !(gridHeight && gridHeight > 0);
|
|
5343
|
+
const userPreferredLimit =
|
|
5344
|
+
userProfile?.rows_per_page ||
|
|
5345
|
+
userProfile?.settings?.rows_per_page;
|
|
5346
|
+
|
|
5347
|
+
if (userChoseLimitRef.current) {
|
|
5348
|
+
debugLog(
|
|
5349
|
+
'[DataGrid] Honouring the size the user picked this session:',
|
|
5350
|
+
limitRef.current
|
|
5351
|
+
);
|
|
5352
|
+
} else if (
|
|
5353
|
+
!autoSizedToWindow &&
|
|
5354
|
+
userPreferredLimit &&
|
|
5355
|
+
userPreferredLimit > 0
|
|
5356
|
+
) {
|
|
5357
|
+
debugLog(
|
|
5358
|
+
'[DataGrid] Fixed-height grid, respecting profile preference:',
|
|
5359
|
+
userPreferredLimit
|
|
5360
|
+
);
|
|
5361
|
+
} else if (!autoSizedToWindow && ajaxSetting?.take >= 35) {
|
|
5229
5362
|
setLimit(ajaxSetting.take);
|
|
5230
5363
|
} else {
|
|
5231
|
-
//
|
|
5232
|
-
//
|
|
5233
|
-
//
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
setLimit(
|
|
5364
|
+
// Prefer the chrome the grid is really wearing — the column
|
|
5365
|
+
// header, the filter row if this page has one, and the pager
|
|
5366
|
+
// toolbar all live inside `minHeight`, and they differ by
|
|
5367
|
+
// density and by config. GRID_CHROME is the fallback for the
|
|
5368
|
+
// first pass, before the grid exists to be measured.
|
|
5369
|
+
const chrome = measureGridChrome() ?? GRID_CHROME;
|
|
5370
|
+
|
|
5371
|
+
setLimit(rowsForHeight(minHeight, chrome, grid.rowUnit));
|
|
5239
5372
|
}
|
|
5240
|
-
}, [
|
|
5373
|
+
}, [
|
|
5374
|
+
dataCount,
|
|
5375
|
+
gridHeight,
|
|
5376
|
+
resolvedHeight,
|
|
5377
|
+
window.innerHeight,
|
|
5378
|
+
grid,
|
|
5379
|
+
ajaxSetting?.take,
|
|
5380
|
+
userProfile?.rows_per_page,
|
|
5381
|
+
userProfile?.settings?.rows_per_page,
|
|
5382
|
+
]);
|
|
5241
5383
|
|
|
5242
5384
|
useEffect(() => {
|
|
5243
5385
|
const debouncedHandleResize = debounce(() => {
|
|
@@ -5393,8 +5535,9 @@ const DataGrid = forwardRef(
|
|
|
5393
5535
|
</div>
|
|
5394
5536
|
)}
|
|
5395
5537
|
|
|
5396
|
-
{/* DataGrid container with its own border
|
|
5397
|
-
|
|
5538
|
+
{/* DataGrid container with its own border; its top edge is
|
|
5539
|
+
what the available height is measured from. */}
|
|
5540
|
+
<div className={styles.dataGridContainer} ref={containerRef}>
|
|
5398
5541
|
|
|
5399
5542
|
{limit && limit > 0 ? (
|
|
5400
5543
|
<ReactDataGrid
|
|
@@ -5933,7 +6076,7 @@ const DataGrid = forwardRef(
|
|
|
5933
6076
|
onFilterValueChange={(fv) => {
|
|
5934
6077
|
handleFilterChange(fv, filterValue);
|
|
5935
6078
|
}}
|
|
5936
|
-
onLimitChange={
|
|
6079
|
+
onLimitChange={handleLimitChange}
|
|
5937
6080
|
onSelectionChange={handleSelectionChange}
|
|
5938
6081
|
onSortInfoChange={handleSort}
|
|
5939
6082
|
onRenderRow={onRenderRow}
|
package/src/components/Fetch.jsx
CHANGED
|
@@ -4,6 +4,9 @@ import { trackPromise } from 'react-promise-tracker';
|
|
|
4
4
|
import { toast } from 'react-toastify';
|
|
5
5
|
import parse from 'html-react-parser';
|
|
6
6
|
|
|
7
|
+
// import.meta.env is Vite-only; this reads it safely under any bundler.
|
|
8
|
+
import { readBuildEnv } from './utils/buildEnv';
|
|
9
|
+
|
|
7
10
|
// Create a singleton QueryClient instance for the entire visns-components ecosystem
|
|
8
11
|
const createQueryClient = () => {
|
|
9
12
|
return new QueryClient({
|
|
@@ -165,7 +168,8 @@ const originalCustomFetch = (
|
|
|
165
168
|
method,
|
|
166
169
|
formData,
|
|
167
170
|
successCallback = null,
|
|
168
|
-
errorCallback = null
|
|
171
|
+
errorCallback = null,
|
|
172
|
+
options = {}
|
|
169
173
|
) => {
|
|
170
174
|
const baseUrl = '';
|
|
171
175
|
|
|
@@ -191,7 +195,7 @@ const originalCustomFetch = (
|
|
|
191
195
|
headers['X-XSRF-TOKEN'] = csrfToken;
|
|
192
196
|
}
|
|
193
197
|
const MAX_PAYLOAD_SIZE_MB =
|
|
194
|
-
parseFloat(
|
|
198
|
+
parseFloat(readBuildEnv('VITE_MAX_PAYLOAD_SIZE_MB')) || 4.5;
|
|
195
199
|
const MAX_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE_MB * 1024 * 1024; // Convert MB to bytes
|
|
196
200
|
|
|
197
201
|
// Estimate headers size
|
|
@@ -304,6 +308,40 @@ const originalCustomFetch = (
|
|
|
304
308
|
resolve(responseObj);
|
|
305
309
|
})
|
|
306
310
|
.catch((error) => {
|
|
311
|
+
/*
|
|
312
|
+
* `options.silentStatuses` — an opt-in list of HTTP statuses
|
|
313
|
+
* that are part of a flow rather than a failure, and so must
|
|
314
|
+
* not be announced.
|
|
315
|
+
*
|
|
316
|
+
* The vault's reveal endpoint answers 423 to mean "confirm
|
|
317
|
+
* your password first", which the caller handles by drawing a
|
|
318
|
+
* confirm panel. Without this, that perfectly ordinary step
|
|
319
|
+
* either toasts an error at the user or forces the caller to
|
|
320
|
+
* pass an errorCallback that can only see a message string —
|
|
321
|
+
* never the status it needs to branch on.
|
|
322
|
+
*
|
|
323
|
+
* A listed status skips the toast AND the errorCallback, and
|
|
324
|
+
* rejects with the status and parsed body hung directly off
|
|
325
|
+
* the error so `catch (e) { e.status; e.data }` works. Opt-in,
|
|
326
|
+
* so every existing caller behaves exactly as before.
|
|
327
|
+
*/
|
|
328
|
+
const silentStatuses = Array.isArray(options.silentStatuses)
|
|
329
|
+
? options.silentStatuses
|
|
330
|
+
: [];
|
|
331
|
+
const responseStatus = error.response
|
|
332
|
+
? error.response.status
|
|
333
|
+
: undefined;
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
responseStatus !== undefined &&
|
|
337
|
+
silentStatuses.includes(responseStatus)
|
|
338
|
+
) {
|
|
339
|
+
error.status = responseStatus;
|
|
340
|
+
error.data = error.response.data;
|
|
341
|
+
reject(error);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
307
345
|
let errorMessage = '';
|
|
308
346
|
|
|
309
347
|
if (error.response && error.response.data) {
|
|
@@ -323,6 +361,21 @@ const originalCustomFetch = (
|
|
|
323
361
|
errorMessage = data.message;
|
|
324
362
|
}
|
|
325
363
|
}
|
|
364
|
+
|
|
365
|
+
// Last resort, and only when nothing above found anything:
|
|
366
|
+
// this library's own backends answer a rejected auth
|
|
367
|
+
// attempt with `{error: '…'}` and no `message` at all, and
|
|
368
|
+
// on a non-200 that body used to be dropped entirely — the
|
|
369
|
+
// caller got an empty toast and no idea why it failed.
|
|
370
|
+
// Ordered after `message` so nothing that already surfaced
|
|
371
|
+
// changes.
|
|
372
|
+
if (
|
|
373
|
+
!errorMessage &&
|
|
374
|
+
typeof data.error === 'string' &&
|
|
375
|
+
data.error !== ''
|
|
376
|
+
) {
|
|
377
|
+
errorMessage = data.error;
|
|
378
|
+
}
|
|
326
379
|
} else {
|
|
327
380
|
errorMessage = error.message;
|
|
328
381
|
}
|
|
@@ -386,6 +439,15 @@ const CustomFetch = (
|
|
|
386
439
|
} else if (data.message) {
|
|
387
440
|
errorMessage = data.message;
|
|
388
441
|
}
|
|
442
|
+
// Same last-resort `{error}` fallback as the
|
|
443
|
+
// uncached path above.
|
|
444
|
+
if (
|
|
445
|
+
!errorMessage &&
|
|
446
|
+
typeof data.error === 'string' &&
|
|
447
|
+
data.error !== ''
|
|
448
|
+
) {
|
|
449
|
+
errorMessage = data.error;
|
|
450
|
+
}
|
|
389
451
|
} else {
|
|
390
452
|
errorMessage = error.message;
|
|
391
453
|
}
|
|
@@ -408,6 +470,14 @@ const CustomFetch = (
|
|
|
408
470
|
errorMessage = data.message;
|
|
409
471
|
}
|
|
410
472
|
}
|
|
473
|
+
// Same last-resort `{error}` fallback as above.
|
|
474
|
+
if (
|
|
475
|
+
!errorMessage &&
|
|
476
|
+
typeof data.error === 'string' &&
|
|
477
|
+
data.error !== ''
|
|
478
|
+
) {
|
|
479
|
+
errorMessage = data.error;
|
|
480
|
+
}
|
|
411
481
|
} else {
|
|
412
482
|
errorMessage = error.message;
|
|
413
483
|
}
|
|
@@ -424,7 +494,14 @@ const CustomFetch = (
|
|
|
424
494
|
} else {
|
|
425
495
|
// Use original implementation for non-cacheable requests (mutations)
|
|
426
496
|
const result = trackPromise(
|
|
427
|
-
originalCustomFetch(
|
|
497
|
+
originalCustomFetch(
|
|
498
|
+
url,
|
|
499
|
+
method,
|
|
500
|
+
formData,
|
|
501
|
+
successCallback,
|
|
502
|
+
errorCallback,
|
|
503
|
+
options
|
|
504
|
+
)
|
|
428
505
|
);
|
|
429
506
|
|
|
430
507
|
// Auto-invalidate related caches after mutations
|
package/src/components/Form.jsx
CHANGED
|
@@ -23,6 +23,7 @@ import SumEqualsSummary, {
|
|
|
23
23
|
sumEqualsHintFor,
|
|
24
24
|
} from './SumEqualsSummary';
|
|
25
25
|
import Table from './DataGrid';
|
|
26
|
+
import { SECONDARY_GRID_HEIGHT } from './utils/useDensity';
|
|
26
27
|
|
|
27
28
|
import styles from './styles/Form.module.scss';
|
|
28
29
|
|
|
@@ -3195,6 +3196,14 @@ function Form({
|
|
|
3195
3196
|
mapbox={mapbox}
|
|
3196
3197
|
paramValue={paramValue}
|
|
3197
3198
|
settings={item.settings}
|
|
3199
|
+
/* A form is almost always inside
|
|
3200
|
+
a modal, so this grid must not
|
|
3201
|
+
take the room between itself and
|
|
3202
|
+
the bottom of the window — that
|
|
3203
|
+
is measured from a box that is
|
|
3204
|
+
already centred, and pushes the
|
|
3205
|
+
pager below the fold. */
|
|
3206
|
+
gridHeight={SECONDARY_GRID_HEIGHT}
|
|
3198
3207
|
style={
|
|
3199
3208
|
userProfile?.settings?.style ||
|
|
3200
3209
|
{}
|