@visns-studio/visns-components 6.3.6 → 6.3.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/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.6",
94
+ "version": "6.3.7",
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';
@@ -959,6 +969,80 @@ const DataGrid = forwardRef(
959
969
  [ajaxSetting]
960
970
  );
961
971
 
972
+ /**
973
+ * Opt-in non-colour row states (`ajaxSetting.rowStyles`).
974
+ *
975
+ * rowStyles: [
976
+ * { id: 'jigging_status', value: 1, style: 'complete' }
977
+ * ]
978
+ *
979
+ * Each rule names a FIELD STATE, not a look: `style` is a token the
980
+ * package maps to a class in DataGrid.module.scss, so a project says
981
+ * what a row means and the library decides how that reads. Today the
982
+ * only token is `complete` (line-through plus a muted ink).
983
+ *
984
+ * Deliberately different from `rowColours` in two ways:
985
+ *
986
+ * - EVERY matching rule applies, where rowColours takes the first
987
+ * match only. A row is frequently more than one thing at once (a
988
+ * finished item that is also on a T-Bar), and with first-match the
989
+ * completion state would be swallowed by whichever tint happened to
990
+ * be listed above it.
991
+ * - It carries no colour. That is the point: a pale tint is the first
992
+ * thing to disappear on a plant-floor tablet in daylight, so a state
993
+ * that matters has to be legible without one. Colour, where a rule
994
+ * also sets one, stays the redundant second channel.
995
+ *
996
+ * `operator` follows the same small vocabulary as `readOnlyWhen`:
997
+ * eq (default), neq, notEmpty.
998
+ */
999
+ const getRowClassName = useCallback(
1000
+ ({ data }) => {
1001
+ const rules = ajaxSetting?.rowStyles;
1002
+
1003
+ if (!Array.isArray(rules) || rules.length === 0 || !data) {
1004
+ return '';
1005
+ }
1006
+
1007
+ const matches = (rule) => {
1008
+ const value = data[rule.id];
1009
+
1010
+ switch (rule.operator) {
1011
+ case 'neq':
1012
+ return value != rule.value;
1013
+ case 'notEmpty':
1014
+ return Array.isArray(value)
1015
+ ? value.length > 0
1016
+ : value != null && value !== '';
1017
+ case 'eq':
1018
+ default:
1019
+ return value == rule.value;
1020
+ }
1021
+ };
1022
+
1023
+ return rules
1024
+ .filter(matches)
1025
+ .map((rule) => ROW_STYLE_CLASSES[rule.style])
1026
+ .filter(Boolean)
1027
+ .join(' ');
1028
+ },
1029
+ [ajaxSetting]
1030
+ );
1031
+
1032
+ /**
1033
+ * The grid's own row class plus whatever `rowStyles` adds. Passed as a
1034
+ * function rather than the bare string the grid used to get: the grid
1035
+ * accepts either, and `vs-datagrid--row` (the hover rule) has to stay
1036
+ * on every row regardless.
1037
+ */
1038
+ const rowClassName = useCallback(
1039
+ (rowProps) =>
1040
+ ['vs-datagrid--row', getRowClassName(rowProps)]
1041
+ .filter(Boolean)
1042
+ .join(' '),
1043
+ [getRowClassName]
1044
+ );
1045
+
962
1046
  const handleDownload = async (d) => {
963
1047
  const toastId = toast.loading('Starting download please wait...');
964
1048
 
@@ -5205,7 +5289,7 @@ const DataGrid = forwardRef(
5205
5289
  pagination
5206
5290
  renderRowContextMenu={renderRowContextMenu}
5207
5291
  renderColumnContextMenu={renderColumnContextMenu}
5208
- rowClassName="vs-datagrid--row"
5292
+ rowClassName={rowClassName}
5209
5293
  rowHeight={isTabletMode ? 52 : null}
5210
5294
  rowStyle={getRowStyle}
5211
5295
  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
- opacity: isStageActive ? 1 : 0.5,
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
+ }