@masterteam/system-settings 0.0.20 → 0.0.22

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.
@@ -489,7 +489,7 @@ class SystemSettings {
489
489
  nonNullable: true,
490
490
  validators: GLOBAL_WORKING_HOURS_WORKING_DAYS_VALIDATORS,
491
491
  }),
492
- hoursPerDay: new FormControl(8, GLOBAL_WORKING_HOURS_HOURS_PER_DAY_VALIDATORS),
492
+ hoursPerDay: new FormControl(8, this.buildHoursPerDayValidators()),
493
493
  holidaysCsv: new FormControl('', { nonNullable: true }),
494
494
  }),
495
495
  }, maxDailyHoursGteHoursPerDay(() => this.hideTimeSheet()));
@@ -714,9 +714,15 @@ class SystemSettings {
714
714
  }
715
715
  const modalRef = this.modal.openModal(SystemSettingsHolidayEditor, 'drawer', {
716
716
  header: this.transloco.translate('system-settings.edit-holiday'),
717
- styleClass: '!w-[30rem] !absolute !shadow-none',
717
+ // Deliberately viewport-fixed (appendTo defaults to 'body', no
718
+ // `!absolute` override) - see DynamicDrawerHost.connect(): appending
719
+ // anywhere other than 'body' drops the `position: fixed` override,
720
+ // and pairing that with a forced `!absolute` used to anchor this
721
+ // drawer to #page-content's document position instead of the
722
+ // viewport, so it rendered off-screen as soon as the page scrolled
723
+ // past Holidays (a card partway down a long Settings page).
724
+ styleClass: '!w-[30rem] !shadow-none',
718
725
  position: 'end',
719
- appendTo: 'page-content',
720
726
  dismissible: true,
721
727
  inputValues: {
722
728
  holiday,
@@ -1007,6 +1013,21 @@ class SystemSettings {
1007
1013
  holidays: parseCsv(globalForm.holidaysCsv),
1008
1014
  };
1009
1015
  }
1016
+ /**
1017
+ * Built via a method (not a module-level constant like the other
1018
+ * GlobalWorkingHours/TimeSheet validator arrays) because
1019
+ * `greaterThanZeroValidator` needs `this.transloco` to resolve its message
1020
+ * - safe to call from the `form` field initializer since `transloco` is
1021
+ * declared and `inject()`-ed earlier in the class body and field
1022
+ * initializers run in declaration order.
1023
+ */
1024
+ buildHoursPerDayValidators() {
1025
+ return [
1026
+ Validators.required,
1027
+ greaterThanZeroValidator(() => this.transloco.translate('system-settings.daily-working-hours-min-message')),
1028
+ Validators.max(24),
1029
+ ];
1030
+ }
1010
1031
  findSettingByKey(key) {
1011
1032
  const normalizedKey = key.trim().toLowerCase();
1012
1033
  return (this.settings()?.find((setting) => setting.key.trim().toLowerCase() === normalizedKey) ?? null);
@@ -1356,14 +1377,21 @@ class SystemSettings {
1356
1377
  * Builds a localized, human-readable label for a date/time format key by
1357
1378
  * translating each format token (e.g. `dd-MM-yyyy` → `يوم-شهر-سنة`) while
1358
1379
  * preserving the original separators. Unknown characters are left as-is.
1380
+ *
1381
+ * `H` (24-hour) and `h` (12-hour) map to distinct translation keys -
1382
+ * NOT the same `format-token-hour` key. They used to share one key, which
1383
+ * made 'H:mm:ss' and 'hh:mm:ss' render as the identical label
1384
+ * "Hour:Minute:Second" despite submitting genuinely different backend
1385
+ * values (`H:mm:ss` vs `hh:mm:ss`), so an admin had no way to tell the two
1386
+ * Time Format options apart before selecting one.
1359
1387
  */
1360
1388
  formatOptionLabel(formatKey) {
1361
1389
  const tokenKeyByLetter = {
1362
1390
  y: 'format-token-year',
1363
1391
  M: 'format-token-month',
1364
1392
  d: 'format-token-day',
1365
- H: 'format-token-hour',
1366
- h: 'format-token-hour',
1393
+ H: 'format-token-hour-24',
1394
+ h: 'format-token-hour-12',
1367
1395
  m: 'format-token-minute',
1368
1396
  s: 'format-token-second',
1369
1397
  t: 'format-token-period',
@@ -1394,7 +1422,12 @@ class SystemSettings {
1394
1422
  { id: '{0:#,0.0}', name: `1,000.0 ${unitLabel}` },
1395
1423
  { id: '{0:#,0.00}', name: `1,000.00 ${unitLabel}` },
1396
1424
  { id: '{0:#,0, K}', name: `1 K ${unitLabel}` },
1397
- { id: '{0:N3}', name: `1,000 N3 ${unitLabel}` },
1425
+ // Was `1,000 N3 ${unitLabel}` - a leftover placeholder that pasted the
1426
+ // .NET format specifier itself into the label instead of what it
1427
+ // renders. N3 = thousands-separated with 3 decimal places, matching
1428
+ // the `{0:#,0.00}` entry above's convention of writing out the actual
1429
+ // formatted example rather than the format code.
1430
+ { id: '{0:N3}', name: `1,000.000 ${unitLabel}` },
1398
1431
  ];
1399
1432
  }
1400
1433
  getCurrencyFormatLabel(currency) {
@@ -1600,11 +1633,32 @@ const TIME_SHEET_SUBMISSION_DEADLINE_VALIDATORS = [
1600
1633
  ];
1601
1634
  const GLOBAL_WORKING_HOURS_TIMEZONE_VALIDATORS = [Validators.required];
1602
1635
  const GLOBAL_WORKING_HOURS_WORKING_DAYS_VALIDATORS = [Validators.required];
1603
- const GLOBAL_WORKING_HOURS_HOURS_PER_DAY_VALIDATORS = [
1604
- Validators.required,
1605
- Validators.min(0),
1606
- Validators.max(24),
1607
- ];
1636
+ /**
1637
+ * `hoursPerDay` ("Daily Working Hours") needs an *exclusive* lower bound -
1638
+ * `0` itself must be rejected, unlike every other range on this page which
1639
+ * uses Angular's inclusive `Validators.min`. There's no built-in "greater
1640
+ * than" validator, and reusing `Validators.min(0)`'s error would render the
1641
+ * wrong message ("must be at least 0" on a control that just rejected 0), so
1642
+ * this returns a distinct `dailyWorkingHoursMin` error carrying its own
1643
+ * pre-translated `.message` - `ErrorMessagePipe` prefers a `.message` on the
1644
+ * error object over its generic per-key translations (see
1645
+ * `field-validation/error-message.pipe.ts`), so no changes are needed there
1646
+ * or to its shared i18n keys.
1647
+ *
1648
+ * Mirrors `Validators.min`'s own null-handling: an empty control is left to
1649
+ * `Validators.required` rather than also failing this check.
1650
+ */
1651
+ function greaterThanZeroValidator(message) {
1652
+ return (control) => {
1653
+ const value = control.value;
1654
+ if (value === null || value === undefined || value === '') {
1655
+ return null;
1656
+ }
1657
+ return typeof value === 'number' && value > 0
1658
+ ? null
1659
+ : { dailyWorkingHoursMin: { min: 0, message: message() } };
1660
+ };
1661
+ }
1608
1662
  /**
1609
1663
  * Cross-field validator comparing `timeSheetPolicy.maxDailyHours` against
1610
1664
  * `globalWorkingHours.hoursPerDay`. Takes the `hideTimeSheet` input as a
@@ -2835,7 +2889,7 @@ function isTestRequestConflict(error) {
2835
2889
  const EMAIL_DELIVERY_ROUTES = [
2836
2890
  {
2837
2891
  path: '',
2838
- loadComponent: () => import('./masterteam-system-settings-email-delivery-shell-CBPKuYXr.mjs').then((module) => module.EmailDeliveryShell),
2892
+ loadComponent: () => import('./masterteam-system-settings-email-delivery-shell-oTWOzKWj.mjs').then((module) => module.EmailDeliveryShell),
2839
2893
  },
2840
2894
  { path: '**', redirectTo: '' },
2841
2895
  ];
@@ -2845,4 +2899,4 @@ const EMAIL_DELIVERY_ROUTES = [
2845
2899
  */
2846
2900
 
2847
2901
  export { DEFAULT_GENERAL_CONFIGURATION as D, EmailDeliveryFacade as E, FIXED_CURRENCIES as F, GENERAL_CONFIGURATION_SETTING_KEY as G, RATE_SOURCE_OPTIONS as R, SystemSettings as S, TIMESHEET_FREQUENCY_OPTIONS as T, WEEKEND_DAY_DEFINITIONS as W, EmailDeliveryStatus as a, EMAIL_DELIVERY_ATTEMPT_STATUSES as b, createEmailTestRequestId as c, areEmailTestRecipientsValid as d, isEmailTestRequestPending as e, EMAIL_DELIVERY_PERMISSIONS as f, getEmailDeliveryStatusMap as g, EXPLORE_DEFAULT_VIEW_OPTIONS as h, isConnectionTestAttempt as i, DEFAULT_DATE_FORMAT_OPTIONS as j, DEFAULT_TIME_FORMAT_OPTIONS as k, DOTNET_DAY_DEFINITIONS as l, SCHEDULE_ITEM_TYPE_OPTIONS as m, SystemSettingsHolidayEditor as n, DEFAULT_EMAIL_DELIVERY_OPTIONS as o, parseEmailTestRecipients as p, EMAIL_DELIVERY_OPTIONS as q, provideEmailDelivery as r, EmailDeliveryAccessService as s, collectEmailTemplatePlaceholders as t, buildEmailTemplatePlaceholderFields as u, EmailUserTestDialog as v, EMAIL_USER_INITIATED_TEST_OPERATION_KIND as w, EMAIL_DELIVERY_ROUTES as x };
2848
- //# sourceMappingURL=masterteam-system-settings-masterteam-system-settings-DbsjQ7c7.mjs.map
2902
+ //# sourceMappingURL=masterteam-system-settings-masterteam-system-settings-7I4TfgwP.mjs.map