ebta-workflow-prod 4.0.16 → 4.0.17

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ebta-workflow-prod",
3
3
  "private": false,
4
- "version": "4.0.16",
4
+ "version": "4.0.17",
5
5
  "description": "",
6
6
  "license": "UNLICENSED",
7
7
  "author": "",
@@ -8,6 +8,8 @@ import ListItemButton from '@vertigis/web/ui/ListItemButton';
8
8
  import ListItemIcon from '@vertigis/web/ui/ListItemIcon';
9
9
  import ListItemText from '@vertigis/web/ui/ListItemText';
10
10
  import Collapse from '@vertigis/web/ui/Collapse';
11
+ import Menu from '@vertigis/web/ui/Menu';
12
+ import MenuItem from '@vertigis/web/ui/MenuItem';
11
13
  import FolderOpenIcon from '@mui/icons-material/FolderOpen';
12
14
  import ExpandLess from '@mui/icons-material/ExpandLess';
13
15
  import ExpandMore from '@mui/icons-material/ExpandMore';
@@ -135,6 +137,9 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
135
137
  const maxDate = React.useRef(2209004187)
136
138
  //const [filteredValues, setFilteredValues] = React.useState<fieldValue[]>([]); // New state for filtered data
137
139
  const [showAll, setShowAll] = React.useState(false);
140
+ const [sortModes, setSortModes] = React.useState<Record<string, string>>({});
141
+ const [sortAnchor, setSortAnchor] = React.useState<null | { el: HTMLElement; name: string }>(null);
142
+
138
143
  // const [minValue, setMinValue] = React.useState<number>(-Infinity)
139
144
  // const [maxValue, setMaxValue] = React.useState<number>(Infinity)
140
145
  // const [startDate, setStartDate] = React.useState<Dayjs>(defaultStart)
@@ -901,7 +906,7 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
901
906
  const value = feature[attributeName];
902
907
  if (value === null || value === undefined || value === '') return;
903
908
  if (value) {
904
-
909
+
905
910
  // Count the occurrence of each value for the attribute
906
911
  if (!attributeSummary[value]) {
907
912
  attributeSummary[value] = 0;
@@ -1030,7 +1035,13 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
1030
1035
 
1031
1036
  }
1032
1037
  else if (attri.display) {
1038
+
1033
1039
  const dataCopy = structuredClone(data)
1040
+ setSortModes(prev => {
1041
+ const copy = { ...prev };
1042
+ delete copy[attri.name];
1043
+ return copy;
1044
+ });
1034
1045
  let result: Attribute[]
1035
1046
  if (recordTable !== undefined && recordTable.length > 0) {
1036
1047
  result = dataCopy.map(item =>
@@ -1215,6 +1226,48 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
1215
1226
 
1216
1227
 
1217
1228
  const renderAttribute = (attribute: Attribute): React.ReactNode => {
1229
+ // --- sort mode for this attribute ---
1230
+ const defaultSort = attribute.type === 'date' ? 'late-early' : 'count';
1231
+ const sortMode = sortModes[attribute.name] ?? defaultSort;
1232
+
1233
+ const sortOptions: Record<string, string[]> = {
1234
+ date: ['late-early', 'early-late', 'count'],
1235
+ number: ['count', 'big-small', 'small-big'],
1236
+ string: ['count', 'a-z', 'z-a'],
1237
+ };
1238
+ const fieldKind = attribute.type === 'date' ? 'date'
1239
+ : (attribute.type && ['integer', 'number', 'float', 'short', 'long', 'big integer'].includes(attribute.type)) || detectNumberAttribute(attribute) ? 'number'
1240
+ : 'string';
1241
+ const modes = sortOptions[fieldKind];
1242
+ const modeLabels: Record<string, string> = {
1243
+ 'count': 'By count',
1244
+ 'a-z': 'A → Z',
1245
+ 'z-a': 'Z → A',
1246
+ 'late-early': 'Latest first',
1247
+ 'early-late': 'Earliest first',
1248
+ 'big-small': 'Biggest first',
1249
+ 'small-big': 'Smallest first',
1250
+ };
1251
+
1252
+ function applySortMode(values: fieldValue[]): fieldValue[] {
1253
+ const copy = [...values];
1254
+ switch (sortMode) {
1255
+ case 'count': return copy.sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
1256
+ case 'a-z': return copy.sort((a, b) => a.value.localeCompare(b.value));
1257
+ case 'z-a': return copy.sort((a, b) => b.value.localeCompare(a.value));
1258
+ case 'late-early': return copy.sort((a, b) => {
1259
+ const da = dayjs(a.value, 'DD/MM/YYYY'), db = dayjs(b.value, 'DD/MM/YYYY');
1260
+ return db.valueOf() - da.valueOf();
1261
+ });
1262
+ case 'early-late': return copy.sort((a, b) => {
1263
+ const da = dayjs(a.value, 'DD/MM/YYYY'), db = dayjs(b.value, 'DD/MM/YYYY');
1264
+ return da.valueOf() - db.valueOf();
1265
+ });
1266
+ case 'big-small': return copy.sort((a, b) => Number(b.value) - Number(a.value));
1267
+ case 'small-big': return copy.sort((a, b) => Number(a.value) - Number(b.value));
1268
+ default: return copy;
1269
+ }
1270
+ }
1218
1271
 
1219
1272
  let ISnumberAttribute = false
1220
1273
  let attributeMin = -Infinity
@@ -1228,6 +1281,9 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
1228
1281
  }
1229
1282
 
1230
1283
 
1284
+
1285
+
1286
+
1231
1287
  let displayedValues: fieldValue[] = []
1232
1288
  let displayedFilterValues: fieldValue[] = []
1233
1289
  let limitInUse
@@ -1238,13 +1294,19 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
1238
1294
  limitInUse = (attribute.type == 'date' && (dayjs.unix(minDate.current).format('DD/MM/YYYY') != startDate.format('DD/MM/YYYY') || (dayjs.unix(maxDate.current).format('DD/MM/YYYY') != endDate.format('DD/MM/YYYY')))) || searchValue.length > 0 || (minValue !== -Infinity || maxValue !== Infinity)
1239
1295
  }
1240
1296
  if (attribute.uniqValue && attribute.uniqValue.length > 0) {
1241
- displayedValues = (attribute.uniqValue.length < defaultNumber + 2) || showAll ? attribute.uniqValue : attribute.uniqValue.slice(0, defaultNumber);
1297
+ const sorted = applySortMode(attribute.uniqValue);
1298
+ displayedValues = (sorted.length < defaultNumber + 2) || showAll
1299
+ ? sorted
1300
+ : sorted.slice(0, defaultNumber);
1242
1301
  }
1243
-
1244
1302
  if (limitInUse && filteredValues.length > 0) {
1245
- displayedFilterValues = ((filteredValues.length < defaultNumber + 2) || showAll) ? filteredValues : filteredValues.slice(0, defaultNumber)
1303
+ const sorted = applySortMode(filteredValues);
1304
+ displayedFilterValues = ((sorted.length < defaultNumber + 2) || showAll)
1305
+ ? sorted
1306
+ : sorted.slice(0, defaultNumber)
1246
1307
  }
1247
1308
 
1309
+
1248
1310
  const handleDateRangeChange = (newStart: Dayjs, newEnd: Dayjs) => {
1249
1311
  setProperty("startDate" as any, newStart || defaultStart);
1250
1312
  setProperty("endDate" as any, newEnd || defaultEnd)
@@ -1435,21 +1497,85 @@ function AttributeFilter(props: AttributeFilterProps): React.ReactElement {
1435
1497
  }
1436
1498
  {((!limitInUse && attribute.uniqValue && attribute.uniqValue.length > 2) ||
1437
1499
  (limitInUse && filteredValues.length > 2)) ?
1438
-
1439
- <ListItemButton sx={{ pl: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} onClick={handleShowAllClick}>
1500
+ <ListItemButton
1501
+ sx={{ pl: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
1502
+ onClick={handleShowAllClick}
1503
+ >
1504
+ {/* Show more/less label */}
1440
1505
  <div style={{ display: 'flex', alignItems: 'center', flexGrow: 1 }}>
1441
- {
1442
- // Display SHOW-ALL or not
1443
- ((!limitInUse && attribute.uniqValue && attribute.uniqValue.length >= defaultNumber + 2) ||
1444
- (limitInUse && filteredValues.length >= defaultNumber + 2)) ?
1445
- <ListItemText primary={`Show ${showAll ? 'less' : 'all others▼'} ${showAll ? '▲' : `(${filteredValues.length > defaultNumber && limitInUse ? filteredValues.length - defaultNumber : attribute.uniqValue.length - defaultNumber})`}`} primaryTypographyProps={{ style: { fontWeight: '500' } }} />
1446
- : null
1447
- }
1506
+ {((!limitInUse && attribute.uniqValue && attribute.uniqValue.length >= defaultNumber + 2) ||
1507
+ (limitInUse && filteredValues.length >= defaultNumber + 2)) ?
1508
+ <ListItemText
1509
+ primary={`Show ${showAll ? 'less' : 'all others▼'} ${showAll ? '▲' : `(${filteredValues.length > defaultNumber && limitInUse ? filteredValues.length - defaultNumber : attribute.uniqValue.length - defaultNumber})`}`}
1510
+ primaryTypographyProps={{ style: { fontWeight: '500' } }}
1511
+ />
1512
+ : null}
1448
1513
  </div>
1449
- <Button style={{ color: 'var(--accentIconBorder) !important' }} variant="text" onClick={(event) => handleSelectAll(event, attribute.name)}>
1514
+
1515
+ {/* Sort dropdown button */}
1516
+ {attribute.uniqValue && attribute.uniqValue.length > 1 && (() => {
1517
+ const open = sortAnchor?.name === attribute.name;
1518
+ return (
1519
+ <Box onClick={e => e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center' }}>
1520
+ <LightTooltip title={`Sort: ${modeLabels[sortMode]}`} arrow>
1521
+ <IconButton
1522
+ size="small"
1523
+ onClick={e => {
1524
+ e.stopPropagation();
1525
+ setSortAnchor(open ? null : { el: e.currentTarget, name: attribute.name });
1526
+ }}
1527
+ sx={{ fontSize: '1rem', opacity: open ? 1 : 0.65, px: 0.5 }}
1528
+ >
1529
+ 📶
1530
+ </IconButton>
1531
+ </LightTooltip>
1532
+
1533
+ {/* Dropdown menu — needs Menu imported from @mui/material or @vertigis/web/ui */}
1534
+ <Menu
1535
+ anchorEl={sortAnchor?.el}
1536
+ open={open}
1537
+ onClose={() => setSortAnchor(null)}
1538
+ onClick={e => e.stopPropagation()}
1539
+ MenuListProps={{ dense: true }}
1540
+ PaperProps={{
1541
+ sx: {
1542
+ fontSize: '1.2rem',
1543
+ minWidth: 160,
1544
+ boxShadow: 3,
1545
+ }
1546
+ }}
1547
+ transformOrigin={{ horizontal: 'right', vertical: 'top' }}
1548
+ anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
1549
+ >
1550
+ {modes.map(mode => (
1551
+ <MenuItem
1552
+ key={mode}
1553
+ selected={sortMode === mode}
1554
+ onClick={e => {
1555
+ e.stopPropagation();
1556
+ setSortModes(prev => ({ ...prev, [attribute.name]: mode }));
1557
+ setSortAnchor(null);
1558
+ }}
1559
+ sx={{ fontSize: '1.2rem' }}
1560
+ >
1561
+ {modeLabels[mode]}
1562
+ </MenuItem>
1563
+ ))}
1564
+ </Menu>
1565
+ </Box>
1566
+ );
1567
+ })()}
1568
+
1569
+ {/* Select / Deselect All */}
1570
+ <Button
1571
+ style={{ color: 'var(--accentIconBorder) !important' }}
1572
+ variant="text"
1573
+ onClick={(event) => handleSelectAll(event, attribute.name)}
1574
+ >
1450
1575
  {allButtonText}
1451
1576
  </Button>
1452
- </ListItemButton> : <></>}
1577
+ </ListItemButton>
1578
+ : <></>}
1453
1579
  {
1454
1580
  // Situation1: Search or range & search result found, only show 'DISPLAYED FILTER VALUE' which is controlled by showall to be all or first 4
1455
1581
  (limitInUse && filteredValues.length > 0) ?