@visns-studio/visns-components 6.3.6 → 6.3.8
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.3.
|
|
94
|
+
"version": "6.3.8",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -78,6 +78,16 @@ import {
|
|
|
78
78
|
} from 'lucide-react';
|
|
79
79
|
import styles from './styles/DataGrid.module.scss';
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* `ajaxSetting.rowStyles[].style` tokens → the module class that draws them.
|
|
83
|
+
* Kept as a map rather than `styles['row' + capitalise(token)]` so an unknown
|
|
84
|
+
* token in a config is simply ignored instead of resolving to `undefined` and
|
|
85
|
+
* putting the literal string "undefined" in the row's class list.
|
|
86
|
+
*/
|
|
87
|
+
const ROW_STYLE_CLASSES = {
|
|
88
|
+
complete: styles.rowComplete,
|
|
89
|
+
};
|
|
90
|
+
|
|
81
91
|
import '@visns-studio/visns-datagrid-enterprise/index.css';
|
|
82
92
|
|
|
83
93
|
import CustomFetch from './Fetch';
|
|
@@ -649,11 +659,11 @@ const DataGrid = forwardRef(
|
|
|
649
659
|
}
|
|
650
660
|
|
|
651
661
|
// Opt-in grouped display (ajaxSetting.treeGroupBy {key,
|
|
652
|
-
// column, parentFields,
|
|
653
|
-
// defaultCollapsed, icons}): consecutive rows
|
|
654
|
-
// non-empty `key` value get a synthetic summary
|
|
655
|
-
// row inserted above them. `parentFields` are lifted
|
|
656
|
-
// the first row onto the parent and blanked on the
|
|
662
|
+
// column, parentFields, parentFieldAliases, countLabel,
|
|
663
|
+
// collapsible, defaultCollapsed, icons}): consecutive rows
|
|
664
|
+
// sharing a non-empty `key` value get a synthetic summary
|
|
665
|
+
// parent row inserted above them. `parentFields` are lifted
|
|
666
|
+
// from the first row onto the parent and blanked on the
|
|
657
667
|
// children, so shared details display once; children get
|
|
658
668
|
// an "↳" prefix in the display column. Single rows stay
|
|
659
669
|
// plain — unlike the grid's own groupBy, which forces a
|
|
@@ -668,6 +678,7 @@ const DataGrid = forwardRef(
|
|
|
668
678
|
key: treeKey,
|
|
669
679
|
column: treeDisplayColumn,
|
|
670
680
|
parentFields = [],
|
|
681
|
+
parentFieldAliases = {},
|
|
671
682
|
countLabel,
|
|
672
683
|
collapsible,
|
|
673
684
|
defaultCollapsed,
|
|
@@ -712,6 +723,35 @@ const DataGrid = forwardRef(
|
|
|
712
723
|
});
|
|
713
724
|
});
|
|
714
725
|
|
|
726
|
+
// `parentFieldAliases` ({columnField:
|
|
727
|
+
// sourceField}): the parent shows a DIFFERENT
|
|
728
|
+
// field in a column than its children do, so
|
|
729
|
+
// one column can carry the per-member value on
|
|
730
|
+
// members and the group's summary on the
|
|
731
|
+
// parent instead of spending a second column
|
|
732
|
+
// on the summary alone.
|
|
733
|
+
//
|
|
734
|
+
// Unlike `parentFields` this deliberately does
|
|
735
|
+
// NOT blank the column on the children — the
|
|
736
|
+
// members' own values are the reason the
|
|
737
|
+
// column exists. (Listing the same field in
|
|
738
|
+
// both would blank it on the children and
|
|
739
|
+
// defeat that, so pick one.)
|
|
740
|
+
//
|
|
741
|
+
// Distinct from `countLabel` too, which
|
|
742
|
+
// templates a summary from the child COUNT and
|
|
743
|
+
// so can only produce one fixed wording. An
|
|
744
|
+
// alias reads a field the backend computed,
|
|
745
|
+
// which is what it takes for the summary to
|
|
746
|
+
// vary with the data ("2 Headers" for one kind
|
|
747
|
+
// of group, "2 Stations" for another).
|
|
748
|
+
Object.entries(parentFieldAliases).forEach(
|
|
749
|
+
([columnField, sourceField]) => {
|
|
750
|
+
parent[columnField] =
|
|
751
|
+
children[0]?.[sourceField];
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
|
|
715
755
|
// Mark children for styling (getRowStyle) and
|
|
716
756
|
// prefix their display value so nesting reads
|
|
717
757
|
children.forEach((child) => {
|
|
@@ -959,6 +999,80 @@ const DataGrid = forwardRef(
|
|
|
959
999
|
[ajaxSetting]
|
|
960
1000
|
);
|
|
961
1001
|
|
|
1002
|
+
/**
|
|
1003
|
+
* Opt-in non-colour row states (`ajaxSetting.rowStyles`).
|
|
1004
|
+
*
|
|
1005
|
+
* rowStyles: [
|
|
1006
|
+
* { id: 'jigging_status', value: 1, style: 'complete' }
|
|
1007
|
+
* ]
|
|
1008
|
+
*
|
|
1009
|
+
* Each rule names a FIELD STATE, not a look: `style` is a token the
|
|
1010
|
+
* package maps to a class in DataGrid.module.scss, so a project says
|
|
1011
|
+
* what a row means and the library decides how that reads. Today the
|
|
1012
|
+
* only token is `complete` (line-through plus a muted ink).
|
|
1013
|
+
*
|
|
1014
|
+
* Deliberately different from `rowColours` in two ways:
|
|
1015
|
+
*
|
|
1016
|
+
* - EVERY matching rule applies, where rowColours takes the first
|
|
1017
|
+
* match only. A row is frequently more than one thing at once (a
|
|
1018
|
+
* finished item that is also on a T-Bar), and with first-match the
|
|
1019
|
+
* completion state would be swallowed by whichever tint happened to
|
|
1020
|
+
* be listed above it.
|
|
1021
|
+
* - It carries no colour. That is the point: a pale tint is the first
|
|
1022
|
+
* thing to disappear on a plant-floor tablet in daylight, so a state
|
|
1023
|
+
* that matters has to be legible without one. Colour, where a rule
|
|
1024
|
+
* also sets one, stays the redundant second channel.
|
|
1025
|
+
*
|
|
1026
|
+
* `operator` follows the same small vocabulary as `readOnlyWhen`:
|
|
1027
|
+
* eq (default), neq, notEmpty.
|
|
1028
|
+
*/
|
|
1029
|
+
const getRowClassName = useCallback(
|
|
1030
|
+
({ data }) => {
|
|
1031
|
+
const rules = ajaxSetting?.rowStyles;
|
|
1032
|
+
|
|
1033
|
+
if (!Array.isArray(rules) || rules.length === 0 || !data) {
|
|
1034
|
+
return '';
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const matches = (rule) => {
|
|
1038
|
+
const value = data[rule.id];
|
|
1039
|
+
|
|
1040
|
+
switch (rule.operator) {
|
|
1041
|
+
case 'neq':
|
|
1042
|
+
return value != rule.value;
|
|
1043
|
+
case 'notEmpty':
|
|
1044
|
+
return Array.isArray(value)
|
|
1045
|
+
? value.length > 0
|
|
1046
|
+
: value != null && value !== '';
|
|
1047
|
+
case 'eq':
|
|
1048
|
+
default:
|
|
1049
|
+
return value == rule.value;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
return rules
|
|
1054
|
+
.filter(matches)
|
|
1055
|
+
.map((rule) => ROW_STYLE_CLASSES[rule.style])
|
|
1056
|
+
.filter(Boolean)
|
|
1057
|
+
.join(' ');
|
|
1058
|
+
},
|
|
1059
|
+
[ajaxSetting]
|
|
1060
|
+
);
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* The grid's own row class plus whatever `rowStyles` adds. Passed as a
|
|
1064
|
+
* function rather than the bare string the grid used to get: the grid
|
|
1065
|
+
* accepts either, and `vs-datagrid--row` (the hover rule) has to stay
|
|
1066
|
+
* on every row regardless.
|
|
1067
|
+
*/
|
|
1068
|
+
const rowClassName = useCallback(
|
|
1069
|
+
(rowProps) =>
|
|
1070
|
+
['vs-datagrid--row', getRowClassName(rowProps)]
|
|
1071
|
+
.filter(Boolean)
|
|
1072
|
+
.join(' '),
|
|
1073
|
+
[getRowClassName]
|
|
1074
|
+
);
|
|
1075
|
+
|
|
962
1076
|
const handleDownload = async (d) => {
|
|
963
1077
|
const toastId = toast.loading('Starting download please wait...');
|
|
964
1078
|
|
|
@@ -5205,7 +5319,7 @@ const DataGrid = forwardRef(
|
|
|
5205
5319
|
pagination
|
|
5206
5320
|
renderRowContextMenu={renderRowContextMenu}
|
|
5207
5321
|
renderColumnContextMenu={renderColumnContextMenu}
|
|
5208
|
-
rowClassName=
|
|
5322
|
+
rowClassName={rowClassName}
|
|
5209
5323
|
rowHeight={isTabletMode ? 52 : null}
|
|
5210
5324
|
rowStyle={getRowStyle}
|
|
5211
5325
|
selected={selected}
|
|
@@ -40,6 +40,55 @@ const buildToggleUrl = (urlTemplate, stageItem, rowData) => {
|
|
|
40
40
|
return url;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate an opt-in stageConfig rule ({key, operator, value}) against one
|
|
45
|
+
* entry of a stage list. Absent rule -> false, so every setting that uses this
|
|
46
|
+
* is inert until a config asks for it.
|
|
47
|
+
*
|
|
48
|
+
* Operators: eq (default), neq, gte, gt, lte, lt, in. The comparison operators
|
|
49
|
+
* coerce to Number, because the values these rules match on arrive as strings
|
|
50
|
+
* from some payloads and as numbers from others, and a stage ordinal compared
|
|
51
|
+
* as a string sorts "10" below "2".
|
|
52
|
+
*/
|
|
53
|
+
const matchesStageRule = (rule, stageItem) => {
|
|
54
|
+
if (!rule || !rule.key || !stageItem) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const value = stageItem[rule.key];
|
|
59
|
+
|
|
60
|
+
if (value === null || value === undefined) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (rule.operator === 'in') {
|
|
65
|
+
return (
|
|
66
|
+
Array.isArray(rule.value) &&
|
|
67
|
+
rule.value.some((candidate) => candidate == value)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const left = Number(value);
|
|
72
|
+
const right = Number(rule.value);
|
|
73
|
+
const comparable = !Number.isNaN(left) && !Number.isNaN(right);
|
|
74
|
+
|
|
75
|
+
switch (rule.operator) {
|
|
76
|
+
case 'neq':
|
|
77
|
+
return value != rule.value;
|
|
78
|
+
case 'gte':
|
|
79
|
+
return comparable && left >= right;
|
|
80
|
+
case 'gt':
|
|
81
|
+
return comparable && left > right;
|
|
82
|
+
case 'lte':
|
|
83
|
+
return comparable && left <= right;
|
|
84
|
+
case 'lt':
|
|
85
|
+
return comparable && left < right;
|
|
86
|
+
case 'eq':
|
|
87
|
+
default:
|
|
88
|
+
return value == rule.value;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
43
92
|
const getStageStatus = (stageItem, toggleConfig) => {
|
|
44
93
|
if (!toggleConfig || !toggleConfig.statusField) return null;
|
|
45
94
|
|
|
@@ -2895,6 +2944,30 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate, onUpda
|
|
|
2895
2944
|
});
|
|
2896
2945
|
}
|
|
2897
2946
|
|
|
2947
|
+
// `completeWhen` (opt-in): the rule that says this
|
|
2948
|
+
// entry is DONE as far as the grid showing it is
|
|
2949
|
+
// concerned, drawn as a rule through the label plus a
|
|
2950
|
+
// step back in weight.
|
|
2951
|
+
//
|
|
2952
|
+
// It lives in the view config rather than in the data
|
|
2953
|
+
// because "complete" is a statement about the VIEWER,
|
|
2954
|
+
// not about the entry: the same stage list is served
|
|
2955
|
+
// unchanged to every grid, and a member sitting at
|
|
2956
|
+
// Despatch is finished business to the Despatch list
|
|
2957
|
+
// while still being work in progress to Pickling. The
|
|
2958
|
+
// grid is the only party that knows which it is.
|
|
2959
|
+
//
|
|
2960
|
+
// Not a colour, on purpose: the pill's fill already
|
|
2961
|
+
// encodes the stage, and a second hue on a tablet in
|
|
2962
|
+
// daylight is the signal least likely to survive the
|
|
2963
|
+
// reading conditions. The rule through the label is
|
|
2964
|
+
// legible in glare and in greyscale; the fill stays as
|
|
2965
|
+
// it was, so nothing is lost by adding this.
|
|
2966
|
+
const isStageComplete = matchesStageRule(
|
|
2967
|
+
column.stageConfig.completeWhen,
|
|
2968
|
+
stageItem
|
|
2969
|
+
);
|
|
2970
|
+
|
|
2898
2971
|
// Apply visual feedback for inactive stages
|
|
2899
2972
|
const stageStyle = {
|
|
2900
2973
|
width: useFullLabel ? 'auto' : '20px',
|
|
@@ -2911,7 +2984,21 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate, onUpda
|
|
|
2911
2984
|
fontSize: '11px',
|
|
2912
2985
|
fontWeight: 'bold',
|
|
2913
2986
|
cursor: isToggleable ? 'pointer' : 'default',
|
|
2914
|
-
|
|
2987
|
+
...(isStageComplete
|
|
2988
|
+
? {
|
|
2989
|
+
textDecoration: 'line-through',
|
|
2990
|
+
textDecorationThickness:
|
|
2991
|
+
'var(--grid-stage-complete-thickness, 2px)',
|
|
2992
|
+
}
|
|
2993
|
+
: {}),
|
|
2994
|
+
// An inactive toggle already mutes to 0.5; a
|
|
2995
|
+
// complete entry steps back a little less, so the
|
|
2996
|
+
// two states stay tellable apart when both apply
|
|
2997
|
+
opacity: !isStageActive
|
|
2998
|
+
? 0.5
|
|
2999
|
+
: isStageComplete
|
|
3000
|
+
? 'var(--grid-stage-complete-opacity, 0.65)'
|
|
3001
|
+
: 1,
|
|
2915
3002
|
transition: 'opacity 0.2s ease, transform 0.1s ease',
|
|
2916
3003
|
border: isToggleable ? '1px solid rgba(255,255,255,0.3)' : 'none',
|
|
2917
3004
|
};
|
|
@@ -1354,3 +1354,51 @@
|
|
|
1354
1354
|
}
|
|
1355
1355
|
|
|
1356
1356
|
|
|
1357
|
+
|
|
1358
|
+
/* =========================================================================
|
|
1359
|
+
Row states (`ajaxSetting.rowStyles`)
|
|
1360
|
+
|
|
1361
|
+
A row tint is the first thing to go on a plant-floor tablet: pale fills
|
|
1362
|
+
are chosen to sit under black text, which is exactly what makes them
|
|
1363
|
+
vanish under glare or a low-angle view. The rows that carry these states
|
|
1364
|
+
are the ones a crew scans a list FOR ("which of these headers is already
|
|
1365
|
+
done?"), so the state has to survive the conditions the list is read in.
|
|
1366
|
+
|
|
1367
|
+
The treatment is therefore a rule through the text plus a step back in
|
|
1368
|
+
ink weight — two signals, neither of them hue. Any `rowColours` tint on
|
|
1369
|
+
the same row is untouched and becomes the redundant third.
|
|
1370
|
+
|
|
1371
|
+
Strike and ink are applied to the CELLS, not the row: the grid lays its
|
|
1372
|
+
cells out absolutely, so `text-decoration` set on the row element has no
|
|
1373
|
+
in-flow descendants to inherit it and would draw nothing.
|
|
1374
|
+
|
|
1375
|
+
Density hooks, per the `--nav-*` / `--grid-legend-*` convention above —
|
|
1376
|
+
every declaration is `var(--hook, <the value it always had>)`, so an unset
|
|
1377
|
+
hook is a no-op and a project retunes from `:root`:
|
|
1378
|
+
|
|
1379
|
+
--grid-row-complete-ink struck-through text colour.
|
|
1380
|
+
default: var(--muted-color, #6b7688)
|
|
1381
|
+
--grid-row-complete-thickness weight of the rule through the text.
|
|
1382
|
+
default: 2px — heavier than the browser
|
|
1383
|
+
default, which is hairline at this size.
|
|
1384
|
+
--grid-row-complete-opacity applies to non-text cell content (status
|
|
1385
|
+
pills, icons), which a rule cannot cross.
|
|
1386
|
+
default: 0.7
|
|
1387
|
+
========================================================================= */
|
|
1388
|
+
|
|
1389
|
+
.rowComplete {
|
|
1390
|
+
:global(.InovuaReactDataGrid__cell) {
|
|
1391
|
+
color: var(--grid-row-complete-ink, var(--muted-color, #6b7688));
|
|
1392
|
+
text-decoration: line-through;
|
|
1393
|
+
text-decoration-thickness: var(--grid-row-complete-thickness, 2px);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Cell content that is drawn rather than set — the action buttons, the
|
|
1397
|
+
// stage pills, the boolean ticks — takes the muting alone, since a rule
|
|
1398
|
+
// through the text cannot cross it.
|
|
1399
|
+
:global(.InovuaReactDataGrid__cell) svg,
|
|
1400
|
+
:global(.InovuaReactDataGrid__cell) img,
|
|
1401
|
+
:global(.InovuaReactDataGrid__cell) button {
|
|
1402
|
+
opacity: var(--grid-row-complete-opacity, 0.7);
|
|
1403
|
+
}
|
|
1404
|
+
}
|