diff --git a/src/app/features/config/config-form/config-form.component.ts b/src/app/features/config/config-form/config-form.component.ts index 3ce30d559d..503b023b11 100644 --- a/src/app/features/config/config-form/config-form.component.ts +++ b/src/app/features/config/config-form/config-form.component.ts @@ -48,13 +48,19 @@ export class ConfigFormComponent { throw new Error('No config for ' + this.sectionKey()); } this.config = cfg; + + // Mark all fields as touched to show validation errors + this.form.markAllAsTouched(); + if (this.form.valid) { this.save.emit({ sectionKey: exists(this.sectionKey()), config: this.config, }); } else { + // Update validity to ensure error messages are shown this.form.updateValueAndValidity(); + console.warn('Form is invalid, not saving config:', this.form.errors); } } } diff --git a/src/app/features/project/dialogs/create-project/dialog-create-project.component.ts b/src/app/features/project/dialogs/create-project/dialog-create-project.component.ts index d3466639d4..080bacbdd4 100644 --- a/src/app/features/project/dialogs/create-project/dialog-create-project.component.ts +++ b/src/app/features/project/dialogs/create-project/dialog-create-project.component.ts @@ -133,6 +133,18 @@ export class DialogCreateProjectComponent implements OnInit, OnDestroy { } submit(): void { + // Check if both forms are valid + if (!this.formBasic.valid || !this.formTheme.valid) { + // Mark all fields as touched to show validation errors + this.formBasic.markAllAsTouched(); + this.formTheme.markAllAsTouched(); + console.warn('Form validation failed', { + basicFormErrors: this.formBasic.errors, + themeFormErrors: this.formTheme.errors, + }); + return; + } + const projectDataToSave: Project | Partial = { ...this.projectData, }; diff --git a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/dialog-edit-task-repeat-cfg.component.ts b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/dialog-edit-task-repeat-cfg.component.ts index ba63816f48..1ef11aefc2 100644 --- a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/dialog-edit-task-repeat-cfg.component.ts +++ b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/dialog-edit-task-repeat-cfg.component.ts @@ -187,6 +187,18 @@ export class DialogEditTaskRepeatCfgComponent implements OnInit, OnDestroy { } save(): void { + // Check if both forms are valid + if (!this.formGroup1.valid || !this.formGroup2.valid) { + // Mark all fields as touched to show validation errors + this.formGroup1.markAllAsTouched(); + this.formGroup2.markAllAsTouched(); + console.warn('Form validation failed', { + form1Errors: this.formGroup1.errors, + form2Errors: this.formGroup2.errors, + }); + return; + } + // workaround for formly not always updating hidden fields correctly (in time??) if (this.repeatCfg.quickSetting !== 'CUSTOM') { const updatesForQuickSetting = getQuickSettingUpdates(this.repeatCfg.quickSetting); diff --git a/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts b/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts index e64e1f10e5..462ed2fc5b 100644 --- a/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts +++ b/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts @@ -78,6 +78,14 @@ export class DialogSyncInitialCfgComponent { } async save(): Promise { + // Check if form is valid + if (!this.form.valid) { + // Mark all fields as touched to show validation errors + this.form.markAllAsTouched(); + console.warn('Sync form validation failed', this.form.errors); + return; + } + await this.syncConfigService.updateSettingsFromForm( { ...this._tmpUpdatedCfg, diff --git a/src/app/ui/formly-config.module.ts b/src/app/ui/formly-config.module.ts index 354013f8f0..adf184cad0 100644 --- a/src/app/ui/formly-config.module.ts +++ b/src/app/ui/formly-config.module.ts @@ -28,7 +28,14 @@ import { FormlyBtnComponent } from './formly-button/formly-btn.component'; FormlyMatSliderModule, ReactiveFormsModule, FormlyModule.forRoot({ - validationMessages: [{ name: 'pattern', message: 'Invalid input' }], + validationMessages: [ + { name: 'pattern', message: 'Invalid input' }, + { name: 'required', message: 'This field is required' }, + { name: 'min', message: 'Value is too low' }, + { name: 'max', message: 'Value is too high' }, + { name: 'minLength', message: 'Value is too short' }, + { name: 'maxLength', message: 'Value is too long' }, + ], types: [ { name: 'link', component: FormlyLinkWidgetComponent }, { @@ -76,6 +83,14 @@ import { FormlyBtnComponent } from './formly-button/formly-btn.component'; ], extras: { immutable: true, + // Show errors when field is touched or form is submitted + showError: (field) => { + return !!( + field.formControl && + field.formControl.invalid && + (field.formControl.touched || field.options?.parentForm?.submitted) + ); + }, }, }), FormlyMatToggleModule,