@visns-studio/visns-components 5.6.5 → 5.6.7
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 +86 -1
- package/package.json +6 -6
- package/src/components/crm/DataGrid.jsx +114 -14
- package/src/components/crm/Field.jsx +1 -0
- package/src/components/crm/MultiSelect.jsx +2 -1
- package/src/components/crm/generic/GenericDetail.jsx +282 -108
- package/src/components/crm/generic/styles/GenericDetail.module.scss +102 -34
- package/src/components/crm/generic/styles/GenericMain.module.scss +2 -2
- package/src/components/crm/styles/DataGrid.module.scss +48 -1
- package/src/components/crm/styles/Form.module.scss +48 -0
- package/src/components/crm/styles/Navigation.module.scss +9 -0
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ VISNS Components is a React-based UI component library that provides a set of re
|
|
|
59
59
|
- **Notification Pages** - User notification center
|
|
60
60
|
- **Sorting Pages** - Content ordering interfaces
|
|
61
61
|
- **Report Pages** - Custom report building and execution
|
|
62
|
+
- **Audit Logs** - System activity tracking and history
|
|
62
63
|
|
|
63
64
|
## Installation
|
|
64
65
|
|
|
@@ -224,6 +225,34 @@ A powerful data table component with sorting, filtering, and pagination.
|
|
|
224
225
|
| setConfig | Callback for configuration changes | Function | - |
|
|
225
226
|
| style | Custom styles | Object | - |
|
|
226
227
|
|
|
228
|
+
#### Auto-Refresh Functionality
|
|
229
|
+
|
|
230
|
+
The DataGrid component includes an auto-refresh feature that automatically reloads data at specified intervals. This is useful for displaying real-time or frequently updated data.
|
|
231
|
+
|
|
232
|
+
To enable auto-refresh:
|
|
233
|
+
|
|
234
|
+
```jsx
|
|
235
|
+
<DataGrid
|
|
236
|
+
ajaxSetting={{
|
|
237
|
+
url: '/api/data',
|
|
238
|
+
method: 'GET',
|
|
239
|
+
params: {},
|
|
240
|
+
autoRefresh: 30, // Refresh every 30 seconds
|
|
241
|
+
// or
|
|
242
|
+
autoRefresh: true, // Use default 30-second interval
|
|
243
|
+
}}
|
|
244
|
+
// Other props...
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
When auto-refresh is configured:
|
|
249
|
+
|
|
250
|
+
- A checkbox appears in the DataGrid header to enable/disable auto-refresh
|
|
251
|
+
- Auto-refresh is enabled by default when configured
|
|
252
|
+
- The interval can be customized (in seconds) by setting a numeric value
|
|
253
|
+
- Setting `autoRefresh: true` uses the default 30-second interval
|
|
254
|
+
- The component silently refreshes data without showing notifications
|
|
255
|
+
|
|
227
256
|
### Form Components
|
|
228
257
|
|
|
229
258
|
#### Form
|
|
@@ -336,7 +365,7 @@ File upload component with drag and drop support.
|
|
|
336
365
|
|
|
337
366
|
#### CustomFetch
|
|
338
367
|
|
|
339
|
-
Data fetching utility with error handling.
|
|
368
|
+
Data fetching utility with error handling and payload size validation.
|
|
340
369
|
|
|
341
370
|
```jsx
|
|
342
371
|
// Using async/await
|
|
@@ -358,6 +387,15 @@ CustomFetch(
|
|
|
358
387
|
);
|
|
359
388
|
```
|
|
360
389
|
|
|
390
|
+
The CustomFetch component includes these features:
|
|
391
|
+
|
|
392
|
+
- **Payload Size Validation**: Automatically checks if the request payload exceeds the maximum allowed size (default: 4.5MB, configurable via `VITE_MAX_PAYLOAD_SIZE_MB` environment variable)
|
|
393
|
+
- **Toast Notifications**: Displays error messages using toast notifications
|
|
394
|
+
- **Promise Tracking**: Integrates with react-promise-tracker for loading indicators
|
|
395
|
+
- **HTML Error Parsing**: Properly renders HTML content in error messages
|
|
396
|
+
- **CSRF Protection**: Redirects to login page on CSRF token mismatch
|
|
397
|
+
- **Authentication Handling**: Silently handles unauthenticated errors without showing toast notifications
|
|
398
|
+
|
|
361
399
|
#### Download
|
|
362
400
|
|
|
363
401
|
File download utility.
|
|
@@ -404,6 +442,53 @@ The QrCode component fetches QR code data from the specified URL and displays it
|
|
|
404
442
|
}
|
|
405
443
|
```
|
|
406
444
|
|
|
445
|
+
#### AuditLogs and AuditLog
|
|
446
|
+
|
|
447
|
+
The AuditLogs component displays a table of system activity logs, showing who made changes, what was changed, and when the changes occurred.
|
|
448
|
+
|
|
449
|
+
```jsx
|
|
450
|
+
<AuditLogs layout="full" />
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The AuditLog component displays detailed information about a specific audit log entry.
|
|
454
|
+
|
|
455
|
+
```jsx
|
|
456
|
+
<AuditLog userProfile={userProfile} />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The AuditLogs component features:
|
|
460
|
+
|
|
461
|
+
- User tracking - Shows which user performed the action
|
|
462
|
+
- Event tracking - Displays the type of action (create, update, delete)
|
|
463
|
+
- Value comparison - Shows old and new values for changed fields
|
|
464
|
+
- IP address logging - Records the IP address of the user
|
|
465
|
+
- Timestamp recording - Shows when the action occurred
|
|
466
|
+
- Formatted display - Presents JSON data in a readable format
|
|
467
|
+
|
|
468
|
+
The component expects audit log data from the API with the following structure:
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"id": 123,
|
|
473
|
+
"user": {
|
|
474
|
+
"id": 1,
|
|
475
|
+
"name": "John Doe"
|
|
476
|
+
},
|
|
477
|
+
"old_values": {
|
|
478
|
+
"field1": "old value",
|
|
479
|
+
"field2": "old value"
|
|
480
|
+
},
|
|
481
|
+
"new_values": {
|
|
482
|
+
"field1": "new value",
|
|
483
|
+
"field2": "new value"
|
|
484
|
+
},
|
|
485
|
+
"formatted_event": "Updating",
|
|
486
|
+
"ip_address": "192.168.1.1",
|
|
487
|
+
"url": "/api/resource/123",
|
|
488
|
+
"created_at": "2023-01-01T12:00:00Z"
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
407
492
|
## Two-Factor Authentication (2FA)
|
|
408
493
|
|
|
409
494
|
## Client Portal
|
package/package.json
CHANGED
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
"akar-icons": "^1.9.31",
|
|
17
17
|
"array-move": "^4.0.0",
|
|
18
18
|
"awesome-debounce-promise": "^2.1.0",
|
|
19
|
-
"axios": "^1.
|
|
19
|
+
"axios": "^1.9.0",
|
|
20
20
|
"browser-image-compression": "^2.0.2",
|
|
21
21
|
"dayjs": "^1.11.13",
|
|
22
22
|
"fabric": "^6.6.4",
|
|
23
23
|
"file-saver": "^2.0.5",
|
|
24
|
-
"framer-motion": "^12.
|
|
24
|
+
"framer-motion": "^12.9.2",
|
|
25
25
|
"html-react-parser": "^5.2.3",
|
|
26
26
|
"lodash": "^4.17.21",
|
|
27
27
|
"lodash.debounce": "^4.0.8",
|
|
28
28
|
"moment": "^2.30.1",
|
|
29
|
-
"motion": "^12.
|
|
29
|
+
"motion": "^12.9.2",
|
|
30
30
|
"numeral": "^2.0.6",
|
|
31
31
|
"pluralize": "^8.0.0",
|
|
32
32
|
"qrcode.react": "^4.2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"react-signature-pad-wrapper": "^4.1.0",
|
|
49
49
|
"react-slugify": "^4.0.1",
|
|
50
50
|
"react-sortable-hoc": "^2.0.0",
|
|
51
|
-
"react-to-print": "^3.0
|
|
51
|
+
"react-to-print": "^3.1.0",
|
|
52
52
|
"react-toastify": "^11.0.5",
|
|
53
53
|
"react-toggle": "^4.1.3",
|
|
54
54
|
"react-tooltip": "^5.28.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"validator": "^13.15.0",
|
|
63
63
|
"vite": "^5.4.18",
|
|
64
64
|
"yarn": "^1.22.22",
|
|
65
|
-
"yet-another-react-lightbox": "^3.
|
|
65
|
+
"yet-another-react-lightbox": "^3.23.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@babel/core": "^7.26.10",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
85
85
|
},
|
|
86
86
|
"name": "@visns-studio/visns-components",
|
|
87
|
-
"version": "5.6.
|
|
87
|
+
"version": "5.6.7",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -17,7 +17,6 @@ import numeral from 'numeral';
|
|
|
17
17
|
import Popup from 'reactjs-popup';
|
|
18
18
|
import 'reactjs-popup/dist/index.css';
|
|
19
19
|
import parse from 'html-react-parser';
|
|
20
|
-
import 'react-toggle/style.css';
|
|
21
20
|
import { saveAs } from 'file-saver';
|
|
22
21
|
import NumberFilter from '@visns-studio/visns-datagrid-enterprise/NumberFilter';
|
|
23
22
|
import SelectFilter from '@visns-studio/visns-datagrid-enterprise/SelectFilter';
|
|
@@ -386,10 +385,51 @@ const DataGrid = forwardRef(
|
|
|
386
385
|
const [localInputValues, setLocalInputValues] = useState({});
|
|
387
386
|
const localInputValuesRef = useRef({}); // Add this ref to track values
|
|
388
387
|
|
|
388
|
+
/** Auto Refresh States */
|
|
389
|
+
const [autoRefreshEnabled, setAutoRefreshEnabled] = useState(false);
|
|
390
|
+
const [refreshInterval, setRefreshInterval] = useState(30); // Default 30 seconds interval
|
|
391
|
+
const refreshTimerRef = useRef(null);
|
|
392
|
+
const autoRefreshInitialized = useRef(false); // Track if we've initialized auto-refresh
|
|
393
|
+
|
|
394
|
+
/** Responsive Modal States */
|
|
395
|
+
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
|
396
|
+
|
|
389
397
|
useEffect(() => {
|
|
390
398
|
localInputValuesRef.current = localInputValues;
|
|
391
399
|
}, [localInputValues]);
|
|
392
400
|
|
|
401
|
+
// Effect to handle window resize for responsive modals
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
const handleResize = () => {
|
|
404
|
+
setWindowWidth(window.innerWidth);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
window.addEventListener('resize', handleResize);
|
|
408
|
+
|
|
409
|
+
// Clean up event listener on unmount
|
|
410
|
+
return () => {
|
|
411
|
+
window.removeEventListener('resize', handleResize);
|
|
412
|
+
};
|
|
413
|
+
}, []);
|
|
414
|
+
|
|
415
|
+
// Effect to handle autoRefresh setting
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (ajaxSetting && ajaxSetting.autoRefresh !== undefined) {
|
|
418
|
+
// Enable auto-refresh by default when the setting is present
|
|
419
|
+
if (!autoRefreshInitialized.current) {
|
|
420
|
+
setAutoRefreshEnabled(true);
|
|
421
|
+
autoRefreshInitialized.current = true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Set the refresh interval based on the provided value
|
|
425
|
+
if (typeof ajaxSetting.autoRefresh === 'number') {
|
|
426
|
+
setRefreshInterval(ajaxSetting.autoRefresh);
|
|
427
|
+
} else if (ajaxSetting.autoRefresh === true) {
|
|
428
|
+
setRefreshInterval(30); // Default 30 seconds
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}, [ajaxSetting]);
|
|
432
|
+
|
|
393
433
|
/** Table Functions */
|
|
394
434
|
useImperativeHandle(ref, () => ({
|
|
395
435
|
reload: () => {
|
|
@@ -754,8 +794,6 @@ const DataGrid = forwardRef(
|
|
|
754
794
|
// Extracting nested value using predefined keys from 's.relation'
|
|
755
795
|
const nestedValue = getNestedValue(d, s.relation);
|
|
756
796
|
|
|
757
|
-
console.info(d, s.relation);
|
|
758
|
-
|
|
759
797
|
// Check if nestedValue is defined and is an array before mapping
|
|
760
798
|
if (!nestedValue || !Array.isArray(nestedValue)) {
|
|
761
799
|
console.warn(
|
|
@@ -1243,11 +1281,33 @@ const DataGrid = forwardRef(
|
|
|
1243
1281
|
className={styles.searchInput}
|
|
1244
1282
|
/>
|
|
1245
1283
|
</div>
|
|
1246
|
-
{
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1284
|
+
<div className={styles.actionButtonsWrapper}>
|
|
1285
|
+
{!isCreateDisabled && renderCreateButton()}
|
|
1286
|
+
</div>
|
|
1287
|
+
</div>
|
|
1288
|
+
);
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
const renderAutoRefreshControls = () => {
|
|
1292
|
+
// Only show auto-refresh if the setting is present in ajaxSetting
|
|
1293
|
+
if (!ajaxSetting || ajaxSetting.autoRefresh === undefined) {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
return (
|
|
1298
|
+
<div className={styles.autoRefreshContainer}>
|
|
1299
|
+
<label className={styles.autoRefreshLabel}>
|
|
1300
|
+
<input
|
|
1301
|
+
type="checkbox"
|
|
1302
|
+
checked={autoRefreshEnabled}
|
|
1303
|
+
onChange={() =>
|
|
1304
|
+
setAutoRefreshEnabled(!autoRefreshEnabled)
|
|
1305
|
+
}
|
|
1306
|
+
className={styles.autoRefreshCheckbox}
|
|
1307
|
+
/>
|
|
1308
|
+
Auto Refresh{' '}
|
|
1309
|
+
{refreshInterval && `(${refreshInterval}s)`}
|
|
1310
|
+
</label>
|
|
1251
1311
|
</div>
|
|
1252
1312
|
);
|
|
1253
1313
|
};
|
|
@@ -1259,7 +1319,7 @@ const DataGrid = forwardRef(
|
|
|
1259
1319
|
if (linkUrl) {
|
|
1260
1320
|
const baseUrl = window.location.origin;
|
|
1261
1321
|
const fullUrl = `${baseUrl}${linkUrl}${
|
|
1262
|
-
rowProps.data[form.primaryKey]
|
|
1322
|
+
rowProps.data[cellProps?.link?.key || form.primaryKey]
|
|
1263
1323
|
}`;
|
|
1264
1324
|
|
|
1265
1325
|
menuProps.autoDismiss = true;
|
|
@@ -3146,6 +3206,34 @@ const DataGrid = forwardRef(
|
|
|
3146
3206
|
};
|
|
3147
3207
|
}, []);
|
|
3148
3208
|
|
|
3209
|
+
// Auto-refresh effect
|
|
3210
|
+
useEffect(() => {
|
|
3211
|
+
// Clear any existing timer
|
|
3212
|
+
if (refreshTimerRef.current) {
|
|
3213
|
+
clearInterval(refreshTimerRef.current);
|
|
3214
|
+
refreshTimerRef.current = null;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// Set up new timer if auto-refresh is enabled and the setting is present in ajaxSetting
|
|
3218
|
+
if (
|
|
3219
|
+
autoRefreshEnabled &&
|
|
3220
|
+
ajaxSetting &&
|
|
3221
|
+
ajaxSetting.autoRefresh !== undefined
|
|
3222
|
+
) {
|
|
3223
|
+
refreshTimerRef.current = setInterval(() => {
|
|
3224
|
+
handleReload();
|
|
3225
|
+
// No toast notification on refresh
|
|
3226
|
+
}, refreshInterval * 1000);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// Clean up on unmount
|
|
3230
|
+
return () => {
|
|
3231
|
+
if (refreshTimerRef.current) {
|
|
3232
|
+
clearInterval(refreshTimerRef.current);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
}, [autoRefreshEnabled, refreshInterval, ajaxSetting]);
|
|
3236
|
+
|
|
3149
3237
|
return (
|
|
3150
3238
|
<div
|
|
3151
3239
|
style={{
|
|
@@ -3156,12 +3244,15 @@ const DataGrid = forwardRef(
|
|
|
3156
3244
|
overflow: 'hidden',
|
|
3157
3245
|
}}
|
|
3158
3246
|
>
|
|
3159
|
-
{/*
|
|
3160
|
-
{
|
|
3161
|
-
<div className={styles.
|
|
3247
|
+
{/* Top container with search, action buttons, and auto-refresh */}
|
|
3248
|
+
<div className={styles.dataGridTopContainer}>
|
|
3249
|
+
<div className={styles.dataGridTopLeftContainer}>
|
|
3162
3250
|
{renderSearch()}
|
|
3163
3251
|
</div>
|
|
3164
|
-
|
|
3252
|
+
<div className={styles.dataGridTopRightContainer}>
|
|
3253
|
+
{renderAutoRefreshControls()}
|
|
3254
|
+
</div>
|
|
3255
|
+
</div>
|
|
3165
3256
|
|
|
3166
3257
|
{/* DataGrid container with its own border */}
|
|
3167
3258
|
<div className={styles.dataGridContainer}>
|
|
@@ -3221,6 +3312,11 @@ const DataGrid = forwardRef(
|
|
|
3221
3312
|
open={modalShow}
|
|
3222
3313
|
onClose={modalClose}
|
|
3223
3314
|
closeOnDocumentClick={false}
|
|
3315
|
+
contentStyle={{
|
|
3316
|
+
width: windowWidth < 768 ? '95vw' : '80vw',
|
|
3317
|
+
minWidth: windowWidth < 768 ? '95vw' : '600px',
|
|
3318
|
+
maxWidth: '1200px',
|
|
3319
|
+
}}
|
|
3224
3320
|
nested
|
|
3225
3321
|
>
|
|
3226
3322
|
<Form
|
|
@@ -3244,7 +3340,11 @@ const DataGrid = forwardRef(
|
|
|
3244
3340
|
open={modalGalleryShow}
|
|
3245
3341
|
onClose={modalGalleryClose}
|
|
3246
3342
|
closeOnDocumentClick={false}
|
|
3247
|
-
contentStyle={{
|
|
3343
|
+
contentStyle={{
|
|
3344
|
+
width: windowWidth < 768 ? '95vw' : '80vw',
|
|
3345
|
+
minWidth: windowWidth < 768 ? '95vw' : '600px',
|
|
3346
|
+
maxWidth: '1200px',
|
|
3347
|
+
}}
|
|
3248
3348
|
overlayStyle={{ background: 'rgba(0, 0, 0, 0.7)' }}
|
|
3249
3349
|
>
|
|
3250
3350
|
<div
|
|
@@ -27,6 +27,7 @@ function MultiSelect({
|
|
|
27
27
|
settings,
|
|
28
28
|
style,
|
|
29
29
|
isCreatable = false,
|
|
30
|
+
isSearchable = true,
|
|
30
31
|
creatableConfig = {},
|
|
31
32
|
}) {
|
|
32
33
|
const [selectOptions, setSelectOptions] = useState([]);
|
|
@@ -142,7 +143,7 @@ function MultiSelect({
|
|
|
142
143
|
<SelectComponent
|
|
143
144
|
closeMenuOnSelect={!multi || settings.limit === 1}
|
|
144
145
|
isClearable
|
|
145
|
-
isSearchable
|
|
146
|
+
isSearchable={isSearchable}
|
|
146
147
|
isMulti={multi}
|
|
147
148
|
filterOption={createFilter({ ignoreAccents: false })}
|
|
148
149
|
components={{ SelectList }}
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* GenericDetail Component
|
|
5
|
+
*
|
|
6
|
+
* This component supports two editing modes:
|
|
7
|
+
* 1. Modal editing (default) - Opens a modal with the form when the edit button is clicked
|
|
8
|
+
* 2. In-place editing - Makes the existing interface editable when the edit button is clicked
|
|
9
|
+
*
|
|
10
|
+
* To enable in-place editing, add the `inPlaceEdit: true` property to the form object in the tab configuration.
|
|
11
|
+
* Example:
|
|
12
|
+
* {
|
|
13
|
+
* id: 'overview',
|
|
14
|
+
* type: 'overview',
|
|
15
|
+
* form: {
|
|
16
|
+
* inPlaceEdit: true,
|
|
17
|
+
* // other form properties
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
3
22
|
import React, { useEffect, useRef, useState } from 'react';
|
|
4
23
|
import { Link, useParams } from 'react-router-dom';
|
|
5
24
|
import { Calendar, momentLocalizer } from 'react-big-calendar';
|
|
@@ -1546,7 +1565,6 @@ function GenericDetail({
|
|
|
1546
1565
|
src={
|
|
1547
1566
|
imageSource
|
|
1548
1567
|
}
|
|
1549
|
-
width="300"
|
|
1550
1568
|
alt={
|
|
1551
1569
|
a.file_name
|
|
1552
1570
|
}
|
|
@@ -1571,6 +1589,17 @@ function GenericDetail({
|
|
|
1571
1589
|
styles.delete
|
|
1572
1590
|
}
|
|
1573
1591
|
/>
|
|
1592
|
+
{a.file_name && (
|
|
1593
|
+
<div
|
|
1594
|
+
className={
|
|
1595
|
+
styles.filename
|
|
1596
|
+
}
|
|
1597
|
+
>
|
|
1598
|
+
{
|
|
1599
|
+
a.file_name
|
|
1600
|
+
}
|
|
1601
|
+
</div>
|
|
1602
|
+
)}
|
|
1574
1603
|
</div>
|
|
1575
1604
|
</li>
|
|
1576
1605
|
)
|
|
@@ -1586,21 +1615,29 @@ function GenericDetail({
|
|
|
1586
1615
|
}
|
|
1587
1616
|
>
|
|
1588
1617
|
{files.map((a, b) => (
|
|
1589
|
-
<li
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1618
|
+
<li key={'tf-' + b}>
|
|
1619
|
+
<div
|
|
1620
|
+
className="file-info"
|
|
1621
|
+
onClick={() => {
|
|
1622
|
+
downloadFile(
|
|
1623
|
+
a
|
|
1624
|
+
);
|
|
1625
|
+
}}
|
|
1626
|
+
>
|
|
1627
|
+
<File
|
|
1628
|
+
strokeWidth={
|
|
1629
|
+
2
|
|
1630
|
+
}
|
|
1631
|
+
size={
|
|
1632
|
+
20
|
|
1633
|
+
}
|
|
1634
|
+
/>
|
|
1635
|
+
<div className="filename">
|
|
1636
|
+
{
|
|
1637
|
+
a.file_name
|
|
1638
|
+
}
|
|
1639
|
+
</div>
|
|
1640
|
+
</div>
|
|
1604
1641
|
<button
|
|
1605
1642
|
onClick={(
|
|
1606
1643
|
event
|
|
@@ -1611,6 +1648,7 @@ function GenericDetail({
|
|
|
1611
1648
|
a.file_name
|
|
1612
1649
|
);
|
|
1613
1650
|
}}
|
|
1651
|
+
title="Delete file"
|
|
1614
1652
|
>
|
|
1615
1653
|
<TrashCan
|
|
1616
1654
|
strokeWidth={
|
|
@@ -1833,104 +1871,225 @@ function GenericDetail({
|
|
|
1833
1871
|
case 'overview':
|
|
1834
1872
|
return (
|
|
1835
1873
|
<>
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1874
|
+
{/* Show the form in-place when editing is active */}
|
|
1875
|
+
{inPlaceEditing &&
|
|
1876
|
+
editingTabId === activeTabConfig.id &&
|
|
1877
|
+
activeTabConfig.form ? (
|
|
1878
|
+
<div
|
|
1879
|
+
className={`${styles.gridtxt} inPlaceEdit`}
|
|
1880
|
+
>
|
|
1881
|
+
<div
|
|
1882
|
+
className={
|
|
1883
|
+
styles.gridtxt__header
|
|
1884
|
+
}
|
|
1885
|
+
>
|
|
1886
|
+
<span>
|
|
1887
|
+
{activeTabConfig.title ||
|
|
1888
|
+
'Edit'}
|
|
1889
|
+
</span>
|
|
1890
|
+
</div>
|
|
1891
|
+
<Form
|
|
1892
|
+
key={`form-inline-${activeTabConfig.id}`}
|
|
1893
|
+
columnId={routeParams[urlParam]}
|
|
1894
|
+
formSettings={
|
|
1895
|
+
activeTabConfig.form
|
|
1896
|
+
}
|
|
1897
|
+
formType="update"
|
|
1898
|
+
paramValue={
|
|
1899
|
+
routeParams[urlParam] ||
|
|
1900
|
+
null
|
|
1901
|
+
}
|
|
1902
|
+
style={
|
|
1903
|
+
userProfile?.settings
|
|
1904
|
+
?.style || {}
|
|
1905
|
+
}
|
|
1906
|
+
updateForm={setFormData}
|
|
1907
|
+
type="inline"
|
|
1908
|
+
userProfile={userProfile}
|
|
1909
|
+
fetchTable={() => {
|
|
1910
|
+
handleReload();
|
|
1911
|
+
setInPlaceEditing(false);
|
|
1912
|
+
setEditingTabId(null);
|
|
1913
|
+
}}
|
|
1914
|
+
/>
|
|
1915
|
+
</div>
|
|
1916
|
+
) : (
|
|
1917
|
+
// Show the regular overview when not editing
|
|
1918
|
+
<div className={styles.gridtxt}>
|
|
1919
|
+
{activeTabConfig.sections.map(
|
|
1920
|
+
(section, sectionIndex) => (
|
|
1921
|
+
<React.Fragment
|
|
1922
|
+
key={`section-${activeTabConfig.id}-${sectionIndex}`}
|
|
1846
1923
|
>
|
|
1847
|
-
<
|
|
1848
|
-
{
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1924
|
+
<div
|
|
1925
|
+
className={
|
|
1926
|
+
styles.gridtxt__header
|
|
1927
|
+
}
|
|
1928
|
+
>
|
|
1929
|
+
<span>
|
|
1930
|
+
{section.title}
|
|
1931
|
+
</span>
|
|
1932
|
+
</div>
|
|
1933
|
+
{section.content &&
|
|
1934
|
+
section.content
|
|
1935
|
+
.length > 0 && (
|
|
1936
|
+
<ul
|
|
1937
|
+
className={
|
|
1938
|
+
styles.customer__overview
|
|
1939
|
+
}
|
|
1940
|
+
>
|
|
1941
|
+
{section.content.map(
|
|
1942
|
+
(
|
|
1943
|
+
item,
|
|
1944
|
+
itemKey
|
|
1945
|
+
) => (
|
|
1946
|
+
<li
|
|
1947
|
+
key={`li-${item.id}-${itemKey}`}
|
|
1948
|
+
className={
|
|
1949
|
+
item.size ===
|
|
1950
|
+
'full'
|
|
1951
|
+
? `${styles['fw-grid-item']} ${styles.notecolor}`
|
|
1952
|
+
: null
|
|
1953
|
+
}
|
|
1954
|
+
>
|
|
1955
|
+
<strong>
|
|
1956
|
+
{item.label
|
|
1957
|
+
? `${item.label}:`
|
|
1958
|
+
: null}
|
|
1959
|
+
</strong>{' '}
|
|
1960
|
+
{item
|
|
1961
|
+
.href
|
|
1962
|
+
?.url &&
|
|
1963
|
+
item
|
|
1964
|
+
.href
|
|
1965
|
+
?.key ? (
|
|
1966
|
+
<Link
|
|
1967
|
+
to={`${
|
|
1891
1968
|
item
|
|
1892
1969
|
.href
|
|
1893
|
-
.
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1970
|
+
.url
|
|
1971
|
+
}${
|
|
1972
|
+
data[
|
|
1973
|
+
item
|
|
1974
|
+
.href
|
|
1975
|
+
.key
|
|
1976
|
+
]
|
|
1977
|
+
}`}
|
|
1978
|
+
>
|
|
1979
|
+
{renderValue(
|
|
1980
|
+
item,
|
|
1981
|
+
data
|
|
1982
|
+
)}
|
|
1983
|
+
</Link>
|
|
1984
|
+
) : (
|
|
1985
|
+
<>
|
|
1986
|
+
{renderValue(
|
|
1987
|
+
item,
|
|
1988
|
+
data
|
|
1989
|
+
)}
|
|
1990
|
+
</>
|
|
1991
|
+
)}
|
|
1992
|
+
</li>
|
|
1993
|
+
)
|
|
1994
|
+
)}
|
|
1995
|
+
</ul>
|
|
1996
|
+
)}
|
|
1997
|
+
</React.Fragment>
|
|
1998
|
+
)
|
|
1999
|
+
)}
|
|
2000
|
+
</div>
|
|
2001
|
+
)}
|
|
1919
2002
|
|
|
1920
2003
|
{activeTabConfig.form && (
|
|
1921
2004
|
<div className={styles.polActions}>
|
|
1922
|
-
{/*
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2005
|
+
{/* Render the "Edit" button if activeTabConfig.form exists */}
|
|
2006
|
+
{!inPlaceEditing ? (
|
|
2007
|
+
<button
|
|
2008
|
+
className={styles.btn}
|
|
2009
|
+
onClick={() => {
|
|
2010
|
+
if (
|
|
2011
|
+
activeTabConfig.form
|
|
2012
|
+
.inPlaceEdit
|
|
2013
|
+
) {
|
|
2014
|
+
startInPlaceEditing(
|
|
2015
|
+
activeTabConfig.id
|
|
2016
|
+
);
|
|
2017
|
+
} else {
|
|
2018
|
+
modalOpen(
|
|
2019
|
+
'update',
|
|
2020
|
+
routeParams[
|
|
2021
|
+
urlParam
|
|
2022
|
+
]
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
}}
|
|
2026
|
+
>
|
|
2027
|
+
Edit
|
|
2028
|
+
</button>
|
|
2029
|
+
) : (
|
|
2030
|
+
editingTabId ===
|
|
2031
|
+
activeTabConfig.id && (
|
|
2032
|
+
<>
|
|
2033
|
+
<button
|
|
2034
|
+
className={
|
|
2035
|
+
styles.btn
|
|
2036
|
+
}
|
|
2037
|
+
onClick={
|
|
2038
|
+
cancelInPlaceEditing
|
|
2039
|
+
}
|
|
2040
|
+
style={{
|
|
2041
|
+
backgroundColor:
|
|
2042
|
+
'#fff0f0',
|
|
2043
|
+
color: '#d32f2f',
|
|
2044
|
+
border: '1px solid #ffcdd2',
|
|
2045
|
+
}}
|
|
2046
|
+
>
|
|
2047
|
+
Cancel
|
|
2048
|
+
</button>
|
|
2049
|
+
<button
|
|
2050
|
+
className={
|
|
2051
|
+
styles.btn
|
|
2052
|
+
}
|
|
2053
|
+
onClick={() => {
|
|
2054
|
+
// Find the form's save button and click it
|
|
2055
|
+
const saveButton =
|
|
2056
|
+
document.querySelector(
|
|
2057
|
+
`.inPlaceEdit .${styles.saveBtn}`
|
|
2058
|
+
);
|
|
2059
|
+
if (
|
|
2060
|
+
saveButton
|
|
2061
|
+
) {
|
|
2062
|
+
saveButton.click();
|
|
2063
|
+
} else {
|
|
2064
|
+
// Fallback to any button with data-type="save"
|
|
2065
|
+
const fallbackButton =
|
|
2066
|
+
document.querySelector(
|
|
2067
|
+
`.inPlaceEdit button[data-type="save"]`
|
|
2068
|
+
);
|
|
2069
|
+
if (
|
|
2070
|
+
fallbackButton
|
|
2071
|
+
) {
|
|
2072
|
+
fallbackButton.click();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// Reset the editing state
|
|
2077
|
+
setInPlaceEditing(
|
|
2078
|
+
false
|
|
2079
|
+
);
|
|
2080
|
+
setEditingTabId(
|
|
2081
|
+
null
|
|
2082
|
+
);
|
|
2083
|
+
|
|
2084
|
+
// Reload data
|
|
2085
|
+
handleReload();
|
|
2086
|
+
}}
|
|
2087
|
+
>
|
|
2088
|
+
Save
|
|
2089
|
+
</button>
|
|
2090
|
+
</>
|
|
2091
|
+
)
|
|
2092
|
+
)}
|
|
1934
2093
|
|
|
1935
2094
|
{/* Check if sections contain "ocrTemplate" type and render the "Save" button under the same polActions */}
|
|
1936
2095
|
{activeTabConfig.sections &&
|
|
@@ -2545,6 +2704,8 @@ function GenericDetail({
|
|
|
2545
2704
|
const [formType, setFormType] = useState('');
|
|
2546
2705
|
const [formId, setFormId] = useState(0);
|
|
2547
2706
|
const [modalShow, setModalShow] = useState(false);
|
|
2707
|
+
const [inPlaceEditing, setInPlaceEditing] = useState(false);
|
|
2708
|
+
const [editingTabId, setEditingTabId] = useState(null);
|
|
2548
2709
|
|
|
2549
2710
|
/** Modal Functions */
|
|
2550
2711
|
const modalOpen = (formType, formId) => {
|
|
@@ -2557,6 +2718,19 @@ function GenericDetail({
|
|
|
2557
2718
|
setModalShow(false);
|
|
2558
2719
|
};
|
|
2559
2720
|
|
|
2721
|
+
/** In-place Editing Functions */
|
|
2722
|
+
const startInPlaceEditing = (tabId) => {
|
|
2723
|
+
setInPlaceEditing(true);
|
|
2724
|
+
setEditingTabId(tabId);
|
|
2725
|
+
setFormType('update');
|
|
2726
|
+
setFormId(routeParams[urlParam]);
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
const cancelInPlaceEditing = () => {
|
|
2730
|
+
setInPlaceEditing(false);
|
|
2731
|
+
setEditingTabId(null);
|
|
2732
|
+
};
|
|
2733
|
+
|
|
2560
2734
|
return (
|
|
2561
2735
|
<>
|
|
2562
2736
|
<div className={styles.grid}>
|
|
@@ -285,76 +285,129 @@
|
|
|
285
285
|
|
|
286
286
|
.dropzone__files {
|
|
287
287
|
list-style: none;
|
|
288
|
-
padding: 0;
|
|
288
|
+
padding: 16px 0;
|
|
289
289
|
margin: 0;
|
|
290
290
|
box-sizing: border-box;
|
|
291
|
+
display: grid;
|
|
292
|
+
grid-template-columns: repeat(2, 1fr); /* 2 columns by default */
|
|
293
|
+
gap: 16px;
|
|
294
|
+
|
|
295
|
+
/* Responsive grid layout */
|
|
296
|
+
@media (max-width: 768px) {
|
|
297
|
+
grid-template-columns: repeat(1, 1fr); /* 1 column on small screens */
|
|
298
|
+
}
|
|
291
299
|
|
|
292
300
|
li {
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
justify-content: space-between;
|
|
295
304
|
background: rgba(var(--primary-rgb), 0.05);
|
|
296
305
|
color: var(--primary-color);
|
|
297
|
-
border-radius:
|
|
298
|
-
transition:
|
|
299
|
-
outline: none;
|
|
306
|
+
border-radius: 6px;
|
|
307
|
+
transition: all 0.2s ease;
|
|
300
308
|
cursor: pointer;
|
|
301
|
-
|
|
302
|
-
padding: 0.5rem;
|
|
309
|
+
padding: 12px 16px;
|
|
303
310
|
box-sizing: border-box;
|
|
304
311
|
position: relative;
|
|
305
312
|
border: 1px solid rgba(var(--primary-rgb), 0.15);
|
|
306
|
-
line-height: 1;
|
|
307
|
-
font-size: 0.
|
|
308
|
-
|
|
313
|
+
line-height: 1.4;
|
|
314
|
+
font-size: 0.9rem;
|
|
315
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
316
|
+
|
|
317
|
+
&:hover {
|
|
318
|
+
transform: translateY(-2px);
|
|
319
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
320
|
+
background: rgba(var(--primary-rgb), 0.08);
|
|
321
|
+
}
|
|
309
322
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
323
|
+
.file-info {
|
|
324
|
+
display: flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
flex: 1;
|
|
327
|
+
overflow: hidden;
|
|
328
|
+
|
|
329
|
+
svg {
|
|
330
|
+
color: var(--secondary-color);
|
|
331
|
+
flex-shrink: 0;
|
|
332
|
+
margin-right: 12px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.filename {
|
|
336
|
+
white-space: nowrap;
|
|
337
|
+
overflow: hidden;
|
|
338
|
+
text-overflow: ellipsis;
|
|
339
|
+
}
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
button {
|
|
321
|
-
position: relative;
|
|
322
|
-
float: right;
|
|
323
343
|
background: none;
|
|
324
344
|
outline: none;
|
|
325
345
|
border: none;
|
|
326
|
-
|
|
346
|
+
padding: 6px;
|
|
347
|
+
margin-left: 8px;
|
|
348
|
+
border-radius: 4px;
|
|
327
349
|
cursor: pointer;
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
transition: background-color 0.2s ease;
|
|
354
|
+
|
|
355
|
+
&:hover {
|
|
356
|
+
background-color: rgba(var(--primary-rgb), 0.1);
|
|
357
|
+
}
|
|
328
358
|
|
|
329
359
|
svg {
|
|
330
|
-
font-size: 1em;
|
|
331
360
|
color: var(--primary-color);
|
|
332
|
-
line-height: 1;
|
|
333
361
|
}
|
|
334
362
|
}
|
|
335
363
|
}
|
|
336
|
-
|
|
337
|
-
li:hover {
|
|
338
|
-
background: rgba(var(--secondary-color-rgb, 0.65));
|
|
339
|
-
}
|
|
340
364
|
}
|
|
341
365
|
|
|
342
366
|
.sortlist {
|
|
343
|
-
display:
|
|
344
|
-
|
|
345
|
-
gap:
|
|
346
|
-
padding: 0;
|
|
367
|
+
display: grid;
|
|
368
|
+
grid-template-columns: repeat(4, 1fr); /* 4 columns by default */
|
|
369
|
+
gap: 16px;
|
|
370
|
+
padding: 16px 0;
|
|
347
371
|
list-style: none;
|
|
348
372
|
|
|
373
|
+
/* Responsive grid layout */
|
|
374
|
+
@media (max-width: 1200px) {
|
|
375
|
+
grid-template-columns: repeat(3, 1fr); /* 3 columns on medium screens */
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@media (max-width: 768px) {
|
|
379
|
+
grid-template-columns: repeat(2, 1fr); /* 2 columns on small screens */
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@media (max-width: 480px) {
|
|
383
|
+
grid-template-columns: repeat(
|
|
384
|
+
1,
|
|
385
|
+
1fr
|
|
386
|
+
); /* 1 column on very small screens */
|
|
387
|
+
}
|
|
388
|
+
|
|
349
389
|
li {
|
|
350
390
|
position: relative;
|
|
391
|
+
aspect-ratio: 4/3;
|
|
392
|
+
border-radius: 8px;
|
|
393
|
+
overflow: hidden;
|
|
394
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
395
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
396
|
+
cursor: pointer;
|
|
397
|
+
|
|
398
|
+
&:hover {
|
|
399
|
+
transform: translateY(-2px);
|
|
400
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
401
|
+
}
|
|
351
402
|
|
|
352
403
|
.sortimg {
|
|
353
|
-
width:
|
|
354
|
-
height:
|
|
404
|
+
width: 100%;
|
|
405
|
+
height: 100%;
|
|
355
406
|
border-radius: 8px;
|
|
356
407
|
overflow: hidden;
|
|
357
408
|
position: relative;
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-direction: column;
|
|
358
411
|
|
|
359
412
|
img {
|
|
360
413
|
width: 100%;
|
|
@@ -376,6 +429,21 @@
|
|
|
376
429
|
background: rgba(255, 255, 255, 1);
|
|
377
430
|
}
|
|
378
431
|
}
|
|
432
|
+
|
|
433
|
+
.filename {
|
|
434
|
+
position: absolute;
|
|
435
|
+
bottom: 0;
|
|
436
|
+
left: 0;
|
|
437
|
+
right: 0;
|
|
438
|
+
padding: 8px;
|
|
439
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
440
|
+
color: white;
|
|
441
|
+
font-size: 0.8rem;
|
|
442
|
+
white-space: nowrap;
|
|
443
|
+
overflow: hidden;
|
|
444
|
+
text-overflow: ellipsis;
|
|
445
|
+
z-index: 1;
|
|
446
|
+
}
|
|
379
447
|
}
|
|
380
448
|
}
|
|
381
449
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.header {
|
|
2
2
|
background: var(--primary-color);
|
|
3
3
|
color: var(--primary-color);
|
|
4
|
-
position:
|
|
4
|
+
position: sticky;
|
|
5
5
|
top: 0;
|
|
6
6
|
width: 100%;
|
|
7
7
|
z-index: 995;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
.headerAlternate {
|
|
12
12
|
background: var(--bg-color);
|
|
13
13
|
color: var(--paragraph-color);
|
|
14
|
-
position:
|
|
14
|
+
position: sticky;
|
|
15
15
|
top: 0;
|
|
16
16
|
width: 100%;
|
|
17
17
|
z-index: 995;
|
|
@@ -596,7 +596,22 @@
|
|
|
596
596
|
|
|
597
597
|
/* DataGrid top container and search container styles */
|
|
598
598
|
.dataGridTopContainer {
|
|
599
|
+
display: flex;
|
|
600
|
+
justify-content: space-between;
|
|
601
|
+
align-items: center;
|
|
599
602
|
margin-bottom: 8px;
|
|
603
|
+
padding: 0 5px;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.dataGridTopLeftContainer {
|
|
607
|
+
flex: 1;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.dataGridTopRightContainer {
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
justify-content: flex-end;
|
|
614
|
+
padding-right: 10px;
|
|
600
615
|
}
|
|
601
616
|
|
|
602
617
|
.searchContainer {
|
|
@@ -651,12 +666,44 @@
|
|
|
651
666
|
justify-content: flex-end;
|
|
652
667
|
}
|
|
653
668
|
|
|
669
|
+
/* Auto-refresh controls */
|
|
670
|
+
.autoRefreshContainer {
|
|
671
|
+
display: inline-flex;
|
|
672
|
+
align-items: center;
|
|
673
|
+
padding: 0 15px;
|
|
674
|
+
height: 38px;
|
|
675
|
+
background-color: #f9fafb;
|
|
676
|
+
border: 1px solid #d1d5db;
|
|
677
|
+
border-radius: var(--br, 4px);
|
|
678
|
+
margin-left: 10px;
|
|
679
|
+
margin-bottom: 0;
|
|
680
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.autoRefreshLabel {
|
|
684
|
+
display: flex;
|
|
685
|
+
align-items: center;
|
|
686
|
+
gap: 8px;
|
|
687
|
+
font-size: 0.9rem;
|
|
688
|
+
color: var(--secondary-color, #1f2937);
|
|
689
|
+
white-space: nowrap;
|
|
690
|
+
cursor: pointer;
|
|
691
|
+
font-weight: normal;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.autoRefreshCheckbox {
|
|
695
|
+
margin: 0;
|
|
696
|
+
cursor: pointer;
|
|
697
|
+
width: 16px;
|
|
698
|
+
height: 16px;
|
|
699
|
+
}
|
|
700
|
+
|
|
654
701
|
/* Container for when only the action button is shown (search disabled) */
|
|
655
702
|
.actionButtonsContainer {
|
|
656
703
|
display: flex;
|
|
657
704
|
justify-content: flex-end;
|
|
658
705
|
width: 100%;
|
|
659
|
-
margin-bottom:
|
|
706
|
+
margin-bottom: 0; /* No bottom margin to align with auto-refresh */
|
|
660
707
|
}
|
|
661
708
|
|
|
662
709
|
.actionButtons {
|
|
@@ -318,6 +318,7 @@ input[type='file'] {
|
|
|
318
318
|
bottom: 15px;
|
|
319
319
|
right: 20px;
|
|
320
320
|
width: auto;
|
|
321
|
+
z-index: 100;
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
.polActions button {
|
|
@@ -327,6 +328,53 @@ input[type='file'] {
|
|
|
327
328
|
margin: 0 0.15em;
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
/* For in-place editing mode */
|
|
332
|
+
:global(.inPlaceEdit) {
|
|
333
|
+
background-color: rgba(245, 247, 250, 0.7);
|
|
334
|
+
border-radius: 5px;
|
|
335
|
+
padding: 10px;
|
|
336
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
|
337
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
338
|
+
margin-bottom: 20px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
:global(.inPlaceEdit) [class*='gridtxt__header'] {
|
|
342
|
+
background-color: rgba(0, 0, 0, 0.03);
|
|
343
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
344
|
+
padding: 10px 15px;
|
|
345
|
+
margin-bottom: 15px;
|
|
346
|
+
border-radius: 4px 4px 0 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
:global(.inPlaceEdit) .polActions {
|
|
350
|
+
position: static;
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: flex-end;
|
|
353
|
+
align-items: center;
|
|
354
|
+
margin-top: 15px;
|
|
355
|
+
margin-bottom: 10px;
|
|
356
|
+
padding: 0 15px;
|
|
357
|
+
width: 100%;
|
|
358
|
+
gap: 10px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
:global(.inPlaceEdit) .polActions button {
|
|
362
|
+
margin: 0;
|
|
363
|
+
min-width: 120px;
|
|
364
|
+
border-radius: 4px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Style for cancel button hover state */
|
|
368
|
+
:global(.inPlaceEdit) button[style*='color: #d32f2f']:hover {
|
|
369
|
+
background-color: #ffebee !important;
|
|
370
|
+
border-color: #ef9a9a !important;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Hide the save button at the bottom of the form when in in-place edit mode */
|
|
374
|
+
:global(.inPlaceEdit) .formcontainer > .polActions {
|
|
375
|
+
display: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
330
378
|
.buttonContainer {
|
|
331
379
|
display: flex;
|
|
332
380
|
justify-content: center;
|
|
@@ -618,6 +618,9 @@
|
|
|
618
618
|
-webkit-overflow-scrolling: touch;
|
|
619
619
|
scrollbar-width: none; /* Firefox */
|
|
620
620
|
-ms-overflow-style: none; /* IE and Edge */
|
|
621
|
+
position: sticky;
|
|
622
|
+
top: 0;
|
|
623
|
+
z-index: 995;
|
|
621
624
|
|
|
622
625
|
/* Hide scrollbar for Chrome/Safari/Opera */
|
|
623
626
|
&::-webkit-scrollbar {
|
|
@@ -805,6 +808,12 @@
|
|
|
805
808
|
|
|
806
809
|
.content {
|
|
807
810
|
margin-left: 0;
|
|
811
|
+
|
|
812
|
+
.content-header {
|
|
813
|
+
position: sticky;
|
|
814
|
+
top: 0;
|
|
815
|
+
z-index: 995;
|
|
816
|
+
}
|
|
808
817
|
}
|
|
809
818
|
|
|
810
819
|
.popup-content {
|