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/build/main.js +2 -2
- package/package.json +1 -1
- package/src/elements/AttributeFilter.tsx +141 -15
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1443
|
-
|
|
1444
|
-
(
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
-
|
|
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) ?
|