RED-9447, RED-10244: disable dossier template field if state is changed.

This commit is contained in:
Nicoleta Panaghiu 2024-10-25 13:39:10 +03:00
parent b0d38e1b0f
commit 2e2eaf476d
3 changed files with 191 additions and 76 deletions

View File

@ -44,11 +44,15 @@
<div class="iqser-input-group w-300">
<label translate="edit-dossier-dialog.general-info.form.dossier-state.label"></label>
<mat-form-field>
<mat-select [placeholder]="statusPlaceholder" formControlName="dossierStatusId">
<mat-option *ngFor="let stateId of states" [value]="stateId">
<div [matTooltip]="getStateName(stateId)" class="flex-align-items-center" matTooltipPosition="after">
<iqser-small-chip *ngIf="!!stateId" [color]="getStateColor(stateId)"></iqser-small-chip>
<div class="clamp-1">{{ getStateName(stateId) }}</div>
<mat-select [placeholder]="statePlaceholder()" formControlName="dossierStatusId">
<mat-option *ngFor="let stateId of states()" [value]="stateId">
<div
[matTooltip]="stateNameAndColor()[stateId]?.name"
class="flex-align-items-center"
matTooltipPosition="after"
>
<iqser-small-chip *ngIf="!!stateId" [color]="stateNameAndColor()[stateId]?.color"></iqser-small-chip>
<div class="clamp-1">{{ stateNameAndColor()[stateId]?.name }}</div>
</div>
</mat-option>
</mat-select>
@ -80,7 +84,7 @@
<div class="dialog-actions">
<iqser-icon-button
(action)="deleteDossier()"
*ngIf="permissionsService.canDeleteDossier(dossier)"
*ngIf="permissionsService.canDeleteDossier(dossier())"
[attr.help-mode-key]="'edit_dossier_delete_dossier_DIALOG'"
[buttonId]="'deleteDossier'"
[icon]="'iqser:trash'"
@ -90,7 +94,7 @@
<iqser-icon-button
(action)="archiveDossier()"
*ngIf="permissionsService.canArchiveDossier(dossier)"
*ngIf="permissionsService.canArchiveDossier(dossier())"
[attr.help-mode-key]="'edit_dossier_archive_dossier_DIALOG'"
[icon]="'red:archive'"
[label]="'dossier-listing.archive.action' | translate"

View File

@ -1,12 +1,12 @@
import { NgForOf, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, effect, input, OnInit, signal, untracked } from '@angular/core';
import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogRef } from '@angular/material/dialog';
import { MatFormField, MatSuffix } from '@angular/material/form-field';
import { MatIcon } from '@angular/material/icon';
import { MatOption, MatSelect } from '@angular/material/select';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatTooltip } from '@angular/material/tooltip';
import { Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -30,12 +30,22 @@ import { DossiersService } from '@services/dossiers/dossiers.service';
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
import { TrashService } from '@services/entity-services/trash.service';
import { PermissionsService } from '@services/permissions.service';
import { dateWithoutTime } from '@utils/functions';
import { dateWithoutTime, formControlToSignal } from '@utils/functions';
import dayjs from 'dayjs';
import { firstValueFrom } from 'rxjs';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { type EditDossierDialogComponent } from '../edit-dossier-dialog.component';
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { AsControl, isJustOne } from '@common-ui/utils';
import { DossierStatesService } from '@services/entity-services/dossier-states.service';
interface GeneralInfoForm {
dossierName: string;
dossierTemplateId: string;
dossierStatusId?: string;
description?: string;
dueDate?: string;
}
@Component({
selector: 'redaction-edit-dossier-general-info',
@ -59,18 +69,36 @@ import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-doss
MatSuffix,
IconButtonComponent,
NgIf,
MatSelectTrigger,
],
})
export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSectionInterface {
@Input() dossier: Dossier;
readonly iconButtonTypes = IconButtonTypes;
form: UntypedFormGroup;
statusPlaceholder: string;
readonly dossier = input<Dossier>();
hasDueDate: boolean;
dossierTemplates: IDossierTemplate[];
states: string[];
form: FormGroup<AsControl<GeneralInfoForm>> = this._formBuilder.group({
dossierName: [null, Validators.required],
dossierTemplateId: [null, Validators.required],
dossierStatusId: [null],
description: [null],
dueDate: [null],
});
initialFormValue: GeneralInfoForm;
readonly dossierStatusIdControl = formControlToSignal(this.form.controls.dossierStatusId);
readonly dossierTemplateIdControl = formControlToSignal<GeneralInfoForm['dossierTemplateId']>(this.form.controls.dossierTemplateId);
readonly states = signal([null]);
readonly stateNameAndColor = computed(() => {
const nameAndColor = {};
this.states().forEach(stateId => {
nameAndColor[stateId] = {
name: this.#getStateName(stateId, untracked(this.dossierTemplateIdControl)),
color: this.#getStateColor(stateId, untracked(this.dossierTemplateIdControl)),
};
});
return nameAndColor;
});
readonly statePlaceholder = computed(() => this.#statePlaceholder);
constructor(
readonly permissionsService: PermissionsService,
@ -87,18 +115,36 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
private readonly _loadingService: LoadingService,
private readonly _translateService: TranslateService,
private readonly _archivedDossiersService: ArchivedDossiersService,
) {}
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _dossierStatesService: DossierStatesService,
) {
effect(() => {
if (this.dossierStatusIdControl() !== this.initialFormValue.dossierStatusId && this.dossierStatusIdControl()) {
this.form.controls.dossierTemplateId.disable();
} else {
this.form.controls.dossierTemplateId.enable();
}
});
effect(
() => {
this.states.set(this.#statesForDossierTemplate);
this.#onDossierTemplateChange();
},
{ allowSignalWrites: true },
);
}
get changed(): boolean {
for (const key of Object.keys(this.form.getRawValue())) {
if (key === 'dueDate') {
if (this.hasDueDate !== !!this.dossier.dueDate) {
if (this.hasDueDate !== !!this.dossier().dueDate) {
return true;
}
if (this.hasDueDate && !dayjs(this.dossier.dueDate).isSame(dayjs(this.form.get(key).value), 'day')) {
if (this.hasDueDate && !dayjs(this.dossier().dueDate).isSame(dayjs(this.form.get(key).value), 'day')) {
return true;
}
} else if (this.dossier[key] !== this.form.get(key).value) {
} else if (this.dossier()[key] !== this.form.get(key).value) {
return true;
}
}
@ -114,40 +160,89 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
return this.hasDueDate && this.form.get('dueDate').value === null;
}
get #statusPlaceholder(): string {
get #statePlaceholder(): string {
return this._translateService.instant(
this.states.length === 1
isJustOne(this.states())
? 'edit-dossier-dialog.general-info.form.dossier-state.no-state-placeholder'
: 'dossier-state.placeholder',
) as string;
}
get #statesForDossierTemplate() {
return [
null,
...this._dossierStatesMapService
.get(this.dossierTemplateIdControl() ?? untracked(this.dossier).dossierTemplateId)
.map(s => s.id),
];
}
get #formValue(): { key: string; value: string; disabled: boolean }[] {
const dossier = untracked(this.dossier);
const formFieldWithArchivedCheck = value => ({ value, disabled: !dossier.isActive });
const dossierStateId = untracked(this.dossierStatusIdControl);
const states = untracked(this.states);
return [
{
key: 'dossierName',
...formFieldWithArchivedCheck(dossier.dossierName),
},
{
key: 'dossierTemplateId',
value: dossier.dossierTemplateId,
disabled: this._dossierStatsService.get(dossier.id).hasFiles || !dossier.isActive || !!dossierStateId,
},
{
key: 'dossierStatusId',
value: dossier.dossierStatusId,
disabled: isJustOne(states) || !dossier.isActive,
},
{
key: 'description',
...formFieldWithArchivedCheck(dossier.description),
},
{
key: 'dueDate',
...formFieldWithArchivedCheck(dossier.dueDate),
},
];
}
ngOnInit() {
this.states = [null, ...this._dossierStatesMapService.get(this.dossier.dossierTemplateId).map(s => s.id)];
this.statusPlaceholder = this.#statusPlaceholder;
this.#filterInvalidDossierTemplates();
this.form = this.#getForm();
if (!this.permissionsService.canEditDossier(this.dossier)) {
this.#patchFormValue();
if (isJustOne(this._dossierTemplatesService.all)) {
this._loadingService.loadWhile(
firstValueFrom(this._dossierTemplatesService.loadOnlyDossierTemplates()).then(async () => {
await firstValueFrom(this._dossierStatesService.loadAllForAllTemplates());
this.#filterInvalidDossierTemplates();
}),
);
} else {
this.#filterInvalidDossierTemplates();
}
if (!this.permissionsService.canEditDossier(this.dossier())) {
this.form.disable();
}
this.hasDueDate = !!this.dossier.dueDate;
this.hasDueDate = !!this.dossier().dueDate;
}
revert() {
this.form.reset({
dossierName: this.dossier.dossierName,
dossierTemplateId: this.dossier.dossierTemplateId,
dossierStatusId: this.dossier.dossierStatusId,
description: this.dossier.description,
dueDate: this.dossier.dueDate,
dossierName: this.dossier().dossierName,
dossierTemplateId: this.dossier().dossierTemplateId,
dossierStatusId: this.dossier().dossierStatusId,
description: this.dossier().description,
dueDate: this.dossier().dueDate,
});
this.hasDueDate = !!this.dossier.dueDate;
this.hasDueDate = !!this.dossier().dueDate;
this.initialFormValue = this.form.getRawValue();
}
async save(): EditDossierSaveResult {
const dueDate = dateWithoutTime(dayjs(this.form.get('dueDate').value));
const dossier = {
...this.dossier,
...this.dossier(),
dossierName: this.form.get('dossierName').value,
description: this.form.get('description').value,
dueDate: dueDate.isValid() ? dueDate.toISOString() : undefined,
@ -156,9 +251,10 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
} as IDossierRequest;
const updatedDossier = await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
if (updatedDossier && updatedDossier.dossierTemplateId !== this.dossier.dossierTemplateId) {
if (updatedDossier && updatedDossier.dossierTemplateId !== this.dossier().dossierTemplateId) {
await this._router.navigate([updatedDossier.routerLink]);
}
this.initialFormValue = this.form.getRawValue();
return { success: !!updatedDossier };
}
@ -171,14 +267,14 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
requireInput: true,
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
translateParams: {
dossierName: this.dossier.dossierName,
dossierName: this.dossier().dossierName,
dossiersCount: 1,
},
};
this._dialogService.openDialog('confirm', data, async () => {
this._loadingService.start();
const successful = await this._trashService.deleteDossier(this.dossier);
const successful = await this._trashService.deleteDossier(this.dossier());
if (successful) {
await this.#closeDialogAndRedirectToDossier();
}
@ -194,7 +290,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
confirmationText: _('confirm-archive-dossier.archive'),
denyText: _('confirm-archive-dossier.cancel'),
titleColor: TitleColors.WARN,
translateParams: { ...this.dossier },
translateParams: { ...this.dossier() },
checkboxes: [{ value: false, label: _('confirm-archive-dossier.checkbox.documents') }],
toastMessage: _('confirm-archive-dossier.toast-error'),
};
@ -202,10 +298,10 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
this._dialogService.openDialog('confirm', data, async result => {
if (result === ConfirmOptions.CONFIRM) {
this._loadingService.start();
await firstValueFrom(this._archivedDossiersService.archive([this.dossier]));
await firstValueFrom(this._archivedDossiersService.archive([this.dossier()]));
this._toaster.success(_('dossier-listing.archive.archive-succeeded'), {
params: {
dossierName: this.dossier.dossierName,
dossierName: this.dossier().dossierName,
},
});
this._editDossierDialogRef.close();
@ -214,15 +310,6 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
});
}
getStateName(stateId: string): string {
return (this._dossierStatesMapService.get(this.dossier.dossierTemplateId, stateId)?.name ||
this._translateService.instant('dossier-state.placeholder')) as string;
}
getStateColor(stateId: string): string {
return this._dossierStatesMapService.get(this.dossier.dossierTemplateId, stateId).color;
}
toggleDueDateField() {
this.hasDueDate = !this.hasDueDate;
if (!this.hasDueDate) {
@ -230,46 +317,63 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
}
}
#getStateName(stateId: string, templateId: string): string {
return (this._dossierStatesMapService.get(templateId, stateId)?.name ||
this._translateService.instant('dossier-state.placeholder')) as string;
}
#getStateColor(stateId: string, templateId: string): string {
return this._dossierStatesMapService.get(templateId, stateId)?.color;
}
#patchFormValue() {
this.#formValue.forEach(formValue => {
this.form.patchValue({ [formValue.key]: formValue.value });
if (formValue.disabled) this.form.get(formValue.key).disable();
});
this.initialFormValue = this.form.getRawValue();
}
async #closeDialogAndRedirectToDossier() {
this._editDossierDialogRef.close();
await this._router.navigate([this.dossier.dossiersListRouterLink]);
await this._router.navigate([this.dossier().dossiersListRouterLink]);
this._toaster.success(_('edit-dossier-dialog.delete-successful'), {
params: {
dossierName: this.dossier.dossierName,
dossierName: this.dossier().dossierName,
},
});
}
#getForm(): UntypedFormGroup {
const formFieldWithArchivedCheck = value => ({ value, disabled: !this.dossier.isActive });
return this._formBuilder.group({
dossierName: [formFieldWithArchivedCheck(this.dossier.dossierName), Validators.required],
dossierTemplateId: [
{
value: this.dossier.dossierTemplateId,
disabled: this._dossierStatsService.get(this.dossier.id).hasFiles || !this.dossier.isActive,
},
Validators.required,
],
dossierStatusId: [
{
value: this.dossier.dossierStatusId,
disabled: this.states.length === 1 || !this.dossier.isActive,
},
],
description: [formFieldWithArchivedCheck(this.dossier.description)],
dueDate: [formFieldWithArchivedCheck(this.dossier.dueDate)],
});
}
#filterInvalidDossierTemplates() {
const dossier = untracked(this.dossier);
this.dossierTemplates = this._dossierTemplatesService.all.filter(r => {
if (this.dossier?.dossierTemplateId === r.dossierTemplateId) {
if (dossier.dossierTemplateId === r.dossierTemplateId) {
return true;
}
const notYetValid = !!r.validFrom && dayjs(r.validFrom).isAfter(dayjs());
const notValidAnymore = !!r.validTo && dayjs(r.validTo).add(1, 'd').isBefore(dayjs());
this._changeDetectorRef.markForCheck();
return !(notYetValid || notValidAnymore) && r.isActive;
});
}
#onDossierTemplateChange() {
const dossierStateId = untracked(this.dossierStatusIdControl);
const dossierTemplateId = untracked(this.dossierTemplateIdControl);
if (!!dossierStateId && dossierTemplateId !== this.initialFormValue.dossierTemplateId) {
this.form.controls.dossierStatusId.setValue(null);
}
const dossier = untracked(this.dossier);
if (dossierTemplateId === this.initialFormValue.dossierTemplateId) {
this.form.controls.dossierStatusId.setValue(dossier.dossierStatusId);
}
const states = untracked(this.states);
if (isJustOne(states) || !dossier.isActive) {
this.form.controls.dossierStatusId.disable();
} else {
this.form.controls.dossierStatusId.enable();
}
this._changeDetectorRef.markForCheck();
}
}

View File

@ -45,6 +45,13 @@ export class DossierTemplatesService extends EntitiesService<IDossierTemplate, D
);
}
loadOnlyDossierTemplates(): Observable<DossierTemplate[]> {
return this.getAll().pipe(
mapEach(entity => new DossierTemplate(entity)),
tap(templates => this.setEntities(templates)),
);
}
loadDossierTemplate(dossierTemplateId: string) {
return this._getOne([dossierTemplateId], this._defaultModelPath).pipe(
map(entity => new DossierTemplate(entity)),