@masterteam/system-settings 0.0.8 → 0.0.10

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.
@@ -189,13 +189,11 @@ class SystemSettingsTimeSheetSection {
189
189
  policyForm = input.required(...(ngDevMode ? [{ debugName: "policyForm" }] : /* istanbul ignore next */ []));
190
190
  globalWorkingHoursForm = input.required(...(ngDevMode ? [{ debugName: "globalWorkingHoursForm" }] : /* istanbul ignore next */ []));
191
191
  dayOptions = input([], ...(ngDevMode ? [{ debugName: "dayOptions" }] : /* istanbul ignore next */ []));
192
- scheduleItemTypeOptions = input([], ...(ngDevMode ? [{ debugName: "scheduleItemTypeOptions" }] : /* istanbul ignore next */ []));
193
- rateSourceOptions = input([], ...(ngDevMode ? [{ debugName: "rateSourceOptions" }] : /* istanbul ignore next */ []));
192
+ frequencyOptions = input([], ...(ngDevMode ? [{ debugName: "frequencyOptions" }] : /* istanbul ignore next */ []));
194
193
  timezoneOptions = input([], ...(ngDevMode ? [{ debugName: "timezoneOptions" }] : /* istanbul ignore next */ []));
195
- allowOvertimeControl = computed(() => this.policyForm().get('allowOvertime'), ...(ngDevMode ? [{ debugName: "allowOvertimeControl" }] : /* istanbul ignore next */ []));
196
- costEnabledControl = computed(() => this.policyForm().get('costEnabled'), ...(ngDevMode ? [{ debugName: "costEnabledControl" }] : /* istanbul ignore next */ []));
194
+ maxDailyBelowDaily = input(false, ...(ngDevMode ? [{ debugName: "maxDailyBelowDaily" }] : /* istanbul ignore next */ []));
197
195
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettingsTimeSheetSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
198
- 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 }, scheduleItemTypeOptions: { classPropertyName: "scheduleItemTypeOptions", publicName: "scheduleItemTypeOptions", isSignal: true, isRequired: false, transformFunction: null }, rateSourceOptions: { classPropertyName: "rateSourceOptions", publicName: "rateSourceOptions", isSignal: true, isRequired: false, transformFunction: null }, timezoneOptions: { classPropertyName: "timezoneOptions", publicName: "timezoneOptions", 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(\"timesheet-policy-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"timesheet-policy-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div\r\n class=\"rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-800\"\r\n >\r\n {{ t(\"timesheet-policy-workflow-note\") }}\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"weekStartDay\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('week-start-day')\"\r\n [placeholder]=\"t('week-start-day-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-multi-select-field\r\n formControlName=\"allowedScheduleItemTypes\"\r\n [options]=\"scheduleItemTypeOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'itemType'\"\r\n [label]=\"t('allowed-schedule-item-types')\"\r\n [placeholder]=\"t('allowed-schedule-item-types-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"regularDailyHours\"\r\n [label]=\"t('regular-daily-hours')\"\r\n [placeholder]=\"t('regular-daily-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\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 />\r\n\r\n <mt-number-field\r\n formControlName=\"pastEntryLockDays\"\r\n [label]=\"t('past-entry-lock-days')\"\r\n [placeholder]=\"t('past-entry-lock-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"3650\"\r\n />\r\n\r\n <mt-select-field\r\n formControlName=\"rateSource\"\r\n [options]=\"rateSourceOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('rate-source')\"\r\n [placeholder]=\"t('rate-source-placeholder')\"\r\n [required]=\"true\"\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=\"generationEnabled\"\r\n [label]=\"t('generation-enabled')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"manualGenerationEnabled\"\r\n [label]=\"t('manual-generation-enabled')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowOvertime\"\r\n [label]=\"t('allow-overtime')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"requireOvertimeReason\"\r\n [label]=\"t('require-overtime-reason')\"\r\n />\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=\"assignedItemsOnly\"\r\n [label]=\"t('assigned-items-only')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowZeroHourSubmit\"\r\n [label]=\"t('allow-zero-hour-submit')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"costEnabled\"\r\n [label]=\"t('cost-enabled')\"\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(\"global-working-hours-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"global-working-hours-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 class=\"md:col-span-2\"\r\n formControlName=\"timeZoneId\"\r\n [options]=\"timezoneOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('global-timezone')\"\r\n [placeholder]=\"t('global-timezone-placeholder')\"\r\n [filter]=\"true\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-text-field\r\n formControlName=\"workDayStart\"\r\n type=\"time\"\r\n [label]=\"t('work-day-start')\"\r\n [placeholder]=\"t('work-day-start-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-text-field\r\n formControlName=\"workDayEnd\"\r\n type=\"time\"\r\n [label]=\"t('work-day-end')\"\r\n [placeholder]=\"t('work-day-end-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\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-textarea-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"holidaysCsv\"\r\n [label]=\"t('global-holidays')\"\r\n [placeholder]=\"t('global-holidays-placeholder')\"\r\n rows=\"3\"\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: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { 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 });
196
+ 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 }, timezoneOptions: { classPropertyName: "timezoneOptions", publicName: "timezoneOptions", 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'\">\n <div class=\"grid gap-6 xl:grid-cols-2 mb-6\">\n <mt-card class=\"block\">\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\n <div class=\"space-y-1\">\n <h4 class=\"text-base font-semibold text-neutral-900\">\n {{ t(\"general-card-title\") }}\n </h4>\n <p class=\"text-sm text-neutral-600\">\n {{ t(\"general-card-description\") }}\n </p>\n </div>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <mt-select-field\n formControlName=\"frequency\"\n [options]=\"frequencyOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'id'\"\n [label]=\"t('frequency')\"\n [placeholder]=\"t('frequency-placeholder')\"\n [required]=\"true\"\n />\n\n <mt-number-field\n formControlName=\"maxDailyHours\"\n [label]=\"t('max-daily-hours')\"\n [placeholder]=\"t('max-daily-hours-placeholder')\"\n [min]=\"1\"\n [max]=\"24\"\n [required]=\"true\"\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\n />\n\n <mt-number-field\n formControlName=\"submissionDeadlineDays\"\n [label]=\"t('submission-deadline-days')\"\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\n [min]=\"0\"\n [max]=\"90\"\n [required]=\"true\"\n [hint]=\"t('submission-deadline-hint')\"\n />\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-toggle-field\n formControlName=\"requireDescription\"\n [label]=\"t('require-description')\"\n />\n <mt-toggle-field\n formControlName=\"allowFutureEntry\"\n [label]=\"t('allow-future-entry')\"\n />\n <mt-toggle-field\n formControlName=\"allowBackdatedEntry\"\n [label]=\"t('allow-backdated-entry')\"\n />\n <mt-toggle-field\n formControlName=\"autoLockOnApprove\"\n [label]=\"t('auto-lock-on-approve')\"\n />\n </div>\n </div>\n </mt-card>\n\n <mt-card class=\"block\">\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\n <div class=\"space-y-1\">\n <h4 class=\"text-base font-semibold text-neutral-900\">\n {{ t(\"working-hours-card-title\") }}\n </h4>\n <p class=\"text-sm text-neutral-600\">\n {{ t(\"working-hours-card-description\") }}\n </p>\n </div>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <mt-select-field\n class=\"md:col-span-2\"\n formControlName=\"timeZoneId\"\n [options]=\"timezoneOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'id'\"\n [label]=\"t('global-timezone')\"\n [placeholder]=\"t('global-timezone-placeholder')\"\n [filter]=\"true\"\n [required]=\"true\"\n />\n\n <mt-multi-select-field\n class=\"md:col-span-2\"\n formControlName=\"workingDays\"\n [options]=\"dayOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'dayNumber'\"\n [label]=\"t('working-days')\"\n [placeholder]=\"t('working-days-placeholder')\"\n [display]=\"'chip'\"\n [required]=\"true\"\n [showClear]=\"true\"\n />\n\n <mt-number-field\n class=\"md:col-span-2\"\n formControlName=\"hoursPerDay\"\n [label]=\"t('daily-working-hours')\"\n [placeholder]=\"t('daily-working-hours-placeholder')\"\n [min]=\"0\"\n [max]=\"24\"\n [required]=\"true\"\n />\n\n <mt-textarea-field\n class=\"md:col-span-2\"\n formControlName=\"holidaysCsv\"\n [label]=\"t('global-holidays')\"\n [placeholder]=\"t('global-holidays-placeholder')\"\n rows=\"3\"\n />\n </div>\n </div>\n </mt-card>\n </div>\n</ng-container>\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: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { 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 });
199
197
  }
200
198
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettingsTimeSheetSection, decorators: [{
201
199
  type: Component,
@@ -207,10 +205,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
207
205
  NumberField,
208
206
  SelectField,
209
207
  TextareaField,
210
- TextField,
211
208
  ToggleField,
212
- ], 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(\"timesheet-policy-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"timesheet-policy-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div\r\n class=\"rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-800\"\r\n >\r\n {{ t(\"timesheet-policy-workflow-note\") }}\r\n </div>\r\n\r\n <div class=\"grid gap-4 md:grid-cols-2\">\r\n <mt-select-field\r\n formControlName=\"weekStartDay\"\r\n [options]=\"dayOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'dayNumber'\"\r\n [label]=\"t('week-start-day')\"\r\n [placeholder]=\"t('week-start-day-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-multi-select-field\r\n formControlName=\"allowedScheduleItemTypes\"\r\n [options]=\"scheduleItemTypeOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'itemType'\"\r\n [label]=\"t('allowed-schedule-item-types')\"\r\n [placeholder]=\"t('allowed-schedule-item-types-placeholder')\"\r\n [display]=\"'chip'\"\r\n [required]=\"true\"\r\n [showClear]=\"true\"\r\n />\r\n\r\n <mt-number-field\r\n formControlName=\"regularDailyHours\"\r\n [label]=\"t('regular-daily-hours')\"\r\n [placeholder]=\"t('regular-daily-hours-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"24\"\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 />\r\n\r\n <mt-number-field\r\n formControlName=\"pastEntryLockDays\"\r\n [label]=\"t('past-entry-lock-days')\"\r\n [placeholder]=\"t('past-entry-lock-days-placeholder')\"\r\n [min]=\"0\"\r\n [max]=\"3650\"\r\n />\r\n\r\n <mt-select-field\r\n formControlName=\"rateSource\"\r\n [options]=\"rateSourceOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('rate-source')\"\r\n [placeholder]=\"t('rate-source-placeholder')\"\r\n [required]=\"true\"\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=\"generationEnabled\"\r\n [label]=\"t('generation-enabled')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"manualGenerationEnabled\"\r\n [label]=\"t('manual-generation-enabled')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowOvertime\"\r\n [label]=\"t('allow-overtime')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"requireOvertimeReason\"\r\n [label]=\"t('require-overtime-reason')\"\r\n />\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=\"assignedItemsOnly\"\r\n [label]=\"t('assigned-items-only')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"allowZeroHourSubmit\"\r\n [label]=\"t('allow-zero-hour-submit')\"\r\n />\r\n <mt-toggle-field\r\n formControlName=\"costEnabled\"\r\n [label]=\"t('cost-enabled')\"\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(\"global-working-hours-title\") }}\r\n </h4>\r\n <p class=\"text-sm text-neutral-600\">\r\n {{ t(\"global-working-hours-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 class=\"md:col-span-2\"\r\n formControlName=\"timeZoneId\"\r\n [options]=\"timezoneOptions()\"\r\n [optionLabel]=\"'name'\"\r\n [optionValue]=\"'id'\"\r\n [label]=\"t('global-timezone')\"\r\n [placeholder]=\"t('global-timezone-placeholder')\"\r\n [filter]=\"true\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-text-field\r\n formControlName=\"workDayStart\"\r\n type=\"time\"\r\n [label]=\"t('work-day-start')\"\r\n [placeholder]=\"t('work-day-start-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\r\n <mt-text-field\r\n formControlName=\"workDayEnd\"\r\n type=\"time\"\r\n [label]=\"t('work-day-end')\"\r\n [placeholder]=\"t('work-day-end-placeholder')\"\r\n [required]=\"true\"\r\n />\r\n\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-textarea-field\r\n class=\"md:col-span-2\"\r\n formControlName=\"holidaysCsv\"\r\n [label]=\"t('global-holidays')\"\r\n [placeholder]=\"t('global-holidays-placeholder')\"\r\n rows=\"3\"\r\n />\r\n </div>\r\n </div>\r\n </mt-card>\r\n </div>\r\n</ng-container>\r\n" }]
213
- }], 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 }] }], scheduleItemTypeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "scheduleItemTypeOptions", required: false }] }], rateSourceOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rateSourceOptions", required: false }] }], timezoneOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "timezoneOptions", required: false }] }] } });
209
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'system-settings'\">\n <div class=\"grid gap-6 xl:grid-cols-2 mb-6\">\n <mt-card class=\"block\">\n <div [formGroup]=\"policyForm()\" class=\"space-y-5\">\n <div class=\"space-y-1\">\n <h4 class=\"text-base font-semibold text-neutral-900\">\n {{ t(\"general-card-title\") }}\n </h4>\n <p class=\"text-sm text-neutral-600\">\n {{ t(\"general-card-description\") }}\n </p>\n </div>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <mt-select-field\n formControlName=\"frequency\"\n [options]=\"frequencyOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'id'\"\n [label]=\"t('frequency')\"\n [placeholder]=\"t('frequency-placeholder')\"\n [required]=\"true\"\n />\n\n <mt-number-field\n formControlName=\"maxDailyHours\"\n [label]=\"t('max-daily-hours')\"\n [placeholder]=\"t('max-daily-hours-placeholder')\"\n [min]=\"1\"\n [max]=\"24\"\n [required]=\"true\"\n [hint]=\"maxDailyBelowDaily() ? t('max-daily-below-daily') : ''\"\n />\n\n <mt-number-field\n formControlName=\"submissionDeadlineDays\"\n [label]=\"t('submission-deadline-days')\"\n [placeholder]=\"t('submission-deadline-days-placeholder')\"\n [min]=\"0\"\n [max]=\"90\"\n [required]=\"true\"\n [hint]=\"t('submission-deadline-hint')\"\n />\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <mt-toggle-field\n formControlName=\"requireDescription\"\n [label]=\"t('require-description')\"\n />\n <mt-toggle-field\n formControlName=\"allowFutureEntry\"\n [label]=\"t('allow-future-entry')\"\n />\n <mt-toggle-field\n formControlName=\"allowBackdatedEntry\"\n [label]=\"t('allow-backdated-entry')\"\n />\n <mt-toggle-field\n formControlName=\"autoLockOnApprove\"\n [label]=\"t('auto-lock-on-approve')\"\n />\n </div>\n </div>\n </mt-card>\n\n <mt-card class=\"block\">\n <div [formGroup]=\"globalWorkingHoursForm()\" class=\"space-y-5\">\n <div class=\"space-y-1\">\n <h4 class=\"text-base font-semibold text-neutral-900\">\n {{ t(\"working-hours-card-title\") }}\n </h4>\n <p class=\"text-sm text-neutral-600\">\n {{ t(\"working-hours-card-description\") }}\n </p>\n </div>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <mt-select-field\n class=\"md:col-span-2\"\n formControlName=\"timeZoneId\"\n [options]=\"timezoneOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'id'\"\n [label]=\"t('global-timezone')\"\n [placeholder]=\"t('global-timezone-placeholder')\"\n [filter]=\"true\"\n [required]=\"true\"\n />\n\n <mt-multi-select-field\n class=\"md:col-span-2\"\n formControlName=\"workingDays\"\n [options]=\"dayOptions()\"\n [optionLabel]=\"'name'\"\n [optionValue]=\"'dayNumber'\"\n [label]=\"t('working-days')\"\n [placeholder]=\"t('working-days-placeholder')\"\n [display]=\"'chip'\"\n [required]=\"true\"\n [showClear]=\"true\"\n />\n\n <mt-number-field\n class=\"md:col-span-2\"\n formControlName=\"hoursPerDay\"\n [label]=\"t('daily-working-hours')\"\n [placeholder]=\"t('daily-working-hours-placeholder')\"\n [min]=\"0\"\n [max]=\"24\"\n [required]=\"true\"\n />\n\n <mt-textarea-field\n class=\"md:col-span-2\"\n formControlName=\"holidaysCsv\"\n [label]=\"t('global-holidays')\"\n [placeholder]=\"t('global-holidays-placeholder')\"\n rows=\"3\"\n />\n </div>\n </div>\n </mt-card>\n </div>\n</ng-container>\n" }]
210
+ }], 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 }] }], timezoneOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "timezoneOptions", required: false }] }], maxDailyBelowDaily: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDailyBelowDaily", required: false }] }] } });
214
211
 
215
212
  const FIXED_CURRENCIES = [
216
213
  {
@@ -303,6 +300,12 @@ const DOTNET_DAY_DEFINITIONS = [
303
300
  { dayNumber: 5, translationKey: 'friday' },
304
301
  { dayNumber: 6, translationKey: 'saturday' },
305
302
  ];
303
+ const TIMESHEET_FREQUENCY_OPTIONS = [
304
+ { id: 'Daily', translationKey: 'frequency-daily' },
305
+ { id: 'Weekly', translationKey: 'frequency-weekly' },
306
+ { id: 'BiWeekly', translationKey: 'frequency-biweekly' },
307
+ { id: 'Monthly', translationKey: 'frequency-monthly' },
308
+ ];
306
309
  const SCHEDULE_ITEM_TYPE_OPTIONS = [
307
310
  { itemType: 'Task', translationKey: 'schedule-item-task' },
308
311
  { itemType: 'Milestone', translationKey: 'schedule-item-milestone' },
@@ -313,6 +316,35 @@ const RATE_SOURCE_OPTIONS = [
313
316
  { id: 'None', translationKey: 'rate-source-none' },
314
317
  ];
315
318
 
319
+ const SYSTEM_SETTINGS_STORAGE_KEY = 'systemSettings.settings';
320
+ function serializeSystemSettings(settings) {
321
+ return JSON.stringify(settings);
322
+ }
323
+ /**
324
+ * Persists the canonical settings array consumed by host-level formatters.
325
+ * Storage failures must not turn a successful API request into a UI failure.
326
+ */
327
+ function synchronizeSystemSettingsStorage(storage, settings) {
328
+ if (!storage) {
329
+ return false;
330
+ }
331
+ try {
332
+ storage.setItem(SYSTEM_SETTINGS_STORAGE_KEY, serializeSystemSettings(settings));
333
+ return true;
334
+ }
335
+ catch {
336
+ return false;
337
+ }
338
+ }
339
+ function resolveBrowserLocalStorage() {
340
+ try {
341
+ return globalThis.localStorage ?? null;
342
+ }
343
+ catch {
344
+ return null;
345
+ }
346
+ }
347
+
316
348
  class SystemSettings {
317
349
  http = inject(HttpClient);
318
350
  router = inject(Router);
@@ -357,53 +389,36 @@ class SystemSettings {
357
389
  port: new FormControl('', { nonNullable: true }),
358
390
  }),
359
391
  timeSheetPolicy: new FormGroup({
360
- weekStartDay: new FormControl(null, Validators.required),
361
- generationEnabled: new FormControl(true, { nonNullable: true }),
362
- manualGenerationEnabled: new FormControl(true, { nonNullable: true }),
363
- regularDailyHours: new FormControl(8, [
364
- Validators.required,
365
- Validators.min(0),
366
- Validators.max(24),
367
- ]),
392
+ frequency: new FormControl('Weekly', Validators.required),
368
393
  maxDailyHours: new FormControl(24, [
369
394
  Validators.required,
370
395
  Validators.min(1),
371
396
  Validators.max(24),
372
397
  ]),
373
- allowOvertime: new FormControl(true, { nonNullable: true }),
374
- requireOvertimeReason: new FormControl(false, { nonNullable: true }),
375
398
  requireDescription: new FormControl(false, { nonNullable: true }),
376
- allowFutureEntry: new FormControl(true, { nonNullable: true }),
377
- pastEntryLockDays: new FormControl(null, [
399
+ allowFutureEntry: new FormControl(false, { nonNullable: true }),
400
+ allowBackdatedEntry: new FormControl(true, { nonNullable: true }),
401
+ autoLockOnApprove: new FormControl(true, { nonNullable: true }),
402
+ submissionDeadlineDays: new FormControl(3, [
403
+ Validators.required,
378
404
  Validators.min(0),
379
- Validators.max(3650),
405
+ Validators.max(90),
380
406
  ]),
381
- allowedScheduleItemTypes: new FormControl(['Deliverable'], {
382
- nonNullable: true,
383
- validators: [Validators.required],
384
- }),
385
- assignedItemsOnly: new FormControl(false, { nonNullable: true }),
386
- allowZeroHourSubmit: new FormControl(true, { nonNullable: true }),
387
- costEnabled: new FormControl(true, { nonNullable: true }),
388
- rateSource: new FormControl('UserHourlyRate', Validators.required),
389
407
  }),
390
408
  globalWorkingHours: new FormGroup({
391
409
  timeZoneId: new FormControl(null, Validators.required),
392
- workDayStart: new FormControl('08:00', {
393
- nonNullable: true,
394
- validators: [Validators.required],
395
- }),
396
- workDayEnd: new FormControl('16:00', {
397
- nonNullable: true,
398
- validators: [Validators.required],
399
- }),
400
410
  workingDays: new FormControl([0, 1, 2, 3, 4], {
401
411
  nonNullable: true,
402
412
  validators: [Validators.required],
403
413
  }),
414
+ hoursPerDay: new FormControl(8, [
415
+ Validators.required,
416
+ Validators.min(0),
417
+ Validators.max(24),
418
+ ]),
404
419
  holidaysCsv: new FormControl('', { nonNullable: true }),
405
420
  }),
406
- });
421
+ }, maxDailyHoursGteHoursPerDay);
407
422
  settings = signal(null, ...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
408
423
  settingsLoading = signal(false, ...(ngDevMode ? [{ debugName: "settingsLoading" }] : /* istanbul ignore next */ []));
409
424
  settingsError = signal(null, ...(ngDevMode ? [{ debugName: "settingsError" }] : /* istanbul ignore next */ []));
@@ -481,21 +496,13 @@ class SystemSettings {
481
496
  name: this.transloco.translate(`system-settings.${day.translationKey}`),
482
497
  }));
483
498
  }, ...(ngDevMode ? [{ debugName: "dotnetDayOptions" }] : /* istanbul ignore next */ []));
484
- scheduleItemTypeOptions = computed(() => {
485
- this.activeLang();
486
- return SCHEDULE_ITEM_TYPE_OPTIONS.map((item) => ({
487
- id: item.itemType,
488
- itemType: item.itemType,
489
- name: this.transloco.translate(`system-settings.${item.translationKey}`),
490
- }));
491
- }, ...(ngDevMode ? [{ debugName: "scheduleItemTypeOptions" }] : /* istanbul ignore next */ []));
492
- rateSourceOptions = computed(() => {
499
+ frequencyOptions = computed(() => {
493
500
  this.activeLang();
494
- return RATE_SOURCE_OPTIONS.map((item) => ({
501
+ return TIMESHEET_FREQUENCY_OPTIONS.map((item) => ({
495
502
  id: item.id,
496
503
  name: this.transloco.translate(`system-settings.${item.translationKey}`),
497
504
  }));
498
- }, ...(ngDevMode ? [{ debugName: "rateSourceOptions" }] : /* istanbul ignore next */ []));
505
+ }, ...(ngDevMode ? [{ debugName: "frequencyOptions" }] : /* istanbul ignore next */ []));
499
506
  isReady = computed(() => this.hydrationVersion() !== null && this.initialSnapshot() !== null, ...(ngDevMode ? [{ debugName: "isReady" }] : /* istanbul ignore next */ []));
500
507
  isLoading = computed(() => this.settingsLoading() || this.timezonesLoading() || !this.isReady(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
501
508
  hasChanges = computed(() => {
@@ -668,7 +675,9 @@ class SystemSettings {
668
675
  .get(this.buildEndpoint('getsystemsettings'))
669
676
  .subscribe({
670
677
  next: (response) => {
671
- this.settings.set(response.data ?? []);
678
+ const settings = response.data ?? [];
679
+ this.settings.set(settings);
680
+ synchronizeSystemSettingsStorage(resolveBrowserLocalStorage(), settings);
672
681
  this.settingsLoading.set(false);
673
682
  this.settingsLastUpdated.set(new Date().toISOString());
674
683
  },
@@ -706,6 +715,7 @@ class SystemSettings {
706
715
  next: () => {
707
716
  const nextSettings = this.applyUpdatedSettings(this.settings() ?? [], values);
708
717
  this.settings.set(nextSettings);
718
+ synchronizeSystemSettingsStorage(resolveBrowserLocalStorage(), nextSettings);
709
719
  this.updating.set(false);
710
720
  this.updateError.set(null);
711
721
  this.settingsLastUpdated.set(new Date().toISOString());
@@ -722,8 +732,8 @@ class SystemSettings {
722
732
  const currency = this.normalizeCurrencySnapshot(this.extractCurrencyValue(currencySetting));
723
733
  const weekend = this.normalizeWeekendSnapshot(this.extractWeekendValue(weekendSetting));
724
734
  const smtp = this.normalizeSmtpSnapshot(this.extractSmtpValue(smtpSetting));
725
- const timeSheetPolicy = this.normalizeTimeSheetPolicySnapshot(this.extractTimeSheetPolicyValue(timeSheetPolicySetting));
726
735
  const globalWorkingHours = this.normalizeGlobalWorkingHoursSnapshot(this.extractGlobalWorkingHoursValue(globalWorkingHoursSetting));
736
+ const timeSheetPolicy = this.normalizeTimeSheetPolicySnapshot(this.extractTimeSheetPolicyValue(timeSheetPolicySetting), globalWorkingHours.hoursPerDay);
727
737
  this.holidayItems.set(holidays.data);
728
738
  this.form.controls.timezone.setValue({
729
739
  regionId: timezone.region?.id ?? null,
@@ -745,27 +755,18 @@ class SystemSettings {
745
755
  port: smtp.port === null ? '' : String(smtp.port),
746
756
  });
747
757
  this.form.controls.timeSheetPolicy.setValue({
748
- weekStartDay: timeSheetPolicy.weekStartDay,
749
- generationEnabled: timeSheetPolicy.generationEnabled,
750
- manualGenerationEnabled: timeSheetPolicy.manualGenerationEnabled,
751
- regularDailyHours: timeSheetPolicy.regularDailyHours,
758
+ frequency: timeSheetPolicy.frequency,
752
759
  maxDailyHours: timeSheetPolicy.maxDailyHours,
753
- allowOvertime: timeSheetPolicy.allowOvertime,
754
- requireOvertimeReason: timeSheetPolicy.requireOvertimeReason,
755
760
  requireDescription: timeSheetPolicy.requireDescription,
756
761
  allowFutureEntry: timeSheetPolicy.allowFutureEntry,
757
- pastEntryLockDays: timeSheetPolicy.pastEntryLockDays,
758
- allowedScheduleItemTypes: timeSheetPolicy.allowedScheduleItemTypes,
759
- assignedItemsOnly: timeSheetPolicy.assignedItemsOnly,
760
- allowZeroHourSubmit: timeSheetPolicy.allowZeroHourSubmit,
761
- costEnabled: timeSheetPolicy.costEnabled,
762
- rateSource: timeSheetPolicy.rateSource,
762
+ allowBackdatedEntry: timeSheetPolicy.allowBackdatedEntry,
763
+ autoLockOnApprove: timeSheetPolicy.autoLockOnApprove,
764
+ submissionDeadlineDays: timeSheetPolicy.submissionDeadlineDays,
763
765
  });
764
766
  this.form.controls.globalWorkingHours.setValue({
765
767
  timeZoneId: globalWorkingHours.timeZoneId,
766
- workDayStart: minutesToTime(globalWorkingHours.workDayStartMinute),
767
- workDayEnd: minutesToTime(globalWorkingHours.workDayEndMinute),
768
768
  workingDays: globalWorkingHours.workingDays,
769
+ hoursPerDay: globalWorkingHours.hoursPerDay,
769
770
  holidaysCsv: globalWorkingHours.holidays.join(', '),
770
771
  });
771
772
  this.form.markAsPristine();
@@ -854,35 +855,23 @@ class SystemSettings {
854
855
  }
855
856
  buildTimeSheetPolicyValue() {
856
857
  const policyForm = this.form.controls.timeSheetPolicy.getRawValue();
858
+ const hoursPerDay = clampNumber(this.form.controls.globalWorkingHours.controls.hoursPerDay.value ?? 8, 0, 24);
857
859
  return {
858
- weekStartDay: policyForm.weekStartDay ?? 0,
859
- generationEnabled: policyForm.generationEnabled,
860
- manualGenerationEnabled: policyForm.manualGenerationEnabled,
861
- regularDailyHours: policyForm.regularDailyHours ?? 8,
862
- maxDailyHours: policyForm.maxDailyHours ?? 24,
863
- allowOvertime: policyForm.allowOvertime,
864
- requireOvertimeReason: policyForm.allowOvertime
865
- ? policyForm.requireOvertimeReason
866
- : false,
860
+ frequency: normalizeFrequency(policyForm.frequency),
861
+ maxDailyHours: Math.max(hoursPerDay, clampNumber(policyForm.maxDailyHours ?? 24, 1, 24)),
867
862
  requireDescription: policyForm.requireDescription,
868
863
  allowFutureEntry: policyForm.allowFutureEntry,
869
- pastEntryLockDays: policyForm.pastEntryLockDays,
870
- allowedScheduleItemTypes: policyForm.allowedScheduleItemTypes,
871
- assignedItemsOnly: policyForm.assignedItemsOnly,
872
- allowZeroHourSubmit: policyForm.allowZeroHourSubmit,
873
- costEnabled: policyForm.costEnabled,
874
- rateSource: policyForm.costEnabled
875
- ? (policyForm.rateSource ?? 'UserHourlyRate')
876
- : 'None',
864
+ allowBackdatedEntry: policyForm.allowBackdatedEntry,
865
+ autoLockOnApprove: policyForm.autoLockOnApprove,
866
+ submissionDeadlineDays: clampNumber(policyForm.submissionDeadlineDays ?? 0, 0, 90),
877
867
  };
878
868
  }
879
869
  buildGlobalWorkingHoursValue() {
880
870
  const globalForm = this.form.controls.globalWorkingHours.getRawValue();
881
871
  return {
882
872
  timeZoneId: globalForm.timeZoneId ?? 'UTC',
883
- workDayStartMinute: timeToMinutes(globalForm.workDayStart),
884
- workDayEndMinute: timeToMinutes(globalForm.workDayEnd),
885
873
  workingDays: normalizeDotnetDays(globalForm.workingDays),
874
+ hoursPerDay: clampNumber(globalForm.hoursPerDay ?? 8, 0, 24),
886
875
  holidays: parseCsv(globalForm.holidaysCsv),
887
876
  };
888
877
  }
@@ -956,47 +945,36 @@ class SystemSettings {
956
945
  if (!isRecord(setting.value)) {
957
946
  return this.getDefaultTimeSheetPolicyValue();
958
947
  }
948
+ const value = setting.value;
959
949
  const defaults = this.getDefaultTimeSheetPolicyValue();
960
950
  return {
961
- weekStartDay: getNumber(setting.value['weekStartDay']) ?? defaults.weekStartDay,
962
- generationEnabled: getBoolean(setting.value['generationEnabled']) ??
963
- defaults.generationEnabled,
964
- manualGenerationEnabled: getBoolean(setting.value['manualGenerationEnabled']) ??
965
- defaults.manualGenerationEnabled,
966
- regularDailyHours: getNumber(setting.value['regularDailyHours']) ??
967
- defaults.regularDailyHours,
968
- maxDailyHours: getNumber(setting.value['maxDailyHours']) ?? defaults.maxDailyHours,
969
- allowOvertime: getBoolean(setting.value['allowOvertime']) ?? defaults.allowOvertime,
970
- requireOvertimeReason: getBoolean(setting.value['requireOvertimeReason']) ??
971
- defaults.requireOvertimeReason,
972
- requireDescription: getBoolean(setting.value['requireDescription']) ??
973
- defaults.requireDescription,
974
- allowFutureEntry: getBoolean(setting.value['allowFutureEntry']) ??
951
+ frequency: normalizeFrequency(value['frequency'] ?? value['Frequency']),
952
+ maxDailyHours: getNumber(value['maxDailyHours'] ?? value['MaxDailyHours']) ??
953
+ defaults.maxDailyHours,
954
+ requireDescription: getBoolean(value['requireDescription'] ?? value['RequireDescription']) ?? defaults.requireDescription,
955
+ allowFutureEntry: getBoolean(value['allowFutureEntry'] ?? value['AllowFutureEntry']) ??
975
956
  defaults.allowFutureEntry,
976
- pastEntryLockDays: getNullableNumber(setting.value['pastEntryLockDays']),
977
- allowedScheduleItemTypes: getStringArray(setting.value['allowedScheduleItemTypes']) ??
978
- defaults.allowedScheduleItemTypes,
979
- assignedItemsOnly: getBoolean(setting.value['assignedItemsOnly']) ??
980
- defaults.assignedItemsOnly,
981
- allowZeroHourSubmit: getBoolean(setting.value['allowZeroHourSubmit']) ??
982
- defaults.allowZeroHourSubmit,
983
- costEnabled: getBoolean(setting.value['costEnabled']) ?? defaults.costEnabled,
984
- rateSource: getString(setting.value['rateSource']) ?? defaults.rateSource,
957
+ allowBackdatedEntry: getBoolean(value['allowBackdatedEntry'] ?? value['AllowBackdatedEntry']) ?? defaults.allowBackdatedEntry,
958
+ autoLockOnApprove: getBoolean(value['autoLockOnApprove'] ?? value['AutoLockOnApprove']) ??
959
+ defaults.autoLockOnApprove,
960
+ submissionDeadlineDays: getNumber(value['submissionDeadlineDays'] ?? value['SubmissionDeadlineDays']) ?? defaults.submissionDeadlineDays,
985
961
  };
986
962
  }
987
963
  extractGlobalWorkingHoursValue(setting) {
988
964
  if (!isRecord(setting.value)) {
989
965
  return this.getDefaultGlobalWorkingHoursValue();
990
966
  }
967
+ const value = setting.value;
991
968
  const defaults = this.getDefaultGlobalWorkingHoursValue();
992
969
  return {
993
- timeZoneId: getString(setting.value['timeZoneId']) ?? defaults.timeZoneId,
994
- workDayStartMinute: getNumber(setting.value['workDayStartMinute']) ??
995
- defaults.workDayStartMinute,
996
- workDayEndMinute: getNumber(setting.value['workDayEndMinute']) ??
997
- defaults.workDayEndMinute,
998
- workingDays: getNumberArray(setting.value['workingDays']) ?? defaults.workingDays,
999
- holidays: getStringArray(setting.value['holidays']) ?? defaults.holidays,
970
+ timeZoneId: getString(value['timeZoneId'] ?? value['TimeZoneId']) ??
971
+ defaults.timeZoneId,
972
+ workingDays: getNumberArray(value['workingDays'] ?? value['WorkingDays']) ??
973
+ defaults.workingDays,
974
+ hoursPerDay: getNumber(value['hoursPerDay'] ?? value['HoursPerDay']) ??
975
+ defaults.hoursPerDay,
976
+ holidays: getStringArray(value['holidays'] ?? value['Holidays']) ??
977
+ defaults.holidays,
1000
978
  };
1001
979
  }
1002
980
  normalizeHolidayItem(value) {
@@ -1092,33 +1070,23 @@ class SystemSettings {
1092
1070
  port: value.port,
1093
1071
  };
1094
1072
  }
1095
- normalizeTimeSheetPolicySnapshot(value) {
1096
- const defaults = this.getDefaultTimeSheetPolicyValue();
1097
- const regularDailyHours = clampNumber(value.regularDailyHours, 0, 24);
1098
- const maxDailyHours = Math.max(regularDailyHours, clampNumber(value.maxDailyHours, 1, 24));
1099
- const allowedScheduleItemTypes = value.allowedScheduleItemTypes.length
1100
- ? value.allowedScheduleItemTypes
1101
- : defaults.allowedScheduleItemTypes;
1073
+ normalizeTimeSheetPolicySnapshot(value, hoursPerDay) {
1102
1074
  return {
1103
- ...defaults,
1104
- ...value,
1105
- weekStartDay: clampNumber(value.weekStartDay, 0, 6),
1106
- regularDailyHours,
1107
- maxDailyHours,
1108
- pastEntryLockDays: value.pastEntryLockDays === null
1109
- ? null
1110
- : clampNumber(value.pastEntryLockDays, 0, 3650),
1111
- allowedScheduleItemTypes,
1112
- rateSource: value.rateSource || defaults.rateSource,
1075
+ frequency: normalizeFrequency(value.frequency),
1076
+ maxDailyHours: Math.max(clampNumber(hoursPerDay, 0, 24), clampNumber(value.maxDailyHours, 1, 24)),
1077
+ requireDescription: value.requireDescription,
1078
+ allowFutureEntry: value.allowFutureEntry,
1079
+ allowBackdatedEntry: value.allowBackdatedEntry,
1080
+ autoLockOnApprove: value.autoLockOnApprove,
1081
+ submissionDeadlineDays: clampNumber(value.submissionDeadlineDays, 0, 90),
1113
1082
  };
1114
1083
  }
1115
1084
  normalizeGlobalWorkingHoursSnapshot(value) {
1116
1085
  const defaults = this.getDefaultGlobalWorkingHoursValue();
1117
1086
  return {
1118
1087
  timeZoneId: value.timeZoneId || defaults.timeZoneId,
1119
- workDayStartMinute: clampNumber(value.workDayStartMinute, 0, 1439),
1120
- workDayEndMinute: clampNumber(value.workDayEndMinute, 1, 1440),
1121
1088
  workingDays: normalizeDotnetDays(value.workingDays),
1089
+ hoursPerDay: clampNumber(value.hoursPerDay, 0, 24),
1122
1090
  holidays: value.holidays,
1123
1091
  };
1124
1092
  }
@@ -1203,29 +1171,20 @@ class SystemSettings {
1203
1171
  }
1204
1172
  getDefaultTimeSheetPolicyValue() {
1205
1173
  return {
1206
- weekStartDay: 0,
1207
- generationEnabled: true,
1208
- manualGenerationEnabled: true,
1209
- regularDailyHours: 8,
1174
+ frequency: 'Weekly',
1210
1175
  maxDailyHours: 24,
1211
- allowOvertime: true,
1212
- requireOvertimeReason: false,
1213
1176
  requireDescription: false,
1214
- allowFutureEntry: true,
1215
- pastEntryLockDays: null,
1216
- allowedScheduleItemTypes: ['Deliverable'],
1217
- assignedItemsOnly: false,
1218
- allowZeroHourSubmit: true,
1219
- costEnabled: true,
1220
- rateSource: 'UserHourlyRate',
1177
+ allowFutureEntry: false,
1178
+ allowBackdatedEntry: true,
1179
+ autoLockOnApprove: true,
1180
+ submissionDeadlineDays: 3,
1221
1181
  };
1222
1182
  }
1223
1183
  getDefaultGlobalWorkingHoursValue() {
1224
1184
  return {
1225
1185
  timeZoneId: this.timezones()[0]?.id ?? 'UTC',
1226
- workDayStartMinute: 480,
1227
- workDayEndMinute: 960,
1228
1186
  workingDays: [0, 1, 2, 3, 4],
1187
+ hoursPerDay: 8,
1229
1188
  holidays: [],
1230
1189
  };
1231
1190
  }
@@ -1343,7 +1302,7 @@ class SystemSettings {
1343
1302
  return JSON.stringify(left) === JSON.stringify(right);
1344
1303
  }
1345
1304
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
1346
- 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-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n\r\n <mt-system-settings-general-section\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [weekendForm]=\"form.controls.weekend\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n [weekendOptions]=\"weekendOptions()\"\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 [scheduleItemTypeOptions]=\"scheduleItemTypeOptions()\"\r\n [rateSourceOptions]=\"rateSourceOptions()\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n />\r\n\r\n <mt-system-settings-smtp-section [smtpForm]=\"form.controls.smtp\" />\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"], 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: ["timezoneForm", "currencyForm", "weekendForm", "timezoneOptions", "dateFormatOptions", "timeFormatOptions", "currencyOptions", "currencyFormatOptions", "weekendOptions"] }, { kind: "component", type: SystemSettingsSmtpSection, selector: "mt-system-settings-smtp-section", inputs: ["smtpForm"] }, { kind: "component", type: SystemSettingsTimeSheetSection, selector: "mt-system-settings-timesheet-section", inputs: ["policyForm", "globalWorkingHoursForm", "dayOptions", "scheduleItemTypeOptions", "rateSourceOptions", "timezoneOptions"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1305
+ 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-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n\r\n <mt-system-settings-general-section\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [weekendForm]=\"form.controls.weekend\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n [weekendOptions]=\"weekendOptions()\"\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 [timezoneOptions]=\"timezoneOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n />\r\n\r\n <mt-system-settings-smtp-section [smtpForm]=\"form.controls.smtp\" />\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"], 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: ["timezoneForm", "currencyForm", "weekendForm", "timezoneOptions", "dateFormatOptions", "timeFormatOptions", "currencyOptions", "currencyFormatOptions", "weekendOptions"] }, { kind: "component", type: SystemSettingsSmtpSection, selector: "mt-system-settings-smtp-section", inputs: ["smtpForm"] }, { kind: "component", type: SystemSettingsTimeSheetSection, selector: "mt-system-settings-timesheet-section", inputs: ["policyForm", "globalWorkingHoursForm", "dayOptions", "frequencyOptions", "timezoneOptions", "maxDailyBelowDaily"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1347
1306
  }
1348
1307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SystemSettings, decorators: [{
1349
1308
  type: Component,
@@ -1359,7 +1318,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1359
1318
  SystemSettingsGeneralSection,
1360
1319
  SystemSettingsSmtpSection,
1361
1320
  SystemSettingsTimeSheetSection,
1362
- ], 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-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n\r\n <mt-system-settings-general-section\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [weekendForm]=\"form.controls.weekend\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n [weekendOptions]=\"weekendOptions()\"\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 [scheduleItemTypeOptions]=\"scheduleItemTypeOptions()\"\r\n [rateSourceOptions]=\"rateSourceOptions()\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n />\r\n\r\n <mt-system-settings-smtp-section [smtpForm]=\"form.controls.smtp\" />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"] }]
1321
+ ], 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-holidays-section\r\n [holidays]=\"holidayTableRows()\"\r\n (addHoliday)=\"openAddHoliday()\"\r\n (editHoliday)=\"editHoliday($event)\"\r\n (deleteHoliday)=\"deleteHoliday($event)\"\r\n />\r\n\r\n <mt-system-settings-general-section\r\n [timezoneForm]=\"form.controls.timezone\"\r\n [currencyForm]=\"form.controls.currency\"\r\n [weekendForm]=\"form.controls.weekend\"\r\n [timezoneOptions]=\"timezoneOptions()\"\r\n [dateFormatOptions]=\"dateFormatOptions()\"\r\n [timeFormatOptions]=\"timeFormatOptions()\"\r\n [currencyOptions]=\"currencyOptions()\"\r\n [currencyFormatOptions]=\"currencyFormatOptions()\"\r\n [weekendOptions]=\"weekendOptions()\"\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 [timezoneOptions]=\"timezoneOptions()\"\r\n [maxDailyBelowDaily]=\"form.errors?.['maxDailyBelowDaily'] ?? false\"\r\n />\r\n\r\n <mt-system-settings-smtp-section [smtpForm]=\"form.controls.smtp\" />\r\n </div>\r\n }\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block}\n"] }]
1363
1322
  }], 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 }] }] } });
1364
1323
  function isRecord(value) {
1365
1324
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -1452,18 +1411,27 @@ function clampNumber(value, min, max) {
1452
1411
  }
1453
1412
  return Math.min(Math.max(value, min), max);
1454
1413
  }
1455
- function timeToMinutes(value) {
1456
- const [hours, minutes] = value.split(':').map((part) => Number(part));
1457
- if (!Number.isInteger(hours) || !Number.isInteger(minutes)) {
1458
- return 0;
1459
- }
1460
- return clampNumber(hours * 60 + minutes, 0, 1440);
1414
+ const TIMESHEET_FREQUENCIES = [
1415
+ 'Daily',
1416
+ 'Weekly',
1417
+ 'BiWeekly',
1418
+ 'Monthly',
1419
+ ];
1420
+ function normalizeFrequency(value) {
1421
+ return typeof value === 'string' &&
1422
+ TIMESHEET_FREQUENCIES.includes(value)
1423
+ ? value
1424
+ : 'Weekly';
1461
1425
  }
1462
- function minutesToTime(value) {
1463
- const normalizedValue = clampNumber(value, 0, 1440);
1464
- const hours = Math.floor(normalizedValue / 60);
1465
- const minutes = normalizedValue % 60;
1466
- return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
1426
+ function maxDailyHoursGteHoursPerDay(group) {
1427
+ const maxDailyHours = group.get('timeSheetPolicy.maxDailyHours')?.value;
1428
+ const hoursPerDay = group.get('globalWorkingHours.hoursPerDay')?.value;
1429
+ if (typeof maxDailyHours === 'number' &&
1430
+ typeof hoursPerDay === 'number' &&
1431
+ maxDailyHours < hoursPerDay) {
1432
+ return { maxDailyBelowDaily: true };
1433
+ }
1434
+ return null;
1467
1435
  }
1468
1436
  function parseCsv(value) {
1469
1437
  return value
@@ -1510,5 +1478,5 @@ function formatDateOnly(value) {
1510
1478
  * Generated bundle index. Do not edit.
1511
1479
  */
1512
1480
 
1513
- export { DEFAULT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT_OPTIONS, DOTNET_DAY_DEFINITIONS, FIXED_CURRENCIES, RATE_SOURCE_OPTIONS, SCHEDULE_ITEM_TYPE_OPTIONS, SystemSettings, SystemSettingsHolidayEditor, WEEKEND_DAY_DEFINITIONS };
1481
+ export { DEFAULT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT_OPTIONS, DOTNET_DAY_DEFINITIONS, FIXED_CURRENCIES, RATE_SOURCE_OPTIONS, SCHEDULE_ITEM_TYPE_OPTIONS, SystemSettings, SystemSettingsHolidayEditor, TIMESHEET_FREQUENCY_OPTIONS, WEEKEND_DAY_DEFINITIONS };
1514
1482
  //# sourceMappingURL=masterteam-system-settings.mjs.map