@masterteam/system-settings 0.0.19 → 0.0.21

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.
@@ -205,8 +205,15 @@ class SystemSettingsTimeSheetSection {
205
205
  dayOptions = input([], ...(ngDevMode ? [{ debugName: "dayOptions" }] : /* istanbul ignore next */ []));
206
206
  frequencyOptions = input([], ...(ngDevMode ? [{ debugName: "frequencyOptions" }] : /* istanbul ignore next */ []));
207
207
  maxDailyBelowDaily = input(false, ...(ngDevMode ? [{ debugName: "maxDailyBelowDaily" }] : /* istanbul ignore next */ []));
208
+ /**
209
+ * Hides the Timesheet Policy card only. Global Working Hours always
210
+ * renders - it is host-relevant on its own (working days/hours feed
211
+ * scheduling and calendar logic beyond timesheets), so a host without a
212
+ * timesheet feature can still configure it.
213
+ */
214
+ hidePolicy = input(false, ...(ngDevMode ? [{ debugName: "hidePolicy" }] : /* istanbul ignore next */ []));
208
215
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettingsTimeSheetSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
209
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: SystemSettingsTimeSheetSection, isStandalone: true, selector: "mt-system-settings-timesheet-section", inputs: { policyForm: { classPropertyName: "policyForm", publicName: "policyForm", isSignal: true, isRequired: true, transformFunction: null }, globalWorkingHoursForm: { classPropertyName: "globalWorkingHoursForm", publicName: "globalWorkingHoursForm", isSignal: true, isRequired: true, transformFunction: null }, dayOptions: { classPropertyName: "dayOptions", publicName: "dayOptions", isSignal: true, isRequired: false, transformFunction: null }, frequencyOptions: { classPropertyName: "frequencyOptions", publicName: "frequencyOptions", isSignal: true, isRequired: false, transformFunction: null }, maxDailyBelowDaily: { classPropertyName: "maxDailyBelowDaily", publicName: "maxDailyBelowDaily", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <div class=\"grid gap-6 xl:grid-cols-2 mb-6\">\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"general-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"general-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"frequency\"\r\n [options]=\"frequencyOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('frequency')\"\r\n [placeholder]=\"t('frequency-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"maxDailyHours\"\r\n [label]=\"t('max-daily-hours')\"\r\n [placeholder]=\"t('max-daily-hours-placeholder')\"\r\n [min]=\"1\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"submissionDeadlineDays\"\r\n [label]=\"t('submission-deadline-days')\"\r\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"90\"\r\n [required]=\"true\"\r\n [hint]=\"t('submission-deadline-hint')\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n formControlName=\"requireDescription\"\r\n [label]=\"t('require-description')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowFutureEntry\"\r\n [label]=\"t('allow-future-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowBackdatedEntry\"\r\n [label]=\"t('allow-backdated-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"autoLockOnApprove\"\r\n [label]=\"t('auto-lock-on-approve')\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"working-hours-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"working-hours-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-multi-select-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"workingDays\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('working-days')\"\r\n [placeholder]=\"t('working-days-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"hoursPerDay\"\r\n [label]=\"t('daily-working-hours')\"\r\n [placeholder]=\"t('daily-working-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: MultiSelectField, selector: "mt-multi-select-field", inputs: ["field", "label", "placeholder", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "display", "required", "maxSelectedLabels", "group", "virtualScroll", "virtualScrollItemSize", "optionGroupLabel", "optionGroupChildren", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape"], outputs: ["onChange"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
216
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: SystemSettingsTimeSheetSection, isStandalone: true, selector: "mt-system-settings-timesheet-section", inputs: { policyForm: { classPropertyName: "policyForm", publicName: "policyForm", isSignal: true, isRequired: true, transformFunction: null }, globalWorkingHoursForm: { classPropertyName: "globalWorkingHoursForm", publicName: "globalWorkingHoursForm", isSignal: true, isRequired: true, transformFunction: null }, dayOptions: { classPropertyName: "dayOptions", publicName: "dayOptions", isSignal: true, isRequired: false, transformFunction: null }, frequencyOptions: { classPropertyName: "frequencyOptions", publicName: "frequencyOptions", isSignal: true, isRequired: false, transformFunction: null }, maxDailyBelowDaily: { classPropertyName: "maxDailyBelowDaily", publicName: "maxDailyBelowDaily", isSignal: true, isRequired: false, transformFunction: null }, hidePolicy: { classPropertyName: "hidePolicy", publicName: "hidePolicy", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <div\r\n [class]=\"\r\n hidePolicy() ? 'grid gap-6 mb-6' : 'grid gap-6 xl:grid-cols-2 mb-6'\r\n \"\r\n >\r\n @if (!hidePolicy()) {\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"general-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"general-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"frequency\"\r\n [options]=\"frequencyOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('frequency')\"\r\n [placeholder]=\"t('frequency-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"maxDailyHours\"\r\n [label]=\"t('max-daily-hours')\"\r\n [placeholder]=\"t('max-daily-hours-placeholder')\"\r\n [min]=\"1\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"submissionDeadlineDays\"\r\n [label]=\"t('submission-deadline-days')\"\r\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"90\"\r\n [required]=\"true\"\r\n [hint]=\"t('submission-deadline-hint')\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n formControlName=\"requireDescription\"\r\n [label]=\"t('require-description')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowFutureEntry\"\r\n [label]=\"t('allow-future-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowBackdatedEntry\"\r\n [label]=\"t('allow-backdated-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"autoLockOnApprove\"\r\n [label]=\"t('auto-lock-on-approve')\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"working-hours-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"working-hours-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-multi-select-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"workingDays\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('working-days')\"\r\n [placeholder]=\"t('working-days-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"hoursPerDay\"\r\n [label]=\"t('daily-working-hours')\"\r\n [placeholder]=\"t('daily-working-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: MultiSelectField, selector: "mt-multi-select-field", inputs: ["field", "label", "placeholder", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "display", "required", "maxSelectedLabels", "group", "virtualScroll", "virtualScrollItemSize", "optionGroupLabel", "optionGroupChildren", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape"], outputs: ["onChange"] }, { kind: "component", type: NumberField, selector: "mt-number-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "pInputs", "format", "useGrouping", "maxFractionDigits", "min", "max", "required"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
210
217
  }
211
218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettingsTimeSheetSection, decorators: [{
212
219
  type: Component,
@@ -218,8 +225,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
218
225
  NumberField,
219
226
  SelectField,
220
227
  ToggleField,
221
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <div class=\"grid gap-6 xl:grid-cols-2 mb-6\">\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"general-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"general-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"frequency\"\r\n [options]=\"frequencyOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('frequency')\"\r\n [placeholder]=\"t('frequency-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"maxDailyHours\"\r\n [label]=\"t('max-daily-hours')\"\r\n [placeholder]=\"t('max-daily-hours-placeholder')\"\r\n [min]=\"1\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"submissionDeadlineDays\"\r\n [label]=\"t('submission-deadline-days')\"\r\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"90\"\r\n [required]=\"true\"\r\n [hint]=\"t('submission-deadline-hint')\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n formControlName=\"requireDescription\"\r\n [label]=\"t('require-description')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowFutureEntry\"\r\n [label]=\"t('allow-future-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowBackdatedEntry\"\r\n [label]=\"t('allow-backdated-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"autoLockOnApprove\"\r\n [label]=\"t('auto-lock-on-approve')\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"working-hours-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"working-hours-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-multi-select-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"workingDays\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('working-days')\"\r\n [placeholder]=\"t('working-days-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"hoursPerDay\"\r\n [label]=\"t('daily-working-hours')\"\r\n [placeholder]=\"t('daily-working-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n </div>\r\n</ng-container>\r\n" }]
222
- }], propDecorators: { policyForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "policyForm", required: true }] }], globalWorkingHoursForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalWorkingHoursForm", required: true }] }], dayOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayOptions", required: false }] }], frequencyOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "frequencyOptions", required: false }] }], maxDailyBelowDaily: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDailyBelowDaily", required: false }] }] } });
228
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <div\r\n [class]=\"\r\n hidePolicy() ? 'grid gap-6 mb-6' : 'grid gap-6 xl:grid-cols-2 mb-6'\r\n \"\r\n >\r\n @if (!hidePolicy()) {\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"general-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"general-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"frequency\"\r\n [options]=\"frequencyOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('frequency')\"\r\n [placeholder]=\"t('frequency-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"maxDailyHours\"\r\n [label]=\"t('max-daily-hours')\"\r\n [placeholder]=\"t('max-daily-hours-placeholder')\"\r\n [min]=\"1\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"submissionDeadlineDays\"\r\n [label]=\"t('submission-deadline-days')\"\r\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"90\"\r\n [required]=\"true\"\r\n [hint]=\"t('submission-deadline-hint')\"\r\n />\r\n </div>\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2\">\r\n <mt-toggle-field\r\n formControlName=\"requireDescription\"\r\n [label]=\"t('require-description')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowFutureEntry\"\r\n [label]=\"t('allow-future-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowBackdatedEntry\"\r\n [label]=\"t('allow-backdated-entry')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"autoLockOnApprove\"\r\n [label]=\"t('auto-lock-on-approve')\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n <mt-card class=\"block\">\r\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\r\n <div class=\"space-y-1\">\r\n <h4 class=\"text-base font-semibold text-neutral-900\">\r\n {{ t(\"working-hours-card-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"working-hours-card-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-multi-select-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"workingDays\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('working-days')\"\r\n [placeholder]=\"t('working-days-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"hoursPerDay\"\r\n [label]=\"t('daily-working-hours')\"\r\n [placeholder]=\"t('daily-working-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\r\n [required]=\"true\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n </div>\r\n</ng-container>\r\n" }]
229
+ }], propDecorators: { policyForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "policyForm", required: true }] }], globalWorkingHoursForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalWorkingHoursForm", required: true }] }], dayOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayOptions", required: false }] }], frequencyOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "frequencyOptions", required: false }] }], maxDailyBelowDaily: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDailyBelowDaily", required: false }] }], hidePolicy: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePolicy", required: false }] }] } });
223
230
 
224
231
  /** The settings record General Configuration fields live in. */
225
232
  const GENERAL_CONFIGURATION_SETTING_KEY = 'Identity';
@@ -267,6 +274,11 @@ const EXPLORE_DEFAULT_VIEW_OPTIONS = [
267
274
  translationKey: 'explore-default-view-timeline',
268
275
  icon: 'time.clock',
269
276
  },
277
+ {
278
+ id: 'table',
279
+ translationKey: 'explore-default-view-table',
280
+ icon: 'layout.table',
281
+ },
270
282
  ];
271
283
  const DEFAULT_GENERAL_CONFIGURATION = {
272
284
  defaultExploreView: 'cards',
@@ -385,23 +397,61 @@ class SystemSettings {
385
397
  transloco = inject(TranslocoService);
386
398
  modal = inject(ModalService);
387
399
  toast = inject(ToastService);
388
- apiBasePath = input('settings', ...(ngDevMode ? [{ debugName: "apiBasePath" }] : /* istanbul ignore next */ []));
389
- backRoute = input('/control-panel/product-settings', ...(ngDevMode ? [{ debugName: "backRoute" }] : /* istanbul ignore next */ []));
400
+ /**
401
+ * Base path for the settings API. Safe to omit from a route's `data`
402
+ * even when the host uses `withComponentInputBinding()` - see
403
+ * `defaultWhenUndefined`.
404
+ */
405
+ apiBasePath = input('settings', { ...(ngDevMode ? { debugName: "apiBasePath" } : /* istanbul ignore next */ {}), transform: defaultWhenUndefined('settings') });
406
+ /**
407
+ * Route the page's back button navigates to; falsy (including an
408
+ * explicit `null`) disables it - see `goBack()`. Safe to omit from a
409
+ * route's `data` even when the host uses `withComponentInputBinding()`
410
+ * - see `defaultWhenUndefined`.
411
+ */
412
+ backRoute = input('/control-panel/product-settings', { ...(ngDevMode ? { debugName: "backRoute" } : /* istanbul ignore next */ {}), transform: defaultWhenUndefined('/control-panel/product-settings') });
390
413
  formatSchema = input(null, ...(ngDevMode ? [{ debugName: "formatSchema" }] : /* istanbul ignore next */ []));
391
- breadcrumbItems = [
392
- {
393
- label: '',
394
- icon: 'general.home-line',
395
- routerLink: '/control-panel/workspaces',
396
- },
397
- {
398
- label: this.transloco.translate('product-settings.product-settings'),
399
- routerLink: '/control-panel/product-settings',
400
- },
401
- {
402
- label: this.transloco.translate('system-settings.page-title'),
403
- },
404
- ];
414
+ /**
415
+ * Breadcrumb "home" link. `null` renders the crumb as non-navigable.
416
+ * Safe to omit from a route's `data` even when the host uses
417
+ * `withComponentInputBinding()` - see `defaultWhenUndefined`.
418
+ */
419
+ homeRoute = input('/control-panel/workspaces', { ...(ngDevMode ? { debugName: "homeRoute" } : /* istanbul ignore next */ {}), transform: defaultWhenUndefined('/control-panel/workspaces') });
420
+ /**
421
+ * Hides the Timesheet Policy card only - Global Working Hours stays
422
+ * visible and required, since it's host-relevant on its own (working
423
+ * days/hours feed scheduling and calendar logic beyond timesheets).
424
+ * TimeSheetPolicy's fields lose their validators and are excluded from
425
+ * save diffing while hidden; GlobalWorkingHours is unaffected.
426
+ */
427
+ hideTimeSheet = input(false, ...(ngDevMode ? [{ debugName: "hideTimeSheet" }] : /* istanbul ignore next */ []));
428
+ /**
429
+ * Restricts which Explore-default-view options are offered. `null` (the
430
+ * default) preserves pplus5's original two options (`'cards'`,
431
+ * `'timeline'`) - NOT the full catalog, since the catalog now also
432
+ * contains `'table'` (added for splus4) and an unset input must not
433
+ * silently surface it to hosts that never opted in. Pass the ids
434
+ * explicitly (e.g. `['cards', 'table']`, or all three) to get anything
435
+ * else.
436
+ */
437
+ exploreViewOptions = input(null, ...(ngDevMode ? [{ debugName: "exploreViewOptions" }] : /* istanbul ignore next */ []));
438
+ breadcrumbItems = computed(() => {
439
+ const homeRoute = this.homeRoute();
440
+ return [
441
+ {
442
+ label: '',
443
+ icon: 'general.home-line',
444
+ ...(homeRoute ? { routerLink: homeRoute } : {}),
445
+ },
446
+ {
447
+ label: this.transloco.translate('product-settings.product-settings'),
448
+ routerLink: '/control-panel/product-settings',
449
+ },
450
+ {
451
+ label: this.transloco.translate('system-settings.page-title'),
452
+ },
453
+ ];
454
+ }, ...(ngDevMode ? [{ debugName: "breadcrumbItems" }] : /* istanbul ignore next */ []));
405
455
  form = new FormGroup({
406
456
  generalConfiguration: new FormGroup({
407
457
  defaultExploreView: new FormControl('cards', {
@@ -425,36 +475,24 @@ class SystemSettings {
425
475
  days: new FormControl([], { nonNullable: true }),
426
476
  }),
427
477
  timeSheetPolicy: new FormGroup({
428
- frequency: new FormControl('Weekly', Validators.required),
429
- maxDailyHours: new FormControl(24, [
430
- Validators.required,
431
- Validators.min(1),
432
- Validators.max(24),
433
- ]),
478
+ frequency: new FormControl('Weekly', TIME_SHEET_FREQUENCY_VALIDATORS),
479
+ maxDailyHours: new FormControl(24, TIME_SHEET_MAX_DAILY_HOURS_VALIDATORS),
434
480
  requireDescription: new FormControl(false, { nonNullable: true }),
435
481
  allowFutureEntry: new FormControl(false, { nonNullable: true }),
436
482
  allowBackdatedEntry: new FormControl(true, { nonNullable: true }),
437
483
  autoLockOnApprove: new FormControl(true, { nonNullable: true }),
438
- submissionDeadlineDays: new FormControl(3, [
439
- Validators.required,
440
- Validators.min(0),
441
- Validators.max(90),
442
- ]),
484
+ submissionDeadlineDays: new FormControl(3, TIME_SHEET_SUBMISSION_DEADLINE_VALIDATORS),
443
485
  }),
444
486
  globalWorkingHours: new FormGroup({
445
- timeZoneId: new FormControl(null, Validators.required),
487
+ timeZoneId: new FormControl(null, GLOBAL_WORKING_HOURS_TIMEZONE_VALIDATORS),
446
488
  workingDays: new FormControl([0, 1, 2, 3, 4], {
447
489
  nonNullable: true,
448
- validators: [Validators.required],
490
+ validators: GLOBAL_WORKING_HOURS_WORKING_DAYS_VALIDATORS,
449
491
  }),
450
- hoursPerDay: new FormControl(8, [
451
- Validators.required,
452
- Validators.min(0),
453
- Validators.max(24),
454
- ]),
492
+ hoursPerDay: new FormControl(8, GLOBAL_WORKING_HOURS_HOURS_PER_DAY_VALIDATORS),
455
493
  holidaysCsv: new FormControl('', { nonNullable: true }),
456
494
  }),
457
- }, maxDailyHoursGteHoursPerDay);
495
+ }, maxDailyHoursGteHoursPerDay(() => this.hideTimeSheet()));
458
496
  settings = signal(null, ...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
459
497
  settingsLoading = signal(false, ...(ngDevMode ? [{ debugName: "settingsLoading" }] : /* istanbul ignore next */ []));
460
498
  settingsError = signal(null, ...(ngDevMode ? [{ debugName: "settingsError" }] : /* istanbul ignore next */ []));
@@ -525,9 +563,25 @@ class SystemSettings {
525
563
  name: this.transloco.translate(`system-settings.${day.translationKey}`),
526
564
  }));
527
565
  }, ...(ngDevMode ? [{ debugName: "dotnetDayOptions" }] : /* istanbul ignore next */ []));
566
+ /**
567
+ * The Explore-default-view ids actually offered, in the fixed catalog's
568
+ * order: `exploreViewOptions()` when the host sets it, else the legacy
569
+ * pplus5 pair (`DEFAULT_EXPLORE_VIEW_OPTIONS`) - deliberately NOT the full
570
+ * `EXPLORE_DEFAULT_VIEW_OPTIONS` catalog, since that now includes `'table'`
571
+ * and an unconfigured host must keep seeing exactly what it saw before.
572
+ * Filtered against the catalog either way, so an invalid/unknown id in a
573
+ * host's list is silently dropped rather than offered. Always non-empty
574
+ * unless a host passes an empty array.
575
+ */
576
+ allowedExploreDefaultViews = computed(() => {
577
+ const configured = this.exploreViewOptions() ?? DEFAULT_EXPLORE_VIEW_OPTIONS;
578
+ const configuredSet = new Set(configured);
579
+ return EXPLORE_DEFAULT_VIEW_OPTIONS.map((option) => option.id).filter((id) => configuredSet.has(id));
580
+ }, ...(ngDevMode ? [{ debugName: "allowedExploreDefaultViews" }] : /* istanbul ignore next */ []));
528
581
  exploreDefaultViewOptions = computed(() => {
529
582
  this.activeLang();
530
- return EXPLORE_DEFAULT_VIEW_OPTIONS.map((option) => ({
583
+ const allowed = new Set(this.allowedExploreDefaultViews());
584
+ return EXPLORE_DEFAULT_VIEW_OPTIONS.filter((option) => allowed.has(option.id)).map((option) => ({
531
585
  id: option.id,
532
586
  name: this.transloco.translate(`system-settings.${option.translationKey}`),
533
587
  icon: option.icon,
@@ -558,7 +612,12 @@ class SystemSettings {
558
612
  !this.areEqual(initial.timezone, current.timezone) ||
559
613
  !this.areEqual(initial.currency, current.currency) ||
560
614
  !this.areEqual(initial.weekend, current.weekend) ||
561
- !this.areEqual(initial.timeSheetPolicy, current.timeSheetPolicy) ||
615
+ // Mirrors onSave()'s exclusion below: while Timesheet Policy is
616
+ // hidden nothing in the UI can change these fields (the card isn't
617
+ // rendered), but keeping this guard symmetric with onSave() means
618
+ // hasChanges() and the actual save payload can never disagree.
619
+ (!this.hideTimeSheet() &&
620
+ !this.areEqual(initial.timeSheetPolicy, current.timeSheetPolicy)) ||
562
621
  !this.areEqual(initial.globalWorkingHours, current.globalWorkingHours));
563
622
  }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : /* istanbul ignore next */ []));
564
623
  canSaveSettings = computed(() => !this.isLoading() &&
@@ -573,6 +632,30 @@ class SystemSettings {
573
632
  to: formatDateOnly(holiday.to),
574
633
  })), ...(ngDevMode ? [{ debugName: "holidayTableRows" }] : /* istanbul ignore next */ []));
575
634
  constructor() {
635
+ // Only Timesheet Policy is conditionally hidden - Global Working Hours
636
+ // keeps its validators unconditionally (set once at construction, see
637
+ // the form below), since that card is always visible.
638
+ effect(() => {
639
+ const hidden = this.hideTimeSheet();
640
+ const timeSheetPolicy = this.form.controls.timeSheetPolicy.controls;
641
+ if (hidden) {
642
+ timeSheetPolicy.frequency.clearValidators();
643
+ timeSheetPolicy.maxDailyHours.clearValidators();
644
+ timeSheetPolicy.submissionDeadlineDays.clearValidators();
645
+ }
646
+ else {
647
+ timeSheetPolicy.frequency.setValidators(TIME_SHEET_FREQUENCY_VALIDATORS);
648
+ timeSheetPolicy.maxDailyHours.setValidators(TIME_SHEET_MAX_DAILY_HOURS_VALIDATORS);
649
+ timeSheetPolicy.submissionDeadlineDays.setValidators(TIME_SHEET_SUBMISSION_DEADLINE_VALIDATORS);
650
+ }
651
+ for (const control of [
652
+ timeSheetPolicy.frequency,
653
+ timeSheetPolicy.maxDailyHours,
654
+ timeSheetPolicy.submissionDeadlineDays,
655
+ ]) {
656
+ control.updateValueAndValidity({ emitEvent: false });
657
+ }
658
+ });
576
659
  effect(() => {
577
660
  const version = [
578
661
  this.settingsLastUpdated() ?? 'none',
@@ -631,9 +714,15 @@ class SystemSettings {
631
714
  }
632
715
  const modalRef = this.modal.openModal(SystemSettingsHolidayEditor, 'drawer', {
633
716
  header: this.transloco.translate('system-settings.edit-holiday'),
634
- 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',
635
725
  position: 'end',
636
- appendTo: 'page-content',
637
726
  dismissible: true,
638
727
  inputValues: {
639
728
  holiday,
@@ -694,7 +783,8 @@ class SystemSettings {
694
783
  value: nextSnapshot.weekend,
695
784
  });
696
785
  }
697
- if (!this.areEqual(initialSnapshot.timeSheetPolicy, nextSnapshot.timeSheetPolicy)) {
786
+ if (!this.hideTimeSheet() &&
787
+ !this.areEqual(initialSnapshot.timeSheetPolicy, nextSnapshot.timeSheetPolicy)) {
698
788
  values.push({
699
789
  key: 'TimeSheetPolicy',
700
790
  value: nextSnapshot.timeSheetPolicy,
@@ -890,7 +980,7 @@ class SystemSettings {
890
980
  }
891
981
  buildGeneralConfigurationValue() {
892
982
  return {
893
- defaultExploreView: normalizeExploreDefaultView(this.form.controls.generalConfiguration.controls.defaultExploreView
983
+ defaultExploreView: this.normalizeExploreDefaultView(this.form.controls.generalConfiguration.controls.defaultExploreView
894
984
  .value),
895
985
  showDesignSwitcher: this.form.controls.generalConfiguration.controls.showDesignSwitcher
896
986
  .value === true,
@@ -932,7 +1022,7 @@ class SystemSettings {
932
1022
  return { ...DEFAULT_GENERAL_CONFIGURATION };
933
1023
  }
934
1024
  return {
935
- defaultExploreView: normalizeExploreDefaultView(setting.value['defaultExploreView'] ??
1025
+ defaultExploreView: this.normalizeExploreDefaultView(setting.value['defaultExploreView'] ??
936
1026
  setting.value['DefaultExploreView']),
937
1027
  showDesignSwitcher: normalizeShowDesignSwitcher(setting.value['showDesignSwitcher'] ??
938
1028
  setting.value['ShowDesignSwitcher']),
@@ -1086,10 +1176,24 @@ class SystemSettings {
1086
1176
  }
1087
1177
  normalizeGeneralConfigurationSnapshot(value) {
1088
1178
  return {
1089
- defaultExploreView: normalizeExploreDefaultView(value.defaultExploreView),
1179
+ defaultExploreView: this.normalizeExploreDefaultView(value.defaultExploreView),
1090
1180
  showDesignSwitcher: normalizeShowDesignSwitcher(value.showDesignSwitcher),
1091
1181
  };
1092
1182
  }
1183
+ /**
1184
+ * Resolves a stored/incoming value against the currently allowed Explore
1185
+ * views (`allowedExploreDefaultViews()`), not the full static catalog, so a
1186
+ * host-restricted `exploreViewOptions` is enforced on both hydration and
1187
+ * save. Falls back to the first allowed option - not a hardcoded 'cards' -
1188
+ * since a host may configure a subset that excludes it.
1189
+ */
1190
+ normalizeExploreDefaultView(value) {
1191
+ const allowed = this.allowedExploreDefaultViews();
1192
+ if (allowed.some((id) => id === value)) {
1193
+ return value;
1194
+ }
1195
+ return allowed[0] ?? DEFAULT_GENERAL_CONFIGURATION.defaultExploreView;
1196
+ }
1093
1197
  normalizeTimezoneSnapshot(value) {
1094
1198
  const defaults = this.getDefaultTimezoneValue();
1095
1199
  return {
@@ -1258,14 +1362,21 @@ class SystemSettings {
1258
1362
  * Builds a localized, human-readable label for a date/time format key by
1259
1363
  * translating each format token (e.g. `dd-MM-yyyy` → `يوم-شهر-سنة`) while
1260
1364
  * preserving the original separators. Unknown characters are left as-is.
1365
+ *
1366
+ * `H` (24-hour) and `h` (12-hour) map to distinct translation keys -
1367
+ * NOT the same `format-token-hour` key. They used to share one key, which
1368
+ * made 'H:mm:ss' and 'hh:mm:ss' render as the identical label
1369
+ * "Hour:Minute:Second" despite submitting genuinely different backend
1370
+ * values (`H:mm:ss` vs `hh:mm:ss`), so an admin had no way to tell the two
1371
+ * Time Format options apart before selecting one.
1261
1372
  */
1262
1373
  formatOptionLabel(formatKey) {
1263
1374
  const tokenKeyByLetter = {
1264
1375
  y: 'format-token-year',
1265
1376
  M: 'format-token-month',
1266
1377
  d: 'format-token-day',
1267
- H: 'format-token-hour',
1268
- h: 'format-token-hour',
1378
+ H: 'format-token-hour-24',
1379
+ h: 'format-token-hour-12',
1269
1380
  m: 'format-token-minute',
1270
1381
  s: 'format-token-second',
1271
1382
  t: 'format-token-period',
@@ -1296,7 +1407,12 @@ class SystemSettings {
1296
1407
  { id: '{0:#,0.0}', name: `1,000.0 ${unitLabel}` },
1297
1408
  { id: '{0:#,0.00}', name: `1,000.00 ${unitLabel}` },
1298
1409
  { id: '{0:#,0, K}', name: `1 K ${unitLabel}` },
1299
- { id: '{0:N3}', name: `1,000 N3 ${unitLabel}` },
1410
+ // Was `1,000 N3 ${unitLabel}` - a leftover placeholder that pasted the
1411
+ // .NET format specifier itself into the label instead of what it
1412
+ // renders. N3 = thousands-separated with 3 decimal places, matching
1413
+ // the `{0:#,0.00}` entry above's convention of writing out the actual
1414
+ // formatted example rather than the format code.
1415
+ { id: '{0:N3}', name: `1,000.000 ${unitLabel}` },
1300
1416
  ];
1301
1417
  }
1302
1418
  getCurrencyFormatLabel(currency) {
@@ -1337,7 +1453,7 @@ class SystemSettings {
1337
1453
  return JSON.stringify(left) === JSON.stringify(right);
1338
1454
  }
1339
1455
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
1340
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: SystemSettings, isStandalone: true, selector: "mt-system-settings", inputs: { apiBasePath: { classPropertyName: "apiBasePath", publicName: "apiBasePath", isSignal: true, isRequired: false, transformFunction: null }, backRoute: { classPropertyName: "backRoute", publicName: "backRoute", isSignal: true, isRequired: false, transformFunction: null }, formatSchema: { classPropertyName: "formatSchema", publicName: "formatSchema", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <mt-page\r\n [title]=\"t('page-title')\"\r\n [avatarIcon]=\"'general.settings-02'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-emerald-100)',\r\n '--p-avatar-color': 'var(--p-emerald-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-col gap-4 md:flex-row md:items-end md:justify-between\"\r\n >\r\n <div>\r\n <h3 class=\"text-lg font-bold text-neutral-900\">\r\n {{ t(\"page-title\") }}\r\n </h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n />\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [tooltip]=\"t('save-tooltip')\"\r\n [badge]=\"hasChanges() ? '!' : ''\"\r\n badgeSeverity=\"warn\"\r\n [loading]=\"updating()\"\r\n [disabled]=\"!canSaveSettings()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div class=\"space-y-4\">\r\n <p-skeleton width=\"100%\" height=\"12rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"18rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"14rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n } @else {\r\n <!--\r\n BE/HTTP errors (timezonesError, settingsError, updateError) are already\r\n surfaced as toasts by the HTTP interceptor, so they are intentionally\r\n not rendered as top-of-page banners here to avoid duplicate messaging.\r\n -->\r\n <div class=\"space-y-6\">\r\n <mt-system-settings-general-section\r\n [generalConfigurationForm]=\"form.controls.generalConfiguration\"\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [exploreDefaultViewOptions]=\"exploreDefaultViewOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n />\r\n\r\n <mt-system-settings-timesheet-section\r\n [policyForm]=\"form.controls.timeSheetPolicy\"\r\n [globalWorkingHoursForm]=\"form.controls.globalWorkingHours\"\r\n [dayOptions]=\"dotnetDayOptions()\"\r\n [frequencyOptions]=\"frequencyOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n />\r\n\r\n <mt-system-settings-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: SystemSettingsHolidaysSection, selector: "mt-system-settings-holidays-section", inputs: ["holidays"], outputs: ["addHoliday", "editHoliday", "deleteHoliday"] }, { kind: "component", type: SystemSettingsGeneralSection, selector: "mt-system-settings-general-section", inputs: ["generalConfigurationForm", "timezoneForm", "currencyForm", "exploreDefaultViewOptions", "dateFormatOptions", "timeFormatOptions", "currencyOptions", "currencyFormatOptions"] }, { kind: "component", type: SystemSettingsTimeSheetSection, selector: "mt-system-settings-timesheet-section", inputs: ["policyForm", "globalWorkingHoursForm", "dayOptions", "frequencyOptions", "maxDailyBelowDaily"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1456
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: SystemSettings, isStandalone: true, selector: "mt-system-settings", inputs: { apiBasePath: { classPropertyName: "apiBasePath", publicName: "apiBasePath", isSignal: true, isRequired: false, transformFunction: null }, backRoute: { classPropertyName: "backRoute", publicName: "backRoute", isSignal: true, isRequired: false, transformFunction: null }, formatSchema: { classPropertyName: "formatSchema", publicName: "formatSchema", isSignal: true, isRequired: false, transformFunction: null }, homeRoute: { classPropertyName: "homeRoute", publicName: "homeRoute", isSignal: true, isRequired: false, transformFunction: null }, hideTimeSheet: { classPropertyName: "hideTimeSheet", publicName: "hideTimeSheet", isSignal: true, isRequired: false, transformFunction: null }, exploreViewOptions: { classPropertyName: "exploreViewOptions", publicName: "exploreViewOptions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <mt-page\r\n [title]=\"t('page-title')\"\r\n [avatarIcon]=\"'general.settings-02'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-emerald-100)',\r\n '--p-avatar-color': 'var(--p-emerald-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-col gap-4 md:flex-row md:items-end md:justify-between\"\r\n >\r\n <div>\r\n <h3 class=\"text-lg font-bold text-neutral-900\">\r\n {{ t(\"page-title\") }}\r\n </h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems()\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n />\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [tooltip]=\"t('save-tooltip')\"\r\n [badge]=\"hasChanges() ? '!' : ''\"\r\n badgeSeverity=\"warn\"\r\n [loading]=\"updating()\"\r\n [disabled]=\"!canSaveSettings()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div class=\"space-y-4\">\r\n <p-skeleton width=\"100%\" height=\"12rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"18rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"14rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n } @else {\r\n <!--\r\n BE/HTTP errors (timezonesError, settingsError, updateError) are already\r\n surfaced as toasts by the HTTP interceptor, so they are intentionally\r\n not rendered as top-of-page banners here to avoid duplicate messaging.\r\n -->\r\n <div class=\"space-y-6\">\r\n <mt-system-settings-general-section\r\n [generalConfigurationForm]=\"form.controls.generalConfiguration\"\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [exploreDefaultViewOptions]=\"exploreDefaultViewOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n />\r\n\r\n <mt-system-settings-timesheet-section\r\n [policyForm]=\"form.controls.timeSheetPolicy\"\r\n [globalWorkingHoursForm]=\"form.controls.globalWorkingHours\"\r\n [dayOptions]=\"dotnetDayOptions()\"\r\n [frequencyOptions]=\"frequencyOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n [hidePolicy]=\"hideTimeSheet()\"\r\n />\r\n\r\n <mt-system-settings-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: SystemSettingsHolidaysSection, selector: "mt-system-settings-holidays-section", inputs: ["holidays"], outputs: ["addHoliday", "editHoliday", "deleteHoliday"] }, { kind: "component", type: SystemSettingsGeneralSection, selector: "mt-system-settings-general-section", inputs: ["generalConfigurationForm", "timezoneForm", "currencyForm", "exploreDefaultViewOptions", "dateFormatOptions", "timeFormatOptions", "currencyOptions", "currencyFormatOptions"] }, { kind: "component", type: SystemSettingsTimeSheetSection, selector: "mt-system-settings-timesheet-section", inputs: ["policyForm", "globalWorkingHoursForm", "dayOptions", "frequencyOptions", "maxDailyBelowDaily", "hidePolicy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1341
1457
  }
1342
1458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettings, decorators: [{
1343
1459
  type: Component,
@@ -1352,8 +1468,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1352
1468
  SystemSettingsHolidaysSection,
1353
1469
  SystemSettingsGeneralSection,
1354
1470
  SystemSettingsTimeSheetSection,
1355
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <mt-page\r\n [title]=\"t('page-title')\"\r\n [avatarIcon]=\"'general.settings-02'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-emerald-100)',\r\n '--p-avatar-color': 'var(--p-emerald-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-col gap-4 md:flex-row md:items-end md:justify-between\"\r\n >\r\n <div>\r\n <h3 class=\"text-lg font-bold text-neutral-900\">\r\n {{ t(\"page-title\") }}\r\n </h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n />\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [tooltip]=\"t('save-tooltip')\"\r\n [badge]=\"hasChanges() ? '!' : ''\"\r\n badgeSeverity=\"warn\"\r\n [loading]=\"updating()\"\r\n [disabled]=\"!canSaveSettings()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div class=\"space-y-4\">\r\n <p-skeleton width=\"100%\" height=\"12rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"18rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"14rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n } @else {\r\n <!--\r\n BE/HTTP errors (timezonesError, settingsError, updateError) are already\r\n surfaced as toasts by the HTTP interceptor, so they are intentionally\r\n not rendered as top-of-page banners here to avoid duplicate messaging.\r\n -->\r\n <div class=\"space-y-6\">\r\n <mt-system-settings-general-section\r\n [generalConfigurationForm]=\"form.controls.generalConfiguration\"\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [exploreDefaultViewOptions]=\"exploreDefaultViewOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n />\r\n\r\n <mt-system-settings-timesheet-section\r\n [policyForm]=\"form.controls.timeSheetPolicy\"\r\n [globalWorkingHoursForm]=\"form.controls.globalWorkingHours\"\r\n [dayOptions]=\"dotnetDayOptions()\"\r\n [frequencyOptions]=\"frequencyOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n />\r\n\r\n <mt-system-settings-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"] }]
1356
- }], ctorParameters: () => [], propDecorators: { apiBasePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "apiBasePath", required: false }] }], backRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "backRoute", required: false }] }], formatSchema: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatSchema", required: false }] }] } });
1471
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\r\n <mt-page\r\n [title]=\"t('page-title')\"\r\n [avatarIcon]=\"'general.settings-02'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-emerald-100)',\r\n '--p-avatar-color': 'var(--p-emerald-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-col gap-4 md:flex-row md:items-end md:justify-between\"\r\n >\r\n <div>\r\n <h3 class=\"text-lg font-bold text-neutral-900\">\r\n {{ t(\"page-title\") }}\r\n </h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems()\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n />\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [tooltip]=\"t('save-tooltip')\"\r\n [badge]=\"hasChanges() ? '!' : ''\"\r\n badgeSeverity=\"warn\"\r\n [loading]=\"updating()\"\r\n [disabled]=\"!canSaveSettings()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n\r\n @if (isLoading()) {\r\n <div class=\"space-y-4\">\r\n <p-skeleton width=\"100%\" height=\"12rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"18rem\" borderRadius=\"0.75rem\" />\r\n <p-skeleton width=\"100%\" height=\"14rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n } @else {\r\n <!--\r\n BE/HTTP errors (timezonesError, settingsError, updateError) are already\r\n surfaced as toasts by the HTTP interceptor, so they are intentionally\r\n not rendered as top-of-page banners here to avoid duplicate messaging.\r\n -->\r\n <div class=\"space-y-6\">\r\n <mt-system-settings-general-section\r\n [generalConfigurationForm]=\"form.controls.generalConfiguration\"\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [exploreDefaultViewOptions]=\"exploreDefaultViewOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n />\r\n\r\n <mt-system-settings-timesheet-section\r\n [policyForm]=\"form.controls.timeSheetPolicy\"\r\n [globalWorkingHoursForm]=\"form.controls.globalWorkingHours\"\r\n [dayOptions]=\"dotnetDayOptions()\"\r\n [frequencyOptions]=\"frequencyOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n [hidePolicy]=\"hideTimeSheet()\"\r\n />\r\n\r\n <mt-system-settings-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"] }]
1472
+ }], ctorParameters: () => [], propDecorators: { apiBasePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "apiBasePath", required: false }] }], backRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "backRoute", required: false }] }], formatSchema: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatSchema", required: false }] }], homeRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "homeRoute", required: false }] }], hideTimeSheet: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideTimeSheet", required: false }] }], exploreViewOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "exploreViewOptions", required: false }] }] } });
1357
1473
  function isRecord(value) {
1358
1474
  return typeof value === 'object' && value !== null && !Array.isArray(value);
1359
1475
  }
@@ -1421,11 +1537,6 @@ function getNumberArray(value) {
1421
1537
  function normalizeShowDesignSwitcher(value) {
1422
1538
  return value === true || value === 'true' || value === 'True';
1423
1539
  }
1424
- function normalizeExploreDefaultView(value) {
1425
- return EXPLORE_DEFAULT_VIEW_OPTIONS.some((option) => option.id === value)
1426
- ? value
1427
- : DEFAULT_GENERAL_CONFIGURATION.defaultExploreView;
1428
- }
1429
1540
  function normalizeWeekendDays(value) {
1430
1541
  if (!Array.isArray(value)) {
1431
1542
  return [];
@@ -1461,15 +1572,80 @@ function normalizeFrequency(value) {
1461
1572
  ? value
1462
1573
  : 'Weekly';
1463
1574
  }
1464
- function maxDailyHoursGteHoursPerDay(group) {
1465
- const maxDailyHours = group.get('timeSheetPolicy.maxDailyHours')?.value;
1466
- const hoursPerDay = group.get('globalWorkingHours.hoursPerDay')?.value;
1467
- if (typeof maxDailyHours === 'number' &&
1468
- typeof hoursPerDay === 'number' &&
1469
- maxDailyHours < hoursPerDay) {
1470
- return { maxDailyBelowDaily: true };
1471
- }
1472
- return null;
1575
+ /**
1576
+ * Restores an input's own default when the router's `withComponentInputBinding()`
1577
+ * wipes it. That feature calls `setInput(name, data[name])` for every
1578
+ * declared input on every route activation - including inputs a route's
1579
+ * `data` doesn't mention at all, which writes `undefined` and permanently
1580
+ * overwrites the `input()`'s own default the moment the route activates
1581
+ * (see `RoutedComponentInputBinder` in `@angular/router`). This substitutes
1582
+ * the fallback only for that specific "the router didn't supply it" case.
1583
+ *
1584
+ * Deliberately narrower than `delegations-list.ts`'s `defaultRouteInput`,
1585
+ * which also collapses an explicit `null` into the fallback: `homeRoute`'s
1586
+ * `null` is a meaningful, tested value ("non-navigable crumb"), not merely
1587
+ * "unset", so this only special-cases `undefined` and passes every other
1588
+ * value - including `null` - through untouched.
1589
+ */
1590
+ function defaultWhenUndefined(fallback) {
1591
+ return (value) => (value === undefined ? fallback : value);
1592
+ }
1593
+ /**
1594
+ * pplus5's original Explore-default-view offering, before `'table'` was
1595
+ * added to the catalog for splus4. This - not the full catalog - is what an
1596
+ * unset `exploreViewOptions` input resolves to, so existing hosts keep
1597
+ * seeing exactly the two options they always have.
1598
+ */
1599
+ const DEFAULT_EXPLORE_VIEW_OPTIONS = [
1600
+ 'cards',
1601
+ 'timeline',
1602
+ ];
1603
+ /**
1604
+ * Named validator sets for the six Timesheet/GlobalWorkingHours fields, kept
1605
+ * as constants so `hideTimeSheet` can restore the exact original validators
1606
+ * on toggle-back rather than re-declaring them inline in two places.
1607
+ */
1608
+ const TIME_SHEET_FREQUENCY_VALIDATORS = [Validators.required];
1609
+ const TIME_SHEET_MAX_DAILY_HOURS_VALIDATORS = [
1610
+ Validators.required,
1611
+ Validators.min(1),
1612
+ Validators.max(24),
1613
+ ];
1614
+ const TIME_SHEET_SUBMISSION_DEADLINE_VALIDATORS = [
1615
+ Validators.required,
1616
+ Validators.min(0),
1617
+ Validators.max(90),
1618
+ ];
1619
+ const GLOBAL_WORKING_HOURS_TIMEZONE_VALIDATORS = [Validators.required];
1620
+ const GLOBAL_WORKING_HOURS_WORKING_DAYS_VALIDATORS = [Validators.required];
1621
+ const GLOBAL_WORKING_HOURS_HOURS_PER_DAY_VALIDATORS = [
1622
+ Validators.required,
1623
+ Validators.min(0),
1624
+ Validators.max(24),
1625
+ ];
1626
+ /**
1627
+ * Cross-field validator comparing `timeSheetPolicy.maxDailyHours` against
1628
+ * `globalWorkingHours.hoursPerDay`. Takes the `hideTimeSheet` input as a
1629
+ * thunk (rather than reading it directly) so it stays a plain module-level
1630
+ * function while still skipping the comparison when Timesheet Policy is
1631
+ * hidden - Global Working Hours stays visible/editable on its own even
1632
+ * then, and a visible `hoursPerDay` must never be blocked by a comparison
1633
+ * against a `maxDailyHours` the user can no longer see or change.
1634
+ */
1635
+ function maxDailyHoursGteHoursPerDay(isTimeSheetHidden) {
1636
+ return (group) => {
1637
+ if (isTimeSheetHidden()) {
1638
+ return null;
1639
+ }
1640
+ const maxDailyHours = group.get('timeSheetPolicy.maxDailyHours')?.value;
1641
+ const hoursPerDay = group.get('globalWorkingHours.hoursPerDay')?.value;
1642
+ if (typeof maxDailyHours === 'number' &&
1643
+ typeof hoursPerDay === 'number' &&
1644
+ maxDailyHours < hoursPerDay) {
1645
+ return { maxDailyBelowDaily: true };
1646
+ }
1647
+ return null;
1648
+ };
1473
1649
  }
1474
1650
  function parseCsv(value) {
1475
1651
  return value
@@ -2677,7 +2853,7 @@ function isTestRequestConflict(error) {
2677
2853
  const EMAIL_DELIVERY_ROUTES = [
2678
2854
  {
2679
2855
  path: '',
2680
- loadComponent: () => import('./masterteam-system-settings-email-delivery-shell-Bf6EXLD2.mjs').then((module) => module.EmailDeliveryShell),
2856
+ loadComponent: () => import('./masterteam-system-settings-email-delivery-shell-CR2wsIif.mjs').then((module) => module.EmailDeliveryShell),
2681
2857
  },
2682
2858
  { path: '**', redirectTo: '' },
2683
2859
  ];
@@ -2687,4 +2863,4 @@ const EMAIL_DELIVERY_ROUTES = [
2687
2863
  */
2688
2864
 
2689
2865
  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 };
2690
- //# sourceMappingURL=masterteam-system-settings-masterteam-system-settings-BjftcBn5.mjs.map
2866
+ //# sourceMappingURL=masterteam-system-settings-masterteam-system-settings-DBy-LQB0.mjs.map