@opengeoweb/taf 1.3.0 → 2.1.3

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/taf.umd.js CHANGED
@@ -228,20 +228,20 @@
228
228
  * Copyright 2021 - Finnish Meteorological Institute (FMI)
229
229
  * */
230
230
  var FORMAT_DAYS_HOURS = 'DDHH';
231
- var FORMAT_DAYS_HOURS_MINUTS = 'DDHHmm'; // formatters
231
+ var FORMAT_DAYS_HOURS_MINUTES = 'DDHHmm'; // formatters
232
232
 
233
233
  var formatValidToString = function formatValidToString(valid) {
234
234
  // Ensure end of valid period if midnight is shown as 24 and not 00
235
235
  var endHours = moment__default["default"].utc(valid.end).format('kk');
236
236
  var endDate = endHours === '24' ? moment__default["default"].utc(valid.end).add(-1, 'day').format('DD') : moment__default["default"].utc(valid.end).format('DD');
237
- return valid.end ? moment__default["default"].utc(valid.start).format(FORMAT_DAYS_HOURS) + "/" + endDate + endHours : moment__default["default"].utc(valid.start).format(FORMAT_DAYS_HOURS_MINUTS);
237
+ return valid.end ? moment__default["default"].utc(valid.start).format(FORMAT_DAYS_HOURS) + "/" + endDate + endHours : moment__default["default"].utc(valid.start).format(FORMAT_DAYS_HOURS_MINUTES);
238
238
  };
239
239
 
240
240
  var getSections = function getSections(value, format) {
241
241
  return {
242
242
  day: parseInt(value.substring(0, 2), 10),
243
243
  hour: parseInt(value.substring(2, 4), 10),
244
- min: format === FORMAT_DAYS_HOURS_MINUTS ? parseInt(value.substring(4), 10) : 0
244
+ min: format === FORMAT_DAYS_HOURS_MINUTES ? parseInt(value.substring(4), 10) : 0
245
245
  };
246
246
  };
247
247
 
@@ -271,7 +271,7 @@
271
271
  var tafStartMoment = moment__default["default"].utc(tafStart);
272
272
  var tafEndMoment = moment__default["default"].utc(tafEnd);
273
273
  var startEnd = trimmedValue.split('/');
274
- var format = trimmedValue.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTS : FORMAT_DAYS_HOURS;
274
+ var format = trimmedValue.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTES : FORMAT_DAYS_HOURS;
275
275
  var startSections = getSections(startEnd[0], format);
276
276
  var start = getValidityTimeStamp('start', startSections, tafStartMoment, tafEndMoment).format('YYYY-MM-DDTHH:mm:ss[Z]'); // If we only have start value - return that otherwise, also get end time
277
277
 
@@ -296,7 +296,8 @@
296
296
  var invalidValidityPeriodMessage = 'Invalid validity period, validity end time has to be after validity start time';
297
297
  var invalidValidityEndTimeMessage = 'Invalid validity end time, change group validity must lie within the TAF validity period';
298
298
  var invalidChangeValidInterFieldMessage = 'Invalid validity period, expected DDHH/DDHH for BECMG and TEMPO or DDHHmm for FM';
299
- var invalidBECMGDurationValidInterFieldMessage = 'Invalid validity period, for BECMG only validity periods with a duration of 1, 2, 3 or 4 hours are allowed'; // Validations
299
+ var invalidBECMGDurationValidInterFieldMessage = 'Invalid validity period, for BECMG only validity periods with a duration of 1, 2, 3 or 4 hours are allowed';
300
+ var invalidChangeGroupOverlapsWithFM = 'The valid time of a change group cannot overlap with the valid time of a following FM group. This change group should end before or at the hour of the start of the FM group'; // Validations
300
301
  // Ensure all individual sections are correct (DD HH mm etc)
301
302
 
302
303
  var validateValidSections = function validateValidSections(sections, isStartEnd) {
@@ -320,7 +321,7 @@
320
321
  var tafStartMoment = moment__default["default"].utc(tafStart);
321
322
  var tafEndMoment = moment__default["default"].utc(tafEnd);
322
323
  var startEnd = value.split('/');
323
- var format = value.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTS : FORMAT_DAYS_HOURS;
324
+ var format = value.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTES : FORMAT_DAYS_HOURS;
324
325
  var validateMsg = []; // Validate start time
325
326
 
326
327
  var startSections = getSections(startEnd[0], format); // Validate individual sections for start
@@ -398,7 +399,7 @@
398
399
  var tafStartMoment = moment__default["default"].utc(tafStart);
399
400
  var tafEndMoment = moment__default["default"].utc(tafEnd);
400
401
  var startEnd = trimmedValue.split('/');
401
- var format = trimmedValue.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTS : FORMAT_DAYS_HOURS; // Validate start time
402
+ var format = trimmedValue.indexOf('/') === -1 ? FORMAT_DAYS_HOURS_MINUTES : FORMAT_DAYS_HOURS; // Validate start time
402
403
 
403
404
  var startSections = getSections(startEnd[0], format);
404
405
  var startTimestamp = getValidityTimeStamp('start', startSections, tafStartMoment, tafEndMoment);
@@ -418,17 +419,17 @@
418
419
  var invalidPreviousChangeGroupIsBeforeMessage = 'Valid time should be after previous valid time';
419
420
  var invalidPreviousChangeGroupOrderMessage = "When valid times are equal, the change/probability order should be: " + validProbChangeOrder.join(', ');
420
421
  var invalidPreviousChangeGroupHasSameValue = "When valid times are equal, probability/change can't have the same value as previous row value";
421
- var validatePreviousChangeGroup = function validatePreviousChangeGroup(currentChangeGroup, nextChangeGroup, tafStart, tafEnd) {
422
+ var validatePreviousChangeGroup = function validatePreviousChangeGroup(currentChangeGroup, previousChangeGroup, tafStart, tafEnd) {
422
423
  // extract the needed values from changeGroups
423
424
  var valid = currentChangeGroup.valid,
424
425
  _a = currentChangeGroup.change,
425
426
  change = _a === void 0 ? '' : _a,
426
427
  _b = currentChangeGroup.probability,
427
428
  probability = _b === void 0 ? '' : _b;
428
- var previousValid = nextChangeGroup.valid,
429
- _c = nextChangeGroup.change,
429
+ var previousValid = previousChangeGroup.valid,
430
+ _c = previousChangeGroup.change,
430
431
  previousChange = _c === void 0 ? '' : _c,
431
- _d = nextChangeGroup.probability,
432
+ _d = previousChangeGroup.probability,
432
433
  previousProbability = _d === void 0 ? '' : _d; // get the parsed values
433
434
 
434
435
  var validValue = parseValidToObject(valid, tafStart, tafEnd);
@@ -462,6 +463,27 @@
462
463
  }
463
464
  }
464
465
 
466
+ return true;
467
+ }; // validate overlapping validity when one of the following changegroups is FM
468
+
469
+ var validateOverlapWithFM = function validateOverlapWithFM(currentChangeGroup, nextChangeGroups, tafStart, tafEnd) {
470
+ var valid = currentChangeGroup.valid;
471
+ if (!valid || !nextChangeGroups.length) return true;
472
+ var validValue = parseValidToObject(valid, tafStart, tafEnd);
473
+ var currentRowEndTime = moment__default["default"](validValue.end);
474
+ var firstFMChangeGroup = nextChangeGroups.find(function (nextChangeGroup) {
475
+ return nextChangeGroup.change.trim() === 'FM';
476
+ });
477
+
478
+ if (firstFMChangeGroup && firstFMChangeGroup.valid) {
479
+ var FMValidValue = parseValidToObject(firstFMChangeGroup.valid, tafStart, tafEnd);
480
+ var FMStartTime = moment__default["default"](FMValidValue.start);
481
+
482
+ if (currentRowEndTime.isAfter(FMStartTime)) {
483
+ return invalidChangeGroupOverlapsWithFM;
484
+ }
485
+ }
486
+
465
487
  return true;
466
488
  };
467
489
 
@@ -1395,8 +1417,12 @@
1395
1417
  var invalidWindDirectionStepMessage = 'Invalid wind direction, the direction must be rounded to the nearest step of 10 degrees';
1396
1418
  var invalidWindSpeedMessage = 'Invalid wind speed, expected a speed equal to 2 digits or P99';
1397
1419
  var invalidWindGustsMessage = 'Invalid wind gusts, expected gusts equal to 2 digits or P99';
1398
- var invalidWindGustSpeedCombinationMessage = 'Invalid wind, wind gusts must at least be 10 knots greater than the wind speed';
1399
- var invalidGustForWindP99Message = 'Invalid wind speed and gust combination, if wind is P99, gusts can only be empty or P99'; // validations
1420
+ var invalidWindGustSpeedCombinationMessage = 'Invalid wind gusts, gusts must at least be 10 knots greater than the wind speed';
1421
+ var invalidWindGustSpeedBelow15Message = 'Invalid wind gusts, gusts are only allowed for a minimum wind speed of 15 knots';
1422
+ var invalidGustForWindP99Message = 'Invalid wind speed and gust combination, if wind is P99, gusts can only be empty or P99';
1423
+ var invalidWindSpeedForDirectionZeroMessage = 'Invalid wind speed, if direction is 000, speed can only be 00';
1424
+ var invalidWindSpeedForDirectionNonZeroMessage = 'Invalid wind speed, if direction is not 000, speed can not be 00';
1425
+ var invalidWindVRBWindDirectionMessage = 'Invalid wind direction, VRB can only be used in combination with windspeeds smaller than 4 or in combination with gusts and showers'; // validations
1400
1426
 
1401
1427
  var validateDirection = function validateDirection(value) {
1402
1428
  // if not a number
@@ -1444,8 +1470,29 @@
1444
1470
  return value === 'KT' || value === '' || invalidWindMessage;
1445
1471
  };
1446
1472
 
1447
- var validateWindInterSection = function validateWindInterSection(speed, gust) {
1448
- if (gust === '' || gust === 'P99') {
1473
+ var validateWindInterSection = function validateWindInterSection(speed, gust, direction, weather1Value, weather2Value, weather3Value) {
1474
+ if (direction === '000' && speed !== '00') {
1475
+ return invalidWindSpeedForDirectionZeroMessage;
1476
+ }
1477
+
1478
+ if (direction !== '000' && speed === '00') {
1479
+ return invalidWindSpeedForDirectionNonZeroMessage;
1480
+ }
1481
+
1482
+ if (direction === 'VRB' && (speed === 'P99' || parseInt(speed, 10) > 3)) {
1483
+ // Only allow VRB for speeds smaller than 4 or in combination with gusts and showers (SH, TS, SQ)
1484
+ var allowedWeatherPhenom = ['SH', 'TS', 'SQ'];
1485
+ if (gust === '' || !allowedWeatherPhenom.some(function (allowedPhenom) {
1486
+ return weather1Value.includes(allowedPhenom);
1487
+ }) && !allowedWeatherPhenom.some(function (allowedPhenom) {
1488
+ return weather2Value.includes(allowedPhenom);
1489
+ }) && !allowedWeatherPhenom.some(function (allowedPhenom) {
1490
+ return weather3Value.includes(allowedPhenom);
1491
+ })) return invalidWindVRBWindDirectionMessage;
1492
+ } // Validate gust
1493
+
1494
+
1495
+ if (gust === '') {
1449
1496
  return true;
1450
1497
  }
1451
1498
 
@@ -1453,24 +1500,35 @@
1453
1500
  return invalidGustForWindP99Message;
1454
1501
  }
1455
1502
 
1456
- return parseInt(gust, 10) - parseInt(speed, 10) >= 10 || invalidWindGustSpeedCombinationMessage;
1503
+ if (parseInt(speed, 10) < 15) {
1504
+ return invalidWindGustSpeedBelow15Message;
1505
+ }
1506
+
1507
+ if (gust !== 'P99' && parseInt(gust, 10) - parseInt(speed, 10) < 10) {
1508
+ return invalidWindGustSpeedCombinationMessage;
1509
+ }
1510
+
1511
+ return true;
1457
1512
  }; // Main validation function
1458
1513
 
1459
1514
 
1460
- var validateWindField = function validateWindField(value) {
1461
- var trimmedValue = value.trim();
1515
+ var validateWindField = function validateWindField(value, weatherfield1, weatherfield2, weatherfield3) {
1516
+ var trimmedWindValue = value.trim();
1517
+ var trimmedWeather1Value = weatherfield1.trim();
1518
+ var trimmedWeather2Value = weatherfield2.trim();
1519
+ var trimmedWeather3Value = weatherfield3.trim();
1462
1520
 
1463
- if (!trimmedValue.length) {
1521
+ if (!trimmedWindValue.length) {
1464
1522
  return true;
1465
1523
  } // Validate length and ensure it doesn't contain any other characters than numbers and VRB P KT and G
1466
1524
 
1467
1525
 
1468
- if (trimmedValue.length < 5 || trimmedValue.length > 12 || !trimmedValue.match(/^[0-9VRBPGKT]+$/)) {
1526
+ if (trimmedWindValue.length < 5 || trimmedWindValue.length > 12 || !trimmedWindValue.match(/^[0-9VRBPGKT]+$/)) {
1469
1527
  return invalidWindMessage;
1470
1528
  } // First validate individual sections
1471
1529
 
1472
1530
 
1473
- var windValidationIndv = extractValidateWindSections(trimmedValue, true);
1531
+ var windValidationIndv = extractValidateWindSections(trimmedWindValue, true);
1474
1532
 
1475
1533
  if (windValidationIndv.validations.length !== 0) {
1476
1534
  return windValidationIndv.validations.find(function (error) {
@@ -1479,7 +1537,7 @@
1479
1537
  } // Then do any intersection validation
1480
1538
 
1481
1539
 
1482
- return validateWindInterSection(windValidationIndv.speed, windValidationIndv.gust);
1540
+ return validateWindInterSection(windValidationIndv.speed, windValidationIndv.gust, windValidationIndv.direction, trimmedWeather1Value, trimmedWeather2Value, trimmedWeather3Value);
1483
1541
  };
1484
1542
 
1485
1543
  var formatSingleCloud = function formatSingleCloud(value) {
@@ -1528,10 +1586,10 @@
1528
1586
  var parseSingleCloud = function parseSingleCloud(value) {
1529
1587
  if (!value) return null;
1530
1588
  var trimmedCloud = value.trim();
1531
- var cloudType = trimmedCloud.substr(6, 3);
1589
+ var cloudType = trimmedCloud.substring(6, 9);
1532
1590
  return __assign({
1533
- coverage: trimmedCloud.substr(0, 3),
1534
- height: parseInt(trimmedCloud.substr(3, 3), 10)
1591
+ coverage: trimmedCloud.substring(0, 3),
1592
+ height: parseInt(trimmedCloud.substring(3, 6), 10)
1535
1593
  }, cloudType && {
1536
1594
  type: cloudType
1537
1595
  });
@@ -1549,20 +1607,22 @@
1549
1607
  switch (key) {
1550
1608
  case 'cloud1':
1551
1609
  {
1552
- if (isVerticalVisibility(clouds[key])) {
1610
+ var trimmedValue = clouds[key].trim();
1611
+
1612
+ if (isVerticalVisibility(trimmedValue)) {
1553
1613
  // Parse Vertical Visibility
1554
1614
  return __assign(__assign({}, list), (_a = {}, _a[key] = {
1555
- verticalVisibility: parseInt(clouds[key].substr(2, 3), 10)
1615
+ verticalVisibility: parseInt(trimmedValue.substring(2, 5), 10)
1556
1616
  }, _a));
1557
1617
  }
1558
1618
 
1559
- if (clouds[key] === 'NSC') {
1619
+ if (trimmedValue === 'NSC') {
1560
1620
  // Parse No Significant Clouds
1561
- return __assign(__assign({}, list), (_b = {}, _b[key] = clouds[key], _b));
1621
+ return __assign(__assign({}, list), (_b = {}, _b[key] = trimmedValue, _b));
1562
1622
  } // Parse Cloud
1563
1623
 
1564
1624
 
1565
- return __assign(__assign({}, list), (_c = {}, _c[key] = parseSingleCloud(clouds[key]), _c));
1625
+ return __assign(__assign({}, list), (_c = {}, _c[key] = parseSingleCloud(trimmedValue), _c));
1566
1626
  }
1567
1627
 
1568
1628
  default:
@@ -1590,16 +1650,19 @@
1590
1650
  var invalidCloudHeightStepsMessage = 'Invalid cloud height, a height above 050 must be rounded to the nearest 1000ft';
1591
1651
  var invalidCLoudTypeMessage = 'Invalid cloud type, expected CB or TCU';
1592
1652
  var invalidHeightTypeCombinationMessage = 'Invalid cloud input, when cloud height is 050 or higher, CB or TCU is required';
1593
- var invalidVerticalVisibilityMessage = 'Invalid vertical visibility, expected VV<value> with value consisting of 3 digits between 000 and 010';
1653
+ var invalidVerticalVisibilityMessage = 'Invalid vertical visibility, expected VV<value> with value consisting of 3 digits';
1654
+ var invalidVerticalVisibilityHeightMessage = 'Invalid vertical visibility, visibility should lie between 001 and 010';
1655
+ var invalidVerticalVisibilityHeightFGMessage = 'Invalid vertical visibility, in case of fog visibility should lie between 001 and 005';
1594
1656
  var invalidCloudHeightComparedToPreviousMessage = 'Invalid cloud height, height must be greater than previous cloud group height';
1595
- var invalidCloud2CoverageTypeCombinationMessage = 'Invalid cloud input, cloud type is required when coverage is FEW';
1657
+ var invalidCloud2CoverageTypeCombinationMessage = 'Invalid cloud input, for the second cloud group (excluding any significant convective cloud groups with CB/TCU) a coverage of FEW is not allowed';
1596
1658
  var invalidCloud2WithVerticalVisibilityMessage = 'Invalid cloud input, cloud type is required when Vertical Visibility is given';
1597
- var invalidCloud3CoverageTypeCombinationMessage = 'Invalid cloud input, cloud type is required when coverage is FEW or SCT';
1659
+ var invalidCloud3CoverageTypeCombinationMessage = 'Invalid cloud input, for the third cloud group (excluding any significant convective cloud groups with CB/TCU) a coverage of FEW or SCT is not allowed';
1598
1660
  var invalidCloudWithVerticalVisibilityMessage = 'Invalid cloud input, third or fourth cloud group is not allowed when Vertical Visibility is given';
1599
1661
  var invalidCloudWithPreviousEmptyCloud = 'Invalid cloud input, value is not allowed when previous cloud group is empty';
1600
1662
  var invalidCloudWithNSCMessage = 'Invalid cloud input, other cloud groups are not allowed when No Significant Clouds is given';
1601
- var invalidCloud4WithPreviousType = 'Invalid cloud input, value is not allowed when previous cloud group has a type';
1602
- var invalidCloudCAVOKMessage = 'Invalid cloud input, when CAVOK is used, cloud groups must be empty'; // validations
1663
+ var invalidCloudWithPreviousType = 'Invalid cloud input, only one significant convective cloud group with CB/TCU is allowed';
1664
+ var invalidCloudCAVOKMessage = 'Invalid cloud input, when CAVOK is used, cloud groups must be empty';
1665
+ var invalidCloud4TypeMissingMessage = 'Invalid cloud input for fourth group, CB or TCU required if no other significant convective cloud group present'; // validations
1603
1666
 
1604
1667
  var isLengthValid = function isLengthValid(value) {
1605
1668
  var onlyDigitsOrSpecificLetters = RegExp(/^[0-9FEWSCTBKNOVU]+$/);
@@ -1646,25 +1709,32 @@
1646
1709
  };
1647
1710
 
1648
1711
  var isVerticalVisibility = function isVerticalVisibility(value) {
1649
- return value.substr(0, 2) === 'VV';
1712
+ return value.substring(0, 2) === 'VV';
1650
1713
  };
1651
1714
 
1652
- var isVerticalVisibilityValid = function isVerticalVisibilityValid(value) {
1715
+ var isVerticalVisibilityValid = function isVerticalVisibilityValid(value, weather1, weather2, weather3) {
1653
1716
  // validate if its a number
1654
1717
  if (!value.match(/^[0-9]+$/)) {
1655
- return false;
1718
+ return invalidVerticalVisibilityMessage;
1656
1719
  } // check if its 3 digits
1657
1720
 
1658
1721
 
1659
1722
  if (value.length !== 3) {
1660
- return false;
1661
- } // value should not be above 10
1662
-
1723
+ return invalidVerticalVisibilityMessage;
1724
+ }
1663
1725
 
1664
1726
  var intValue = parseInt(value, 10);
1665
1727
 
1666
- if (intValue > 10) {
1667
- return false;
1728
+ if (weather1 !== 'FG' && weather2 !== 'FG' && weather3 !== 'FG') {
1729
+ // value should not be above 10
1730
+ if (intValue < 1 || intValue > 10) {
1731
+ return invalidVerticalVisibilityHeightMessage;
1732
+ }
1733
+ } else if (intValue < 1 || intValue > 5) {
1734
+ // Apply Dutch rule as national exception
1735
+ // In case of fog (FG) the vertical visibility can only be between VV001 up to and including VV005
1736
+ // HEIGHT_VV is: (001-(1)-005). Deze maxima zijn officiële Nederlandse afwijkingen van de WMO/ICAO regelgeving
1737
+ return invalidVerticalVisibilityHeightFGMessage;
1668
1738
  }
1669
1739
 
1670
1740
  return true;
@@ -1681,14 +1751,14 @@
1681
1751
  } // Validate coverage
1682
1752
 
1683
1753
 
1684
- var coverage = value.substr(0, 3);
1754
+ var coverage = value.substring(0, 3);
1685
1755
 
1686
1756
  if (!isCoverageValid(coverage)) {
1687
1757
  errorMessages.push(invalidCloudCoverageMessage);
1688
1758
  } // Validate height
1689
1759
 
1690
1760
 
1691
- var height = value.substr(3, 3);
1761
+ var height = value.substring(3, 6);
1692
1762
  var isValidHeight = isHeightValid(height);
1693
1763
 
1694
1764
  if (isValidHeight !== true) {
@@ -1697,7 +1767,7 @@
1697
1767
  } // Validate type
1698
1768
 
1699
1769
 
1700
- var type = value.substr(6, 3);
1770
+ var type = value.substring(6, 9);
1701
1771
 
1702
1772
  if (!isTypeValid(type)) {
1703
1773
  errorMessages.push(invalidCLoudTypeMessage);
@@ -1716,56 +1786,66 @@
1716
1786
  };
1717
1787
  };
1718
1788
 
1719
- var validateFirstCloud = function validateFirstCloud(value, visibilityValue) {
1720
- var trimmedValue = value.trim(); // Field can be left empty
1789
+ var validateFirstCloud = function validateFirstCloud(value, visibilityValue, weatherfield1, weatherfield2, weatherfield3) {
1790
+ var trimmedValueFirstCloud = value.trim();
1791
+ var trimmedValueVisibility = visibilityValue.trim();
1792
+ var trimmedWeather1Value = weatherfield1.trim();
1793
+ var trimmedWeather2Value = weatherfield2.trim();
1794
+ var trimmedWeather3Value = weatherfield3.trim(); // Field can be left empty
1721
1795
 
1722
- if (!trimmedValue.length) {
1796
+ if (!trimmedValueFirstCloud.length) {
1723
1797
  return true;
1724
1798
  }
1725
1799
 
1726
- if (trimmedValue && visibilityValue === 'CAVOK') {
1800
+ if (trimmedValueFirstCloud && trimmedValueVisibility === 'CAVOK') {
1727
1801
  return invalidCloudCAVOKMessage;
1728
1802
  } // Check for No Signicant Clouds
1729
1803
 
1730
1804
 
1731
- if (trimmedValue === 'NSC') {
1805
+ if (trimmedValueFirstCloud === 'NSC') {
1732
1806
  return true;
1733
1807
  } // Validate Vertical Visibility
1734
1808
 
1735
1809
 
1736
- if (isVerticalVisibility(trimmedValue)) {
1810
+ if (isVerticalVisibility(trimmedValueFirstCloud)) {
1737
1811
  if ( // Validate length of 5
1738
- trimmedValue.length !== 5 || // Ensure it only contains digits or VV
1739
- !trimmedValue.match(/^[0-9V]+$/)) {
1812
+ trimmedValueFirstCloud.length !== 5 || // Ensure it only contains digits or VV
1813
+ !trimmedValueFirstCloud.match(/^[0-9V]+$/)) {
1740
1814
  return invalidVerticalVisibilityMessage;
1741
1815
  }
1742
1816
 
1743
- var verticalVisibilityValue = trimmedValue.substr(2, 3);
1744
-
1745
- if (!isVerticalVisibilityValid(verticalVisibilityValue)) {
1746
- return invalidVerticalVisibilityMessage;
1747
- }
1748
-
1749
- return true;
1817
+ var verticalVisibilityValue = trimmedValueFirstCloud.substring(2, 5);
1818
+ return isVerticalVisibilityValid(verticalVisibilityValue, trimmedWeather1Value, trimmedWeather2Value, trimmedWeather3Value);
1750
1819
  }
1751
1820
 
1752
1821
  var errorMessages = sharedCloudValidations({
1753
- value: trimmedValue,
1822
+ value: trimmedValueFirstCloud,
1754
1823
  canBeNSC: true
1755
1824
  }).errorMessages; // Return first error if any
1756
1825
 
1757
1826
  return errorMessages.length ? errorMessages[0] : true;
1758
1827
  };
1759
1828
  var validateSecondCloud = function validateSecondCloud(firstCloudValue, secondCloudValue, visibilityValue) {
1760
- var trimmedValue = secondCloudValue.trim(); // Field can be left empty
1829
+ var trimmedValueFirstCloud = firstCloudValue.trim();
1830
+ var trimmedValueSecondCloud = secondCloudValue.trim(); // Field can be left empty
1761
1831
 
1762
- if (!trimmedValue.length) {
1832
+ if (!trimmedValueSecondCloud.length) {
1763
1833
  return true;
1834
+ } // If Cloud1 is NSC, Cloud2 has to be empty
1835
+
1836
+
1837
+ if (trimmedValueFirstCloud === 'NSC' && trimmedValueSecondCloud !== '') {
1838
+ return invalidCloudWithNSCMessage;
1839
+ } // If Cloud1 is empty Cloud2 should be empty
1840
+
1841
+
1842
+ if (trimmedValueFirstCloud === '') {
1843
+ return invalidCloudWithPreviousEmptyCloud;
1764
1844
  } // Generic validations
1765
1845
 
1766
1846
 
1767
1847
  var _a = sharedCloudValidations({
1768
- value: trimmedValue
1848
+ value: trimmedValueSecondCloud
1769
1849
  }),
1770
1850
  coverage = _a.coverage,
1771
1851
  height = _a.height,
@@ -1775,32 +1855,32 @@
1775
1855
 
1776
1856
  if (errorMessages.length) {
1777
1857
  return errorMessages[0];
1778
- } // Cloud2 specific validations
1779
- // Without type, coverage can not be FEW
1858
+ } // Cloud2 specific validations - only to be applied when cloud1 does not contain type CB/TCU
1780
1859
 
1781
1860
 
1782
- if (type === '' && coverage === 'FEW') {
1783
- return invalidCloud2CoverageTypeCombinationMessage;
1861
+ var typeCloud1 = trimmedValueFirstCloud.substring(6, 9);
1862
+
1863
+ if (typeCloud1 !== 'CB' && typeCloud1 !== 'TCU') {
1864
+ // Without a type specified in cloud group 1, this group counts as #2 and coverage can not be FEW
1865
+ if (type === '' && coverage === 'FEW') {
1866
+ return invalidCloud2CoverageTypeCombinationMessage;
1867
+ }
1784
1868
  } // If Cloud1 is not valid, skip further validation of Cloud2
1869
+ // No need to pass the weatherfields as their content does not influence what happens below
1785
1870
 
1786
1871
 
1787
- if (validateFirstCloud(firstCloudValue, visibilityValue) !== true) {
1872
+ if (validateFirstCloud(trimmedValueFirstCloud, visibilityValue, '', '', '') !== true) {
1788
1873
  return true;
1789
- } // If Cloud1 is NSC, Cloud2 has to be empty
1790
-
1874
+ } // If Cloud1 contains a type, Cloud2 cannot contain a type
1791
1875
 
1792
- if (firstCloudValue === 'NSC' && trimmedValue !== '') {
1793
- return invalidCloudWithNSCMessage;
1794
- } // If Cloud1 is empty Cloud2 should be empty
1795
1876
 
1796
-
1797
- if (firstCloudValue === '') {
1798
- return invalidCloudWithPreviousEmptyCloud;
1877
+ if (type !== '' && typeCloud1 !== '') {
1878
+ return invalidCloudWithPreviousType;
1799
1879
  } // Cloud2 height has to be greater than Cloud1 height
1800
1880
 
1801
1881
 
1802
- if (!isVerticalVisibility(firstCloudValue)) {
1803
- var cloud1Height = firstCloudValue.substr(3, 3);
1882
+ if (!isVerticalVisibility(trimmedValueFirstCloud)) {
1883
+ var cloud1Height = trimmedValueFirstCloud.substring(3, 6);
1804
1884
 
1805
1885
  if (height <= cloud1Height) {
1806
1886
  return invalidCloudHeightComparedToPreviousMessage;
@@ -1808,22 +1888,34 @@
1808
1888
  } // If Cloud1 contains Vertical Visibility then Cloud2 type is required
1809
1889
 
1810
1890
 
1811
- if (isVerticalVisibility(firstCloudValue) && type === '') {
1891
+ if (isVerticalVisibility(trimmedValueFirstCloud) && type === '') {
1812
1892
  return invalidCloud2WithVerticalVisibilityMessage;
1813
1893
  }
1814
1894
 
1815
1895
  return true;
1816
1896
  };
1817
1897
  var validateThirdCloud = function validateThirdCloud(firstCloudValue, secondCloudValue, thirdCloudValue, visibilityValue) {
1818
- var trimmedValue = thirdCloudValue.trim(); // Field can be left empty
1898
+ var trimmedValueFirstCloud = firstCloudValue.trim();
1899
+ var trimmedValueSecondCloud = secondCloudValue.trim();
1900
+ var trimmedValueThirdCloud = thirdCloudValue.trim(); // Field can be left empty
1819
1901
 
1820
- if (!trimmedValue.length) {
1902
+ if (!trimmedValueThirdCloud.length) {
1821
1903
  return true;
1904
+ } // If Cloud1 is NSC, Cloud3 has to be empty
1905
+
1906
+
1907
+ if (trimmedValueFirstCloud === 'NSC' && trimmedValueThirdCloud !== '') {
1908
+ return invalidCloudWithNSCMessage;
1909
+ } // If Cloud1 or Cloud2 is empty Cloud3 should be empty
1910
+
1911
+
1912
+ if (trimmedValueFirstCloud === '' || trimmedValueSecondCloud === '') {
1913
+ return invalidCloudWithPreviousEmptyCloud;
1822
1914
  } // Generic validations
1823
1915
 
1824
1916
 
1825
1917
  var _a = sharedCloudValidations({
1826
- value: trimmedValue
1918
+ value: trimmedValueThirdCloud
1827
1919
  }),
1828
1920
  coverage = _a.coverage,
1829
1921
  height = _a.height,
@@ -1834,45 +1926,45 @@
1834
1926
  if (errorMessages.length) {
1835
1927
  return errorMessages[0];
1836
1928
  } // Cloud3 specific validations
1837
- // Without type, coverage can not be FEW or SCT
1929
+ // Without a type specified in cloud group 1 or 2, this group counts as #3 and coverage can not be FEW or SCT
1838
1930
 
1839
1931
 
1840
- if (type === '' && (coverage === 'FEW' || coverage === 'SCT')) {
1841
- return invalidCloud3CoverageTypeCombinationMessage;
1932
+ var typeCloud1 = trimmedValueFirstCloud.substring(6, 9);
1933
+ var typeCloud2 = trimmedValueSecondCloud.substring(6, 9);
1934
+
1935
+ if (typeCloud1 !== 'CB' && typeCloud1 !== 'TCU' && typeCloud2 !== 'CB' && typeCloud2 !== 'TCU') {
1936
+ if (type === '' && (coverage === 'FEW' || coverage === 'SCT')) {
1937
+ return invalidCloud3CoverageTypeCombinationMessage;
1938
+ }
1939
+ } else if (type === '' && coverage === 'FEW') {
1940
+ // In case we do have a CB/TCU group in one of the previous clouf groups. This cloudgroup should be evaluated as if it was the second group
1941
+ return invalidCloud2CoverageTypeCombinationMessage;
1842
1942
  } // If Cloud1 is not valid, skip further validation of Cloud3
1943
+ // No need to pass the weatherfields as their content does not influence what happens below
1944
+ // Since in case of VV the third field should be empty anyways
1843
1945
 
1844
1946
 
1845
- if (validateFirstCloud(firstCloudValue, visibilityValue) !== true) {
1947
+ if (validateFirstCloud(trimmedValueFirstCloud, visibilityValue, '', '', '') !== true) {
1846
1948
  return true;
1847
- } // If Cloud1 is empty Cloud3 should be empty
1848
-
1849
-
1850
- if (firstCloudValue === '') {
1851
- return invalidCloudWithPreviousEmptyCloud;
1852
1949
  } // If Cloud1 contains Vertical Visibility then Cloud3 should be empty
1853
1950
 
1854
1951
 
1855
- if (isVerticalVisibility(firstCloudValue)) {
1952
+ if (isVerticalVisibility(trimmedValueFirstCloud)) {
1856
1953
  return invalidCloudWithVerticalVisibilityMessage;
1857
- } // If Cloud1 is NSC, Cloud3 has to be empty
1858
-
1859
-
1860
- if (firstCloudValue === 'NSC' && trimmedValue !== '') {
1861
- return invalidCloudWithNSCMessage;
1862
1954
  } // If Cloud2 is not valid, skip further validation of Cloud3
1863
1955
 
1864
1956
 
1865
- if (validateSecondCloud(firstCloudValue, secondCloudValue, visibilityValue) !== true) {
1957
+ if (validateSecondCloud(trimmedValueFirstCloud, trimmedValueSecondCloud, visibilityValue) !== true) {
1866
1958
  return true;
1867
- } // If Cloud2 is empty Cloud3 should be empty
1959
+ } // If one of the previous groups contains a type, Cloud3 cannot contain a type
1868
1960
 
1869
1961
 
1870
- if (secondCloudValue === '') {
1871
- return invalidCloudWithPreviousEmptyCloud;
1962
+ if (type !== '' && (typeCloud1 !== '' || typeCloud2 !== '')) {
1963
+ return invalidCloudWithPreviousType;
1872
1964
  } // Cloud3 height has to be greater than Cloud2 height
1873
1965
 
1874
1966
 
1875
- var cloud2Height = secondCloudValue.substr(3, 3);
1967
+ var cloud2Height = trimmedValueSecondCloud.substring(3, 6);
1876
1968
 
1877
1969
  if (height <= cloud2Height) {
1878
1970
  return invalidCloudHeightComparedToPreviousMessage;
@@ -1881,17 +1973,31 @@
1881
1973
  return true;
1882
1974
  };
1883
1975
  var validateFourthCloud = function validateFourthCloud(firstCloudValue, secondCloudValue, thirdCloudValue, fourthCloudValue, visibilityValue) {
1884
- var trimmedValue = fourthCloudValue.trim(); // Field can be left empty
1976
+ var trimmedValueFirstCloud = firstCloudValue.trim();
1977
+ var trimmedValueSecondCloud = secondCloudValue.trim();
1978
+ var trimmedValueThirdCloud = thirdCloudValue.trim();
1979
+ var trimmedValueFourthCloud = fourthCloudValue.trim(); // Field can be left empty
1885
1980
 
1886
- if (!trimmedValue.length) {
1981
+ if (!trimmedValueFourthCloud.length) {
1887
1982
  return true;
1983
+ } // If Cloud1 is NSC, Cloud4 has to be empty
1984
+
1985
+
1986
+ if (trimmedValueFirstCloud === 'NSC' && trimmedValueFourthCloud !== '') {
1987
+ return invalidCloudWithNSCMessage;
1988
+ } // If Cloud 1, 2 or 3 is empty Cloud4 should be empty
1989
+
1990
+
1991
+ if (trimmedValueFirstCloud === '' || trimmedValueSecondCloud === '' || trimmedValueThirdCloud === '') {
1992
+ return invalidCloudWithPreviousEmptyCloud;
1888
1993
  } // Generic validations
1889
1994
 
1890
1995
 
1891
1996
  var _a = sharedCloudValidations({
1892
- value: trimmedValue
1997
+ value: trimmedValueFourthCloud
1893
1998
  }),
1894
1999
  height = _a.height,
2000
+ coverage = _a.coverage,
1895
2001
  type = _a.type,
1896
2002
  errorMessages = _a.errorMessages; // Return generic errors if any
1897
2003
 
@@ -1899,50 +2005,48 @@
1899
2005
  if (errorMessages.length) {
1900
2006
  return errorMessages[0];
1901
2007
  } // Cloud4 specific validations
1902
- // Type is required
2008
+ // If none of the previous cloud groups contains a type, the fourth group must contain a type if not empty
2009
+
1903
2010
 
2011
+ var typeCloud1 = trimmedValueFirstCloud.substring(6, 9);
2012
+ var typeCloud2 = trimmedValueSecondCloud.substring(6, 9);
2013
+ var typeCloud3 = trimmedValueThirdCloud.substring(6, 9);
1904
2014
 
1905
- if (type === '') {
1906
- return invalidCLoudTypeMessage;
2015
+ if (typeCloud1 !== 'CB' && typeCloud1 !== 'TCU' && typeCloud2 !== 'CB' && typeCloud2 !== 'TCU' && typeCloud3 !== 'CB' && typeCloud3 !== 'TCU') {
2016
+ if (type === '') {
2017
+ return invalidCloud4TypeMissingMessage;
2018
+ }
2019
+ } else if (coverage === 'FEW' || coverage === 'SCT') {
2020
+ // We have a CB/TCU group in one of the previous cloud groups. This group should be evaluated as if it was the third group
2021
+ return invalidCloud3CoverageTypeCombinationMessage;
1907
2022
  } // If Cloud1 is not valid, skip further validation of Cloud4
2023
+ // No need to pass the weatherfields as their content does not influence what happens below
2024
+ // Since in case of VV the fourth field should be empty anyways
1908
2025
 
1909
2026
 
1910
- if (validateFirstCloud(firstCloudValue, visibilityValue) !== true) {
2027
+ if (validateFirstCloud(trimmedValueFirstCloud, visibilityValue, '', '', '') !== true) {
1911
2028
  return true;
1912
2029
  } // If Cloud1 contains Vertical Visibility then Cloud4 should be empty
1913
2030
 
1914
2031
 
1915
- if (isVerticalVisibility(firstCloudValue)) {
2032
+ if (isVerticalVisibility(trimmedValueFirstCloud)) {
1916
2033
  return invalidCloudWithVerticalVisibilityMessage;
1917
- } // If Cloud1 is NSC, Cloud4 has to be empty
1918
-
1919
-
1920
- if (firstCloudValue === 'NSC' && trimmedValue !== '') {
1921
- return invalidCloudWithNSCMessage;
1922
- } // If Cloud 1, 2 or 3 is empty Cloud4 should be empty
1923
-
1924
-
1925
- if (firstCloudValue === '' || secondCloudValue === '' || thirdCloudValue === '') {
1926
- return invalidCloudWithPreviousEmptyCloud;
1927
2034
  } // If Cloud3 is not valid, skip further validation of Cloud4
1928
2035
 
1929
2036
 
1930
- if (validateThirdCloud(firstCloudValue, secondCloudValue, thirdCloudValue, visibilityValue) !== true) {
2037
+ if (validateThirdCloud(trimmedValueFirstCloud, trimmedValueSecondCloud, trimmedValueThirdCloud, visibilityValue) !== true) {
1931
2038
  return true;
1932
- } // If Cloud3 contains a type, Cloud4 should be empty
2039
+ } // If one of the previous groups contains a type, Cloud4 cannot contain a type
1933
2040
 
1934
2041
 
1935
- var cloud3Type = thirdCloudValue.substr(6, 3);
1936
-
1937
- if (cloud3Type !== '') {
1938
- return invalidCloud4WithPreviousType;
2042
+ if (type !== '' && (typeCloud1 !== '' || typeCloud2 !== '' || typeCloud3 !== '')) {
2043
+ return invalidCloudWithPreviousType;
1939
2044
  } // Cloud4 height has to be greater than Cloud3 height
1940
2045
 
1941
2046
 
1942
- var cloud3Height = thirdCloudValue.substr(3, 3);
2047
+ var cloud3Height = trimmedValueThirdCloud.substring(3, 6);
1943
2048
 
1944
2049
  if (height <= cloud3Height) {
1945
- // Because of the type validations this is automatically enforced for Cloud4, so impossible to end up here but leaving the check in just in case
1946
2050
  return invalidCloudHeightComparedToPreviousMessage;
1947
2051
  }
1948
2052
 
@@ -2351,12 +2455,25 @@
2351
2455
  if (action === 'CORRECT') return 'This TAF is a correction';
2352
2456
  return 'Cancel TAF';
2353
2457
  };
2354
- var getConfirmationDialogContent = function getConfirmationDialogContent(action) {
2355
- if (action === 'DISCARD') return 'Are you sure you would like to discard this TAF? Any changes made will be lost';
2356
- if (action === 'PUBLISH') return 'Are you sure you want to publish this TAF?';
2357
- if (action === 'AMEND') return 'Do you want to publish it?';
2358
- if (action === 'CORRECT') return 'Do you want to publish it?';
2359
- return 'Are you sure you want to cancel this TAF?';
2458
+ var getConfirmationDialogContent = function getConfirmationDialogContent(action, isFormDirty) {
2459
+ if (isFormDirty === void 0) {
2460
+ isFormDirty = true;
2461
+ }
2462
+
2463
+ switch (action) {
2464
+ case 'DISCARD':
2465
+ return 'Are you sure you would like to discard this TAF? Any changes made will be lost';
2466
+
2467
+ case 'PUBLISH':
2468
+ return 'Are you sure you want to publish this TAF?';
2469
+
2470
+ case 'AMEND':
2471
+ case 'CORRECT':
2472
+ return isFormDirty ? 'Do you want to publish it?' : 'Are you sure you want to publish the TAF? You did not make any changes!';
2473
+
2474
+ default:
2475
+ return 'Are you sure you want to cancel this TAF?';
2476
+ }
2360
2477
  };
2361
2478
  var getConfirmationDialogButtonLabel = function getConfirmationDialogButtonLabel(action) {
2362
2479
  switch (action) {
@@ -2433,7 +2550,7 @@
2433
2550
  borderRadius: '3px'
2434
2551
  },
2435
2552
  addButton: {
2436
- marginRight: '38px'
2553
+ marginRight: '48px'
2437
2554
  },
2438
2555
  lifecycleButtons: {
2439
2556
  marginTop: '40px',
@@ -2448,6 +2565,9 @@
2448
2565
  backdrop: {
2449
2566
  zIndex: 1500,
2450
2567
  color: '#fff'
2568
+ },
2569
+ emptyButtonContainer: {
2570
+ minWidth: 96
2451
2571
  }
2452
2572
  };
2453
2573
  });
@@ -2691,8 +2811,10 @@
2691
2811
  field = _f === void 0 ? null : _f,
2692
2812
  _g = _a.dragHandle,
2693
2813
  dragHandle = _g === void 0 ? null : _g,
2694
- props = __rest(_a, ["isChangeGroup", "disabled", "index", "lastRow", "onRemoveChangeGroup", "field", "dragHandle"]);
2814
+ onAddChangeGroup = _a.onAddChangeGroup,
2815
+ props = __rest(_a, ["isChangeGroup", "disabled", "index", "lastRow", "onRemoveChangeGroup", "field", "dragHandle", "onAddChangeGroup"]);
2695
2816
 
2817
+ var classes = useStyles$9();
2696
2818
  var namePrefix = !isChangeGroup ? 'baseForecast' : "changeGroups[" + index + "]";
2697
2819
  var fieldNames = getFieldNames(isChangeGroup, index);
2698
2820
 
@@ -2769,16 +2891,43 @@
2769
2891
  return true;
2770
2892
  }, [index, isChangeGroup, getValues]);
2771
2893
 
2772
- var getFieldFromNextRow = function getFieldFromNextRow(fieldname) {
2773
- if (fieldname === void 0) {
2774
- fieldname = 'valid';
2894
+ var getFieldFromRow = function getFieldFromRow(fieldname, rowIndex) {
2895
+ var indexedFieldName = "changeGroups[" + rowIndex + "]." + fieldname;
2896
+ var rowValue = getValues(indexedFieldName);
2897
+ return rowValue ? [indexedFieldName] : [];
2898
+ };
2899
+
2900
+ var getValidFieldFromNextRow = function getValidFieldFromNextRow() {
2901
+ return getFieldFromRow('valid', index + 1);
2902
+ };
2903
+
2904
+ var validateNextRows = React__default["default"].useCallback(function () {
2905
+ if (isChangeGroup) {
2906
+ var _a = getValues(),
2907
+ validDateEnd = _a.validDateEnd,
2908
+ validDateStart = _a.validDateStart,
2909
+ changeGroups = _a.changeGroups;
2910
+
2911
+ var currentChangeGroup = changeGroups[index];
2912
+ var nextChangeGroups = changeGroups.slice(index + 1);
2913
+ return validateOverlapWithFM(currentChangeGroup, nextChangeGroups, validDateStart, validDateEnd);
2775
2914
  }
2776
2915
 
2777
- var nextValidFieldName = "changeGroups[" + (index + 1) + "]." + fieldname;
2778
- var nextRowValidValue = getValues(nextValidFieldName);
2779
- return nextRowValidValue ? [nextValidFieldName] : [];
2916
+ return true;
2917
+ }, [index, isChangeGroup, getValues]);
2918
+
2919
+ var getPreviousValidFields = function getPreviousValidFields(rowIndex) {
2920
+ var changeGroups = getValues().changeGroups;
2921
+ var previousChangeGroups = changeGroups.slice(0, rowIndex);
2922
+ var validFields = previousChangeGroups.map(function (_element, index) {
2923
+ return getFieldFromRow('valid', index)[0];
2924
+ });
2925
+ return validFields;
2780
2926
  };
2781
2927
 
2928
+ var addChangeGroup = React__default["default"].useCallback(function () {
2929
+ onAddChangeGroup(index);
2930
+ }, [onAddChangeGroup, index]);
2782
2931
  return /*#__PURE__*/React__default["default"].createElement(core.Grid, __assign({
2783
2932
  "data-testid": "row-" + namePrefix,
2784
2933
  container: true,
@@ -2822,7 +2971,7 @@
2822
2971
  }],
2823
2972
  onBlur: function onBlur() {
2824
2973
  if (isChangeGroup) {
2825
- var nextValidFieldName = getFieldFromNextRow();
2974
+ var nextValidFieldName = getValidFieldFromNextRow();
2826
2975
  trigger(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(subFields)), __read(mainOptionalFields)), __read(mainFields)), __read(nextValidFieldName)));
2827
2976
  }
2828
2977
  }
@@ -2852,8 +3001,9 @@
2852
3001
  }],
2853
3002
  onBlur: function onBlur() {
2854
3003
  if (isChangeGroup) {
2855
- var nextValidFieldName = getFieldFromNextRow();
2856
- trigger(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(subFields)), __read(mainOptionalFields)), __read(mainFields)), __read(nextValidFieldName)));
3004
+ var nextValidFieldName = getValidFieldFromNextRow();
3005
+ var previousValidFieldNames = getPreviousValidFields(index);
3006
+ trigger(__spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(subFields)), __read(mainOptionalFields)), __read(mainFields)), __read(nextValidFieldName)), __read(previousValidFieldNames)));
2857
3007
  }
2858
3008
  }
2859
3009
  })), /*#__PURE__*/React__default["default"].createElement(TafFormTextField, {
@@ -2871,13 +3021,15 @@
2871
3021
  validateValidChangeGroupInterField: function validateValidChangeGroupInterField$1(value) {
2872
3022
  return isChangeGroup ? validateValidChangeGroupInterField(value, getValues(fieldNames.change), getValues('validDateStart'), getValues('validDateEnd')) : true;
2873
3023
  },
2874
- validatePreviousRow: validatePreviousRow
3024
+ validatePreviousRow: validatePreviousRow,
3025
+ validateNextRows: validateNextRows
2875
3026
  }
2876
3027
  },
2877
3028
  onBlur: function onBlur() {
2878
3029
  if (isChangeGroup) {
2879
- var nextValidFieldName = getFieldFromNextRow();
2880
- trigger(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(subFields)), __read(mainOptionalFields)), __read(mainFields)), __read(nextValidFieldName)));
3030
+ var nextValidFieldName = getValidFieldFromNextRow();
3031
+ var previousValidFieldNames = getPreviousValidFields(index);
3032
+ trigger(__spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(subFields)), __read(mainOptionalFields)), __read(mainFields)), __read(nextValidFieldName)), __read(previousValidFieldNames)));
2881
3033
  }
2882
3034
  }
2883
3035
  }), /*#__PURE__*/React__default["default"].createElement(TafFormTextField, {
@@ -2891,7 +3043,9 @@
2891
3043
  return _validateRequiredMainFields(value, 'Wind');
2892
3044
  },
2893
3045
  validateRequiredSub: validateRequiredSub,
2894
- validateWindField: validateWindField
3046
+ validateWindField: function validateWindField$1(value) {
3047
+ return validateWindField(value, getValues(fieldNames.weather1), getValues(fieldNames.weather2), getValues(fieldNames.weather3));
3048
+ }
2895
3049
  }
2896
3050
  },
2897
3051
  onBlur: function onBlur() {
@@ -2940,7 +3094,7 @@
2940
3094
  },
2941
3095
  onBlur: function onBlur() {
2942
3096
  var fields = isChangeGroup ? __spreadArray(__spreadArray([], __read(subFields)), __read(mainFields)) : [];
2943
- return trigger(__spreadArray(__spreadArray([], __read(fields)), [fieldNames.weather2, fieldNames.weather3]));
3097
+ return trigger(__spreadArray(__spreadArray([], __read(fields)), [fieldNames.weather2, fieldNames.weather3, fieldNames.wind, fieldNames.cloud1]));
2944
3098
  }
2945
3099
  }), /*#__PURE__*/React__default["default"].createElement(TafFormTextField, {
2946
3100
  label: "Weather",
@@ -2956,7 +3110,7 @@
2956
3110
  },
2957
3111
  onBlur: function onBlur() {
2958
3112
  var fields = isChangeGroup ? __spreadArray(__spreadArray([], __read(subFields)), __read(mainFields)) : [];
2959
- return trigger(__spreadArray(__spreadArray([], __read(fields)), [fieldNames.weather3]));
3113
+ return trigger(__spreadArray(__spreadArray([], __read(fields)), [fieldNames.weather3, fieldNames.wind, fieldNames.cloud1]));
2960
3114
  }
2961
3115
  }), /*#__PURE__*/React__default["default"].createElement(TafFormTextField, {
2962
3116
  label: "Weather",
@@ -2969,6 +3123,9 @@
2969
3123
  return validateThirdWeatherField(getValues(fieldNames.weather1), getValues(fieldNames.weather2), value, getValues(fieldNames.visibility));
2970
3124
  }
2971
3125
  }
3126
+ },
3127
+ onBlur: function onBlur() {
3128
+ return trigger([fieldNames.wind, fieldNames.cloud1]);
2972
3129
  }
2973
3130
  }), /*#__PURE__*/React__default["default"].createElement(TafFormTextField, {
2974
3131
  label: "Cloud",
@@ -2982,7 +3139,7 @@
2982
3139
  },
2983
3140
  validateRequiredSub: validateRequiredSub,
2984
3141
  validateFirstCloud: function validateFirstCloud$1(value) {
2985
- return validateFirstCloud(value, getValues(fieldNames.visibility));
3142
+ return validateFirstCloud(value, getValues(fieldNames.visibility), getValues(fieldNames.weather1), getValues(fieldNames.weather2), getValues(fieldNames.weather3));
2986
3143
  }
2987
3144
  }
2988
3145
  },
@@ -3033,19 +3190,27 @@
3033
3190
  }
3034
3191
  }
3035
3192
  }), (!isChangeGroup || disabled) && /*#__PURE__*/React__default["default"].createElement(core.Grid, {
3193
+ className: classes.emptyButtonContainer,
3036
3194
  item: true,
3037
3195
  xs: true
3038
- }), isChangeGroup && !disabled && /*#__PURE__*/React__default["default"].createElement(core.Grid, {
3039
- item: true,
3040
- xs: true
3041
- }, /*#__PURE__*/React__default["default"].createElement(core.IconButton, __assign({
3196
+ }, !disabled && /*#__PURE__*/React__default["default"].createElement(core.IconButton, {
3197
+ "aria-label": "add",
3198
+ "data-testid": "add-changeGroup",
3199
+ onClick: addChangeGroup
3200
+ }, /*#__PURE__*/React__default["default"].createElement(icons.Add, null))), isChangeGroup && !disabled && /*#__PURE__*/React__default["default"].createElement(core.Grid, {
3201
+ item: true
3202
+ }, /*#__PURE__*/React__default["default"].createElement(core.IconButton, {
3203
+ "aria-label": "add",
3204
+ "data-testid": "add-changeGroup",
3205
+ onClick: addChangeGroup
3206
+ }, /*#__PURE__*/React__default["default"].createElement(icons.Add, null)), /*#__PURE__*/React__default["default"].createElement(core.IconButton, __assign({
3042
3207
  "aria-label": "delete",
3043
3208
  "data-testid": "remove-changeGroup",
3044
3209
  onClick: function onClick() {
3045
3210
  return onRemoveChangeGroup(index);
3046
3211
  }
3047
3212
  }, lastRow ? {
3048
- tabIndex: '-1'
3213
+ tabIndex: -1
3049
3214
  } : {}), /*#__PURE__*/React__default["default"].createElement(icons.Delete, null))));
3050
3215
  });
3051
3216
 
@@ -3547,7 +3712,7 @@
3547
3712
  var showConfirmDialog = function showConfirmDialog(action) {
3548
3713
  return confirmDialog({
3549
3714
  title: getConfirmationDialogTitle(action),
3550
- description: getConfirmationDialogContent(action),
3715
+ description: getConfirmationDialogContent(action, isFormDirty),
3551
3716
  confirmLabel: getConfirmationDialogButtonLabel(action)
3552
3717
  });
3553
3718
  };
@@ -3596,32 +3761,61 @@
3596
3761
  name: 'changeGroups'
3597
3762
  }),
3598
3763
  fields = _h.fields,
3599
- append = _h.append,
3764
+ insert = _h.insert,
3600
3765
  remove = _h.remove,
3601
3766
  move = _h.move;
3602
3767
 
3603
- var lastTotalFields = shared.usePrevious(fields.length);
3604
-
3605
3768
  var _j = useDragOrder(trigger, move, onFormBlur),
3606
3769
  onChangeOrder = _j.onChangeOrder,
3607
3770
  onStartDrag = _j.onStartDrag,
3608
3771
  activeDraggingIndex = _j.activeDraggingIndex;
3609
3772
 
3610
3773
  var onRemoverow = function onRemoverow(index) {
3611
- remove(index);
3774
+ confirmDialog({
3775
+ title: 'Delete row',
3776
+ description: 'Are you sure you want to delete this row?',
3777
+ confirmLabel: 'Yes'
3778
+ }).then(function () {
3779
+ remove(index);
3780
+ trigger(); // trigger all fields validation
3781
+
3782
+ onFormBlur();
3783
+ });
3784
+ }; // TODO: https://gitlab.com/opengeoweb/opengeoweb/-/issues/726 insert (react-hook-form) does not work well with focus (only last row will get focus), so added this dirty solution. This can be fixed in a better way in react-hook-form v7
3785
+
3786
+
3787
+ var focusFirstInput = function focusFirstInput(rowIndex) {
3788
+ var nextRowFirstInput = document.querySelector("[name=\"changeGroups[" + rowIndex + "].probability\"]");
3789
+
3790
+ if (nextRowFirstInput) {
3791
+ nextRowFirstInput.focus();
3792
+ }
3612
3793
  };
3613
3794
 
3614
- var onAddRow = function onAddRow() {
3615
- append(emptyChangegroup, true);
3795
+ var onAddRow = function onAddRow(rowIndex) {
3796
+ if (rowIndex === void 0) {
3797
+ rowIndex = -1;
3798
+ }
3799
+
3800
+ var newRowIndex = rowIndex + 1;
3801
+ insert(newRowIndex, emptyChangegroup, false); // adding rows and validation don't work well combined, with always skipping validation of fields of rows above the one the inserted row. A setTimeout prevents this issue
3802
+
3803
+ setTimeout(function () {
3804
+ focusFirstInput(newRowIndex);
3805
+ trigger(); // trigger all fields validation
3806
+ }, 0);
3616
3807
  };
3617
3808
 
3618
3809
  React__default["default"].useEffect(function () {
3619
- if (isFormDirty && fields.length !== lastTotalFields) {
3620
- onFormBlur();
3621
- trigger(); // trigger all field validation,
3810
+ if (!isDisabled) {
3811
+ // trigger validation on all changegroup validity times after initial load since they could be outside the new validity period
3812
+ var validFields = fields.map(function (_field, index) {
3813
+ return "changeGroups[" + index + "].valid";
3814
+ });
3815
+ trigger(validFields);
3622
3816
  } // eslint-disable-next-line react-hooks/exhaustive-deps
3623
3817
 
3624
- }, [fields.length, isFormDirty]);
3818
+ }, []);
3625
3819
  return /*#__PURE__*/React__default["default"].createElement("form", {
3626
3820
  onSubmit: handleSubmit(onSubmit),
3627
3821
  onBlur: onFormBlur
@@ -3637,7 +3831,8 @@
3637
3831
  "data-testid": "tafform-tac-message-line"
3638
3832
  }, line);
3639
3833
  })), /*#__PURE__*/React__default["default"].createElement(TafFormRow, {
3640
- disabled: isDisabled
3834
+ disabled: isDisabled,
3835
+ onAddChangeGroup: onAddRow
3641
3836
  }), /*#__PURE__*/React__default["default"].createElement(reactSortablejs.ReactSortable, {
3642
3837
  tag: "div",
3643
3838
  list: fields,
@@ -3661,6 +3856,7 @@
3661
3856
  lastRow: fields.length === index + 1,
3662
3857
  disabled: isDisabled,
3663
3858
  onRemoveChangeGroup: onRemoverow,
3859
+ onAddChangeGroup: onAddRow,
3664
3860
  isChangeGroup: true,
3665
3861
  dragHandle: /*#__PURE__*/React__default["default"].createElement(DragHandle, {
3666
3862
  isDisabled: fields.length === 1 || isDisabled,
@@ -3668,20 +3864,10 @@
3668
3864
  index: index
3669
3865
  })
3670
3866
  });
3671
- })), !isDisabled && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(core.Grid, {
3672
- container: true,
3673
- item: true,
3674
- xs: 12,
3675
- justify: "flex-end"
3676
- }, /*#__PURE__*/React__default["default"].createElement(core.IconButton, {
3677
- "aria-label": "add",
3678
- "data-testid": "add-changeGroup",
3679
- onClick: onAddRow,
3680
- className: classes.addButton
3681
- }, /*#__PURE__*/React__default["default"].createElement(icons.Add, null))), /*#__PURE__*/React__default["default"].createElement(TafFormButtons, {
3867
+ })), !isDisabled && /*#__PURE__*/React__default["default"].createElement(TafFormButtons, {
3682
3868
  tafAction: tafAction,
3683
3869
  onTafFormButtonPress: onTafButtonPress
3684
- })), hiddenInputFields.map(function (field) {
3870
+ }), hiddenInputFields.map(function (field) {
3685
3871
  return /*#__PURE__*/React__default["default"].createElement(formFields.ReactHookFormHiddenInput, {
3686
3872
  name: field,
3687
3873
  key: field
@@ -4713,8 +4899,8 @@
4713
4899
  return /*#__PURE__*/React__default["default"].createElement(shared.ConfirmationServiceProvider, null, /*#__PURE__*/React__default["default"].createElement(TafWrapper, null));
4714
4900
  };
4715
4901
 
4716
- var createApi = function createApi(url, appUrl, auth, setAuth, authTokenUrl) {
4717
- var axiosInstance = api.createApiInstance(url, appUrl, auth, setAuth, authTokenUrl);
4902
+ var createApi = function createApi(url, appUrl, auth, setAuth, authTokenUrl, authClientId) {
4903
+ var axiosInstance = api.createApiInstance(url, appUrl, auth, setAuth, authTokenUrl, authClientId);
4718
4904
  return {
4719
4905
  // api data
4720
4906
  getTafList: function getTafList() {