Skip to content

Commit

Permalink
Option to configure an external create case form per case (#1334)
Browse files Browse the repository at this point in the history
* Add fix for width chaning in plugin config (#1329)

* fix files being duplicated on upload (#1330)

* WIP: external case create form

* Story/fvm fixes (#1332)

* fix fvm not showing errors, translations, fix missing updateStartForm handle

* feature/fvm-bugfixes fix error message not showing

* fix breadcrumbs endpoint call (#1333)

* added option to specify an external case create form on dossier management configuration tab

* added additional translations

* improved URL validation

* removed unused method

* added translations and toastr

* checking case definition setting if an external create case form is configured, if so open the form in a new window instead of the regular start case modal

* updated regex due to a code scanning alert was triggered

---------

Co-authored-by: teodor-ritense <[email protected]>
Co-authored-by: mbritense <[email protected]>
Co-authored-by: floris-thijssen-ritense <[email protected]>
  • Loading branch information
4 people authored Jan 29, 2025
1 parent 3ffdd8e commit 5deb42a
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 788 deletions.
755 changes: 92 additions & 663 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
}

.value-path-selector__form {
display: flex;
display: grid;
grid-template-columns: 110px 1fr;

width: 100%;
align-items: center;
gap: 16px;
Expand Down
9 changes: 8 additions & 1 deletion projects/valtimo/config/assets/core/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,14 @@
"enabledToggleOn": "Ja",
"enabledToggleOff": "Nein",
"externalUrl": "Die URL des externen Formulars",
"apply": "Anwenden"
"externalUrlPlaceholder": "Die URL des externen Formulars",
"externalUrlWarnText": "Bitte geben Sie eine gültige URL ein",
"externalUrlInvalidText": "Der eingegebene Wert ist keine gültige URL",
"apply": "Anwenden",
"notification": {
"success": "Konfiguration erfolgreich aktualisiert",
"error": "Die Konfiguration konnte nicht aktualisiert werden"
}
},
"tabs": {
"cases": "Konfiguration",
Expand Down
9 changes: 8 additions & 1 deletion projects/valtimo/config/assets/core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,14 @@
"enabledToggleOn": "Yes",
"enabledToggleOff": "No",
"externalUrl": "The URL of the external Create New Case form",
"apply": "Apply"
"externalUrlPlaceholder": "The URL of the external form",
"externalUrlWarnText": "Please enter a valid URL",
"externalUrlInvalidText": "The entered value is not a valid URL",
"apply": "Apply",
"notification": {
"success" : "Configuration updated successfully",
"error" : "Configuration failed to update"
}
},
"tabs": {
"cases": "Configuration",
Expand Down
9 changes: 8 additions & 1 deletion projects/valtimo/config/assets/core/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,14 @@
"enabledToggleOn": "Ja",
"enabledToggleOff": "Nee",
"externalUrl": "De URL van het externe formulier",
"apply": "Toepassen"
"externalUrlPlaceholder": "De URL van het externe formulier",
"externalUrlWarnText": "Voer een geldige URL in",
"externalUrlInvalidText": "De ingevoerde waarde is geen geldige URL",
"apply": "Toepassen",
"notification": {
"success" : "Configuratie succesvol bijgewerkt",
"error" : "Configuratie kan niet worden bijgewerkt"
}
},
"tabs": {
"cases": "Configuratie",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!--
~ Copyright 2015-2024 Ritense BV, the Netherlands.
~
~ Licensed under EUPL, Version 1.2 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" basis,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<ng-container
*ngIf="{
documentDefinitionName: documentDefinitionName$ | async,
caseSettings: caseSettings$ | async
} as obs"
>
<h2 class="mb-4">{{ 'dossierManagement.externalCreateCaseForm.title' | translate }}</h2>

<div class="card card-border card-contrast">
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<cds-toggle
formControlName="hasExternalForm"
label="{{ 'dossierManagement.externalCreateCaseForm.enabledToggle' | translate }}"
onText="{{ 'dossierManagement.externalCreateCaseForm.enabledToggleOn' | translate }}"
offText="{{ 'dossierManagement.externalCreateCaseForm.enabledToggleOff' | translate }}"
></cds-toggle>

<div class="mt-2">
<cds-label
warnText="{{ 'dossierManagement.externalCreateCaseForm.externalUrlWarnText' | translate }}"
invalidText="{{ 'dossierManagement.externalCreateCaseForm.externalUrlInvalidText' | translate }}">
{{ 'dossierManagement.externalCreateCaseForm.externalUrl' | translate }}
<input
cdsText
type="text"
class="input-field"
formControlName="externalFormUrl"
placeholder="{{ 'dossierManagement.externalCreateCaseForm.externalUrlPlaceholder' | translate }}"
maxlength="512"
/>
</cds-label>
</div>

<div class="mt-3">
<button cdsButton="primary" type="submit" [disabled]="!canSubmit()">
{{ 'dossierManagement.externalCreateCaseForm.apply' | translate }}
</button>
</div>
</form>
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {BehaviorSubject, filter, map, Observable, Subscription} from 'rxjs';
import {CaseSettings, DocumentService} from '@valtimo/document';
import {ActivatedRoute} from '@angular/router';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {NGXLogger} from 'ngx-logger';
import {ToastrService} from 'ngx-toastr';
import {TranslateService} from '@ngx-translate/core';

@Component({
selector: 'valtimo-dossier-management-external-create-case-form',
templateUrl: './dossier-management-external-create-case-form.component.html',
})
export class DossierManagementExternalCreateCaseFormComponent implements OnInit, OnDestroy {
public form!: FormGroup;

readonly documentDefinitionName$: Observable<string> = this.route.params.pipe(
map(params => params?.name),
filter(docDefName => !!docDefName)
);

readonly caseSettings$: BehaviorSubject<CaseSettings> = new BehaviorSubject(null);

private readonly urlPattern = new RegExp(
'^(https?:\\/\\/)(([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}|\\d{1,3}(\\.\\d{1,3}){3})(:\\d+)?(\\/\\S*)?(\\?\\S*)?(#\\S*)?$'
);

private _subscriptions = new Subscription();

constructor(
private readonly route: ActivatedRoute,
private readonly documentService: DocumentService,
private readonly fb: FormBuilder,
private readonly translateService: TranslateService,
private readonly toastrService: ToastrService,
private readonly logger: NGXLogger,
) { }

ngOnInit(): void {
this.logger.debug('External Case Create Form - onInit');

this.form = this.fb.group({
hasExternalForm: [false], // Toggle is off by default
externalFormUrl: [ {value: '', disabled: true}, [
Validators.required,
Validators.pattern(this.urlPattern),
Validators.maxLength(512)
]],
});

// Subscribe to the toggle field value changes
this._subscriptions.add(
this.hasExternalForm?.valueChanges.subscribe(isEnabled => {
if (isEnabled) {
this.externalFormUrl.enable();
} else {
this.externalFormUrl.disable();
this.externalFormUrl.reset(); // Clear the URL field when disabled
}
})
);

this._subscriptions.add(
this.documentDefinitionName$.subscribe(documentDefinitionName => {
this.logger.debug(
'Fetching case definition settings for documentDefinitionName',
documentDefinitionName
);
this.documentService
.getCaseSettingsForManagement(documentDefinitionName)
.subscribe(caseSettings => {
this.logger.debug('Fetched case definition settings', caseSettings);
this.caseSettings$.next(caseSettings);
});
})
);

this._subscriptions.add(
this.caseSettings$.subscribe(caseSettings => {
if (caseSettings) {
this.logger.debug('Applying case definition settings to form', caseSettings);
this.form.setValue({
hasExternalForm: caseSettings.hasExternalCreateCaseForm,
externalFormUrl: caseSettings.externalCreateCaseFormUrl,
});
}
})
);
}

ngOnDestroy(): void {
this.logger.debug('External Case Create Form - onDestroy');
// Clean up subscriptions when the component is destroyed
this._subscriptions.unsubscribe();
}

public get hasExternalForm() {
return this.form.get('hasExternalForm');
}

public get externalFormUrl() {
return this.form.get('externalFormUrl');
}

public canSubmit(): boolean {
return this.form.valid;
}

public onSubmit(): void {
if (this.canSubmit()) {
this.logger.debug('Submitted case definition settings form with values:', this.form.value);
const caseSettings = this.caseSettings$.getValue();
this.updateCaseSettings(caseSettings.name, {
hasExternalCreateCaseForm: this.hasExternalForm.value,
externalCreateCaseFormUrl: (typeof this.externalFormUrl.value === 'string') ?
this.externalFormUrl.value.trim() : this.externalFormUrl.value
});
}
}

private updateCaseSettings(documentDefinitionName: string, caseSettings: CaseSettings): void {
this.logger.debug('Updating case definition settings', documentDefinitionName, caseSettings);
this.documentService
.patchCaseSettingsForManagement(documentDefinitionName, caseSettings)
.subscribe({
next: result => {
this.logger.debug('Updated case definition settings', result);
this.caseSettings$.next(result);
},
error: e => {
this.logger.error('An error occurred while updating case definition settings', e);
this.toastrService.error(this.translateService.instant('dossierManagement.externalCreateCaseForm.notification.error'))
},
complete: () => {
this.logger.debug('Finished updating case definition settings');
this.toastrService.success(this.translateService.instant('dossierManagement.externalCreateCaseForm.notification.success'));
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angula
import {Router} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {CARBON_CONSTANTS} from '@valtimo/components';
import {DocumentService, ProcessDocumentDefinition} from '@valtimo/document';
import {CaseSettings, DocumentService, ProcessDocumentDefinition} from '@valtimo/document';
import {NotificationService} from 'carbon-components-angular';
import {BehaviorSubject, combineLatest, map, Observable, of, switchMap} from 'rxjs';
import {DossierListService} from '../../services';
import {DossierProcessStartModalComponent} from '../dossier-process-start-modal/dossier-process-start-modal.component';
import {NGXLogger} from 'ngx-logger';

declare const $;

Expand Down Expand Up @@ -63,6 +64,8 @@ export class DossierListActionsComponent implements OnInit {
})
);

readonly caseSettings$: BehaviorSubject<CaseSettings> = new BehaviorSubject(null);

private selectedProcessDocumentDefinition: ProcessDocumentDefinition | null = null;
private modalListenerAdded = false;
private _cachedAssociatedProcessDocumentDefinitions: Array<ProcessDocumentDefinition> = [];
Expand All @@ -72,21 +75,41 @@ export class DossierListActionsComponent implements OnInit {
private readonly listService: DossierListService,
private readonly notificationService: NotificationService,
private readonly router: Router,
private readonly translateService: TranslateService
private readonly translateService: TranslateService,
private readonly logger: NGXLogger,
) {}

public ngOnInit(): void {
this.modalListenerAdded = false;

this.listService.documentDefinitionName$.subscribe(documentDefinitionName => {
this.logger.debug(
'Fetching case definition settings for documentDefinitionName', documentDefinitionName
);
this.documentService
.getCaseSettings(documentDefinitionName)
.subscribe(caseSettings => {
this.logger.debug('Fetched case definition settings', caseSettings);
this.caseSettings$.next(caseSettings);
});
});
}

public startDossier(): void {
const associatedProcessDocumentDefinitions = this._cachedAssociatedProcessDocumentDefinitions;

if (associatedProcessDocumentDefinitions.length > 1) {
$('#startProcess').modal('show');
const caseSettings = this.caseSettings$.getValue();
this.logger.debug('Has external Create Case form?', caseSettings.hasExternalCreateCaseForm)
if (caseSettings.hasExternalCreateCaseForm) {
this.logger.debug('External Create Case form URL:', caseSettings.externalCreateCaseFormUrl)
window.open(caseSettings.externalCreateCaseFormUrl, '_blank');
} else {
this.selectedProcessDocumentDefinition = associatedProcessDocumentDefinitions[0];
this.showStartProcessModal();
const associatedProcessDocumentDefinitions = this._cachedAssociatedProcessDocumentDefinitions;

if (associatedProcessDocumentDefinitions.length > 1) {
$('#startProcess').modal('show');
} else {
this.selectedProcessDocumentDefinition = associatedProcessDocumentDefinitions[0];
this.showStartProcessModal();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
tokenSetInLocalStorage: tokenSetInLocalStorage$ | async,
formioOptions: formioOptions$ | async,
loading: loading$ | async,
errors: formErrors$ | async,
renderOptions: renderOptions$ | async,
} as obs"
>
<div *ngIf="obs.loading" class="overlay">
Expand All @@ -30,9 +32,9 @@
</div>
</div>

<div *ngIf="errors.length > 0" class="alert alert-danger pt-5 pb-5 mb-2">
<div *ngIf="obs.errors.length > 0" class="alert alert-danger pt-5 pb-5 mb-2">
<ol>
<li *ngFor="let error of errors" class="pl-2">
<li *ngFor="let error of obs.errors" class="pl-2">
{{ error }}
</li>
</ol>
Expand All @@ -43,6 +45,7 @@
[submission]="obs.submission"
[form]="obs.form"
[options]="obs.formioOptions"
[renderOptions]="obs.renderOptions"
[refresh]="refreshForm"
(submit)="onSubmit($event)"
(change)="onChange($event)"
Expand Down
Loading

0 comments on commit 5deb42a

Please sign in to comment.