Skip to content

Commit

Permalink
#2037 create edit form (#2089)
Browse files Browse the repository at this point in the history
* #2037 added edit form component

* #2037 added dynamic form controls

* #2037 added editing

* #2037 added page reload and last input focus

* #2041 added tab storing in state

* #2037 added directions input

* #2037 added close button, fixed editing when not all fields are present

* #2037 fixed formatting, added autofocus, cleaned code

* #2037 reverted some changes

* #2037 resolved comments

* fixed test, temporarily deleted test file for edit form

* #2037 resolve comments 2

* fixed name in template

* #2037 added access modifiers to variables

* #2037 added translation

---------

Co-authored-by: Nazarii Ivasyshyn <nivasy@softserveinc.com>
  • Loading branch information
nazarstig and Nazarii Ivasyshyn authored May 29, 2023
1 parent 2afb994 commit 85d18d8
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 18 deletions.
4 changes: 4 additions & 0 deletions src/app/shared/services/institutions/institutions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export class InstitutionsService {

return this.http.get<InstituitionHierarchy[]>(`/api/v1/InstitutionHierarchy/GetParents/${id}`, { params });
}

editInstitutionHierarchy(insHierarchy: InstituitionHierarchy): Observable<InstituitionHierarchy> {
return this.http.put<InstituitionHierarchy>(`/api/v1/InstitutionHierarchy/Update`, insHierarchy);
}
}
2 changes: 1 addition & 1 deletion src/app/shared/store/admin.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,4 @@ export class OnUpdateRegionAdminFail {
export class OnUpdateRegionAdminSuccess {
static readonly type = '[admin] update Region Admin success';
constructor(public payload: RegionAdmin) {}
}
}
6 changes: 6 additions & 0 deletions src/app/shared/store/meta-data.actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CodeficatorCategories } from '../enum/codeficator-categories';
import { InstituitionHierarchy } from '../models/institution.model';
import { RateParameters } from '../models/rating';

export class GetDirections {
Expand Down Expand Up @@ -70,6 +71,11 @@ export class ResetInstitutionHierarchy {
constructor() {}
}

export class UpdateInstitutionHierarchy {
static readonly type = '[meta-data] Update Institution Hierarchy';
constructor(public payload: InstituitionHierarchy) {}
}

export class GetInstitutionHierarchyParentsById {
static readonly type = '[meta-data] Get Institution Hierarchy Parents By Id';

Expand Down
8 changes: 7 additions & 1 deletion src/app/shared/store/meta-data.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
GetProviderTypes,
GetRateByEntityId,
GetSocialGroup,
ResetInstitutionHierarchy
ResetInstitutionHierarchy,
UpdateInstitutionHierarchy
} from './meta-data.actions';

export interface MetaDataStateModel {
Expand Down Expand Up @@ -299,6 +300,11 @@ export class MetaDataState {
});
}

@Action(UpdateInstitutionHierarchy)
updateInstitutionHierarchy({ patchState }: StateContext<MetaDataStateModel>, { payload }: UpdateInstitutionHierarchy): Observable<InstituitionHierarchy | Observable<void>> {
return this.institutionsService.editInstitutionHierarchy(payload).pipe();
}

@Action(GetCodeficatorSearch)
getCodeficatorSearch(
{ patchState }: StateContext<MetaDataStateModel>,
Expand Down
2 changes: 2 additions & 0 deletions src/app/shell/admin-tools/data/data.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { AdminApplicationsComponent } from './admin-applications/admin-applications.component';
import { SharedCabinetModule } from '../../personal-cabinet/shared-cabinet/shared-cabinet.module';
import { StatisticsComponent } from './statistics/statistics.component';
import { DirectionsInstitutionHierarchiesEditFormComponent } from './directions-wrapper/directions-institution-hierarchies-edit-form/directions-institution-hierarchies-edit-form.component';

@NgModule({
declarations: [
Expand All @@ -37,6 +38,7 @@ import { StatisticsComponent } from './statistics/statistics.component';
DirectionsInstitutionHierarchiesListComponent,
AdminApplicationsComponent,
StatisticsComponent,
DirectionsInstitutionHierarchiesEditFormComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<div class="create-form" fxLayout="column" fxLayoutAlign="center center">
<div class="wrapper">
<div class="create-form-header" fxLayout="column" fxLayoutAlign="center center">
<h3 class="wrapper-title">{{ 'TITLES.EDIT_INSTITUTION_HIERARCHY_FORM_TITLE' | translate }}</h3>
<p class="wrapper-text">
{{ 'TITLES.EDIT_INSTITUTION_HIERARCHY_FORM_SUBTITLE' | translate }}
</p>
</div>
<div fxLayout="row" fxLayoutAlign="center start" class="warning-box">
<span>
<i class="material-icons status-info-icon inactiveInfoBtn">info_outline</i>
</span>
<div>
<span>{{ 'FORMS.EDIT_INSTITUTION_HIERARCHY_WARNING_MSG' | translate }}
</span>
</div>
</div>

<form #editForm [formGroup]="editDirectionFormGroup" fxLayout="column" fxLayoutAlign="center space-between" class="step form">
<label class="step-label">{{ 'FORMS.LABELS.MINISTRY_SUBMISSION' | translate }}</label>
<mat-form-field>
<input matInput class="step-input" [formControlName]="ministryControl" type="text" autocomplete="none" appTrimValue />
</mat-form-field>
<ng-container *ngFor="let field of this.fields.slice(1); let i = index;">
<label class="step-label">{{ field }}</label>
<mat-form-field>
<input matInput class="step-input" [formControlName]="field" type="text" autocomplete="none" appTrimValue />
</mat-form-field>
</ng-container>
<ng-container>
<mat-form-field appearance="none">
<label class="step-label">{{ 'FORMS.LABELS.USER_DIRECTIONS' | translate }}</label>
<mat-select
#select
multiple
disableOptionCentering
panelClass="dropdown-panel"
class="step-input"
[compareWith]="compareItems"
[formControl]="this.directionsControl">

<mat-select-trigger>
<mat-chip-list #chipList>
<mat-chip *ngFor="let direction of this.directionsControl.value" (removed)="onRemoveItem(direction)">
<img class="min-logo" src="../../assets/icons/icon_painting.svg" alt="Link" />
<span>{{ direction.title }}</span>
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</mat-select-trigger>
<mat-option class="dropdown-option" *ngFor="let direction of (directions$ | async)" [value]="direction" >
{{ direction.title }}
</mat-option>
</mat-select>
</mat-form-field>
</ng-container>
</form>

<div fxLayout="row" fxLayoutAlign="center center" class="footer">
<button mat-raised-button class="btn btn-cancel" (click)="onCancel()">Скасувати</button>
<button class="btn" mat-button type="submit" (click)="onSubmit()">Зберегти</button>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@import 'src/app/shared/styles/create-form-wrapper.scss';
@import 'src/app/shared/styles/create-form.scss';
@import 'src/app/shared/styles/buttons.scss';
@import 'src/app/shared/styles/validation-form.scss';
@import 'src/app/shared/styles/dropdown.scss';

.warning-box {
width: 410px;
height: 50px;
padding: 5px;
border-radius: 5px;
background-color: #ffdab9;

i {
color: orange;
}
}

mat-select {
img {
width: 20px;
height: 20px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { Component, Inject, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatOption } from '@angular/material/core';
import { MatSelect } from '@angular/material/select';
import { Router } from '@angular/router';
import { Select, Store } from '@ngxs/store';
import { Observable, forkJoin, asyncScheduler } from 'rxjs';
import { InstituitionHierarchy } from '../../../../../shared/models/institution.model';
import { GetAllInstitutionsHierarchy, GetDirections, UpdateInstitutionHierarchy } from '../../../../../shared/store/meta-data.actions';
import { MetaDataState } from '../../../../../shared/store/meta-data.state';
import { Direction } from '../../../../../shared/models/category.model';
import { DataItem } from '../../../../../shared/models/item.model';
import { EditInsHierarchyModel } from '../edit-ins-hierarchy-model';

@Component({
selector: 'app-directions-institution-hierarchies-edit-form',
templateUrl: './directions-institution-hierarchies-edit-form.component.html',
styleUrls: ['./directions-institution-hierarchies-edit-form.component.scss']
})
export class DirectionsInstitutionHierarchiesEditFormComponent implements AfterViewInit {
@ViewChild('editForm') editForm: ElementRef;
@ViewChild('select') select: MatSelect;

@Select(MetaDataState.directions) directions$: Observable<Direction[]>;

readonly ministryControl: string = 'Ministry';
readonly userDirectionsControl: string = 'USER_DIRECTIONS';

public editDirectionFormGroup: FormGroup;
public directionsControl: FormControl = new FormControl([]);
public fields: string[] = [];

private lastInsHierarchy: InstituitionHierarchy;
private editedInsHierarchies: InstituitionHierarchy[] = [];

constructor(private router: Router, private dialogRef: MatDialogRef<DirectionsInstitutionHierarchiesEditFormComponent>,
private store: Store, @Inject(MAT_DIALOG_DATA) public data: EditInsHierarchyModel) {
this.store.dispatch(new GetDirections());
this.lastInsHierarchy = this.getLastInsHierarchy();
this.buildForm();
}

private buildForm(): void {
let directions = [...this.lastInsHierarchy.directions];
const formGroupFields = this.getFormControlsFields();
formGroupFields[this.userDirectionsControl] = new FormControl(directions);
this.editDirectionFormGroup = new FormGroup(formGroupFields);
this.directionsControl = this.editDirectionFormGroup.get(this.userDirectionsControl) as FormControl;
}

private getLastInsHierarchy(): InstituitionHierarchy {
return this.data.element.insHierarchies[this.data.element.insHierarchies.length - 1];
}

private getFormControlsFields(): any {
const formGroupFields = {};
formGroupFields[this.ministryControl] = new FormControl({value: this.data.element.insHierarchies[0].institution.title, disabled: true});
this.fields.push(this.ministryControl);
let field, title;
for (let i = 0; i < this.data.columns.length; ++i) {
field = this.data.columns[i];
title = this.data.element.insHierarchies[i]?.title;
if (title) {
formGroupFields[field] = new FormControl(title);
}
else {
formGroupFields[field] = new FormControl({value: null, disabled: true});
}
this.fields.push(field);
}
return formGroupFields;
}

private editInstitutionalHierarchy(insHierarchy: InstituitionHierarchy): Observable<InstituitionHierarchy | Observable<void>> {
return this.store.dispatch(new UpdateInstitutionHierarchy(insHierarchy));
}

private lastInputFocus(): void {
let inputs = this.editForm.nativeElement.getElementsByTagName('input') as HTMLInputElement[];
let lastInput = inputs[inputs.length - 1];
lastInput.focus();
}

private reloadPage(): void {
const currentUrl = this.router.url;
this.router.navigateByUrl('/', {skipLocationChange: true}).then(() => {
this.router.navigate([currentUrl]);
});
}

private closeDialog(): void {
this.dialogRef.close();
}

private compareTwoArrays(array1: Direction[], array2: Direction[]): boolean {
return (
array1.length === array2.length &&
array1.every((first: Direction) =>
array2.some((second: Direction) =>
Object.keys(first).every((key) => first[key] === second[key])
)
)
);
}

public ngAfterViewInit(): void {
asyncScheduler.schedule(() => {
if (this.editForm) {
this.lastInputFocus();
}
}, 0);
}

public onCancel(): void {
this.closeDialog();
}

public onSubmit(): void {
for (let i = 0; i < this.data.columns.length; ++i) {
const fieldName = this.data.columns[i];
const field = this.editDirectionFormGroup.controls[fieldName];
if (field.value != this.data.element.name[i]) {
let editedInsHierarchy = this.data.element.insHierarchies[i];
editedInsHierarchy.title = field.value;
this.editedInsHierarchies.push(editedInsHierarchy);
}
}

if (!this.compareTwoArrays(this.directionsControl.value, this.lastInsHierarchy.directions)) {
let editedInsHierarchy = this.editedInsHierarchies.find((ins: InstituitionHierarchy) => ins.id === this.lastInsHierarchy.id);
if (editedInsHierarchy) {
editedInsHierarchy.directions = this.directionsControl.value;
}
else {
editedInsHierarchy = this.lastInsHierarchy;
editedInsHierarchy.directions = this.directionsControl.value;
this.editedInsHierarchies.push(editedInsHierarchy);
}
}

forkJoin(
this.editedInsHierarchies.map((ins: InstituitionHierarchy) => this.editInstitutionalHierarchy(ins))).subscribe((result) => {
this.store.dispatch(new GetAllInstitutionsHierarchy());
this.reloadPage();
});

this.closeDialog();
}

public compareItems(item1: DataItem, item2: DataItem): boolean {
return item1.id === item2.id;
}

public onRemoveItem(direction: DataItem): void {
this.directionsControl.value.splice(this.directionsControl.value.indexOf(direction), 1);
this.select.options.find((option: MatOption) => option.value.id === direction.id).deselect();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<ng-container class="actions-column" matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef ></th>
<td mat-cell *matCellDef="let element" >
<button mat-button>
<button mat-button (click)="onEdit(element)">
<mat-icon>edit</mat-icon>
</button>
<button mat-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatTableModule } from '@angular/material/table';
import { NgxsModule } from '@ngxs/store';
import { DirectionsInstitutionHierarchiesListComponent } from './directions-institution-hierarchies-list.component';
import { MatDialogModule } from '@angular/material/dialog';

describe('DirectionsInstitutionHierarchiesListComponent', () => {
let component: DirectionsInstitutionHierarchiesListComponent;
let fixture: ComponentFixture<DirectionsInstitutionHierarchiesListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([]), MatTableModule],
imports: [NgxsModule.forRoot([]), MatTableModule, MatDialogModule],
declarations: [DirectionsInstitutionHierarchiesListComponent]
})
.compileComponents();
Expand Down
Loading

0 comments on commit 85d18d8

Please sign in to comment.