Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 184 additions & 26 deletions src/app/shared/dialogs/dialogs-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,159 @@ import { Component, Inject } from '@angular/core';
import {
MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA
} from '@angular/material/legacy-dialog';
import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms';
import {
AbstractControl,
FormArray,
FormBuilder,
FormGroup
} from '@angular/forms';
import { DialogsLoadingService } from './dialogs-loading.service';
import { DialogsListService } from './dialogs-list.service';
import { DialogsListComponent } from './dialogs-list.component';
import { UserService } from '../user.service';

export interface DialogFieldOption {
name: string;
value: unknown;
}

export interface DialogListSelection {
_id: string;
[key: string]: unknown;
}

type DialogFieldType =
| 'checkbox'
| 'textbox'
| 'password'
| 'selectbox'
| 'radio'
| 'rating'
| 'textarea'
| 'markdown'
| 'dialog'
| 'date'
| 'time'
| 'toggle';

interface DialogFieldBase {
name: string;
type: DialogFieldType;
planetBeta?: boolean;
disabled?: boolean;
required?: boolean;
placeholder?: string;
tooltip?: string;
label?: string;
authorizedRoles?: string | string[];
}

interface DialogCheckboxField extends DialogFieldBase {
type: 'checkbox';
}

interface DialogTextboxField extends DialogFieldBase {
type: 'textbox';
inputType?: string;
min?: number;
step?: string | number;
}

interface DialogPasswordField extends DialogFieldBase {
type: 'password';
}

interface DialogSelectboxField extends DialogFieldBase {
type: 'selectbox';
multiple?: boolean;
reset?: boolean;
options: DialogFieldOption[];
}

interface DialogRadioField extends DialogFieldBase {
type: 'radio';
options: string[];
}

interface DialogRatingField extends DialogFieldBase {
type: 'rating';
}

interface DialogTextareaField extends DialogFieldBase {
type: 'textarea';
}

interface DialogMarkdownField extends DialogFieldBase {
type: 'markdown';
imageGroup?: string;
}

interface DialogDialogField extends DialogFieldBase {
type: 'dialog';
text: string;
db: string;
}

interface DialogDateField extends DialogFieldBase {
type: 'date';
min?: Date | string;
max?: Date | string;
}

interface DialogTimeField extends DialogFieldBase {
type: 'time';
}

interface DialogToggleField extends DialogFieldBase {
type: 'toggle';
}

export type DialogField =
| DialogCheckboxField
| DialogTextboxField
| DialogPasswordField
| DialogSelectboxField
| DialogRadioField
| DialogRatingField
| DialogTextareaField
| DialogMarkdownField
| DialogDialogField
| DialogDateField
| DialogTimeField
| DialogToggleField;

type DialogFieldValue =
| string
| number
| boolean
| Date
| DialogListSelection[]
| string[]
| number[]
| null
| undefined;

interface DialogFormControls {
[key: string]: AbstractControl<DialogFieldValue | null>;
}

export type DialogFormGroup = FormGroup<DialogFormControls>;

export type DialogFormGroupConfig = Parameters<FormBuilder['group']>[0];

export type DialogFormGroupOptions = Parameters<FormBuilder['group']>[1];

export interface DialogFormData {
title: string;
formGroup: DialogFormGroup | DialogFormGroupConfig;
formOptions?: DialogFormGroupOptions;
fields: DialogField[];
onSubmit?: (value: DialogFormGroup['value'], form: DialogFormGroup) => void;
closeOnSubmit?: boolean;
disableIfInvalid?: boolean;
autoFocus?: boolean;
}

@Component({
templateUrl: './dialogs-form.component.html',
styles: [ `
Expand All @@ -26,37 +173,37 @@ import { UserService } from '../user.service';
})
export class DialogsFormComponent {

public title: string;
public fields: any;
public modalForm: UntypedFormGroup;
passwordVisibility = new Map();
public title = '';
public fields: DialogField[] = [];
public modalForm!: DialogFormGroup;
passwordVisibility = new Map<string, boolean>();
isSpinnerOk = true;
errorMessage = '';
dialogListRef: MatDialogRef<DialogsListComponent>;
disableIfInvalid = false;

private markFormAsTouched (formGroup: UntypedFormGroup) {
(<any>Object).values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control.controls) {
this.markFormAsTouched(control);
}
});
private markFormAsTouched(control: AbstractControl): void {
if (control instanceof FormGroup) {
Object.values(control.controls).forEach(childControl => this.markFormAsTouched(childControl));
} else if (control instanceof FormArray) {
control.controls.forEach(childControl => this.markFormAsTouched(childControl));
}
control.markAsTouched();
}

constructor(
public dialogRef: MatDialogRef<DialogsFormComponent>,
private dialog: MatDialog,
private fb: UntypedFormBuilder,
@Inject(MAT_DIALOG_DATA) public data,
private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: DialogFormData | null,
private dialogsLoadingService: DialogsLoadingService,
private dialogsListService: DialogsListService,
private userService: UserService
) {
if (this.data && this.data.formGroup) {
this.modalForm = this.data.formGroup instanceof UntypedFormGroup ?
this.data.formGroup :
this.fb.group(this.data.formGroup, this.data.formOptions || {});
this.modalForm = this.data.formGroup instanceof FormGroup ?
this.data.formGroup as DialogFormGroup :
this.fb.group(this.data.formGroup, this.data.formOptions || {}) as DialogFormGroup;
this.title = this.data.title;
this.fields = this.data.fields.filter(field => !field.planetBeta || this.userService.isBetaEnabled());
this.isSpinnerOk = false;
Expand All @@ -69,7 +216,7 @@ export class DialogsFormComponent {
}
}

onSubmit(mForm, dialog) {
onSubmit(mForm: DialogFormGroup, dialog: MatDialogRef<DialogsFormComponent>) {
if (!mForm.valid) {
this.markFormAsTouched(mForm);
return;
Expand All @@ -84,15 +231,21 @@ export class DialogsFormComponent {
}
}

togglePasswordVisibility(fieldName) {
togglePasswordVisibility(fieldName: string) {
const visibility = this.passwordVisibility.get(fieldName) || false;
this.passwordVisibility.set(fieldName, !visibility);
}

openDialog(field) {
const initialSelection = this.modalForm.controls[field.name].value.map((value: any) => value._id);
openDialog(field: DialogDialogField) {
const control = this.modalForm.controls[field.name];
const currentValue = control?.value;
const initialSelection = Array.isArray(currentValue)
? currentValue
.filter((value): value is DialogListSelection => this.isDialogSelection(value))
.map(value => value._id)
: [];
this.dialogsLoadingService.start();
this.dialogsListService.attachDocsData(field.db, 'title', this.dialogOkClick(field).bind(this), initialSelection).subscribe((data) => {
this.dialogsListService.attachDocsData(field.db, 'title', this.dialogOkClick(field), initialSelection).subscribe((data) => {
this.dialogsLoadingService.stop();
this.dialogListRef = this.dialog.open(DialogsListComponent, {
data: data,
Expand All @@ -103,10 +256,11 @@ export class DialogsFormComponent {
});
}

dialogOkClick(field) {
return (selection) => {
this.modalForm.controls[field.name].setValue(selection);
this.dialogListRef.close();
dialogOkClick(field: DialogDialogField) {
return (selection: DialogListSelection[]) => {
const control = this.modalForm.controls[field.name];
control?.setValue(selection);
this.dialogListRef?.close();
this.modalForm.markAsDirty();
};
}
Expand All @@ -119,4 +273,8 @@ export class DialogsFormComponent {
return this.modalForm.dirty;
}

private isDialogSelection(value: unknown): value is DialogListSelection {
return typeof value === 'object' && value !== null && '_id' in value;
}

}
36 changes: 28 additions & 8 deletions src/app/shared/dialogs/dialogs-form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,50 @@ import { DialogsFormComponent } from './dialogs-form.component';
import { MatLegacyDialogRef as MatDialogRef, MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { Injectable } from '@angular/core';
import {
UntypedFormBuilder,
UntypedFormGroup
FormBuilder,
FormGroup
} from '@angular/forms';
import {
DialogField,
DialogFormData,
DialogFormGroup,
DialogFormGroupConfig
} from './dialogs-form.component';

@Injectable()
export class DialogsFormService {

private dialogRef: MatDialogRef<DialogsFormComponent>;

constructor(private dialog: MatDialog, private fb: UntypedFormBuilder) { }
constructor(private dialog: MatDialog, private fb: FormBuilder) { }

public confirm(title: string, fields: any, formGroup: any, autoFocus = false): Observable<boolean> {
public confirm(
title: string,
fields: DialogField[],
formGroup: DialogFormGroup | DialogFormGroupConfig,
autoFocus = false
): Observable<boolean> {
let dialogRef: MatDialogRef<DialogsFormComponent>;
dialogRef = this.dialog.open(DialogsFormComponent, {
width: '600px',
autoFocus: autoFocus
});
if (formGroup instanceof UntypedFormGroup) {
dialogRef.componentInstance.modalForm = formGroup;
if (formGroup instanceof FormGroup) {
dialogRef.componentInstance.modalForm = formGroup as DialogFormGroup;
} else {
dialogRef.componentInstance.modalForm = this.fb.group(formGroup);
dialogRef.componentInstance.modalForm = this.fb.group(formGroup) as DialogFormGroup;
}
dialogRef.componentInstance.title = title;
dialogRef.componentInstance.fields = fields;
return dialogRef.afterClosed();
}

openDialogsForm(title: string, fields: any[], formGroup: any, options: any) {
openDialogsForm(
title: string,
fields: DialogField[],
formGroup: DialogFormGroup | DialogFormGroupConfig,
options: DialogFormOpenOptions = {}
) {
this.dialogRef = this.dialog.open(DialogsFormComponent, {
width: '600px',
autoFocus: options.autoFocus,
Expand All @@ -47,3 +63,7 @@ export class DialogsFormService {
}

}

type DialogFormOpenOptions = Partial<Omit<DialogFormData, 'title' | 'fields' | 'formGroup'>> & {
autoFocus?: boolean;
};
Loading