Pull request #203: Move delete dossier action to edit modal

Merge in RED/ui from RED-1544 to master

* commit 'dc0f89441a645ecf9d94526e04a591173208dd8f':
  Move delete dossier action to edit modal
This commit is contained in:
Timo Bejan 2021-06-07 13:31:27 +02:00
commit 3c4d6b0bb3
21 changed files with 173 additions and 108 deletions

View File

@ -93,7 +93,3 @@
.mb-5 { .mb-5 {
margin-bottom: 5px; margin-bottom: 5px;
} }
.mb-8 {
margin-bottom: 8px;
}

View File

@ -17,6 +17,7 @@ export enum AppConfigKey {
ADMIN_CONTACT_URL = 'ADMIN_CONTACT_URL', ADMIN_CONTACT_URL = 'ADMIN_CONTACT_URL',
AUTO_READ_TIME = 'AUTO_READ_TIME', AUTO_READ_TIME = 'AUTO_READ_TIME',
MAX_FILE_SIZE_MB = 'MAX_FILE_SIZE_MB', MAX_FILE_SIZE_MB = 'MAX_FILE_SIZE_MB',
DELETE_RETENTION_HOURS = 'DELETE_RETENTION_HOURS',
APP_NAME = 'APP_NAME', APP_NAME = 'APP_NAME',
// TODO // TODO

View File

@ -54,7 +54,3 @@
.mt-12 { .mt-12 {
margin-top: 12px; margin-top: 12px;
} }
.mt-24 {
margin-top: 24px;
}

View File

@ -1,14 +1,5 @@
<redaction-status-bar [config]="getDossierStatusConfig(dossier)"></redaction-status-bar> <redaction-status-bar [config]="getDossierStatusConfig(dossier)"></redaction-status-bar>
<div [class.active]="actionMenuOpen" class="action-buttons"> <div [class.active]="actionMenuOpen" class="action-buttons">
<redaction-circle-button
(action)="openDeleteDossierDialog($event, dossier)"
*ngIf="permissionsService.canDeleteDossier(dossier)"
icon="red:trash"
tooltip="dossier-listing.delete.action"
type="dark-bg"
>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="openEditDossierDialog($event, dossier)" (action)="openEditDossierDialog($event, dossier)"
*ngIf="permissionsService.isManager()" *ngIf="permissionsService.isManager()"

View File

@ -23,12 +23,6 @@ export class DossierListingActionsComponent {
private readonly _fileManagementControllerService: FileManagementControllerService private readonly _fileManagementControllerService: FileManagementControllerService
) {} ) {}
openDeleteDossierDialog($event: MouseEvent, dossier: DossierWrapper) {
this._dialogService.openDeleteDossierDialog($event, dossier, () => {
this.actionPerformed.emit();
});
}
openEditDossierDialog($event: MouseEvent, dossier: DossierWrapper) { openEditDossierDialog($event: MouseEvent, dossier: DossierWrapper) {
this._dialogService.openEditDossierDialog($event, dossier, () => { this._dialogService.openEditDossierDialog($event, dossier, () => {
this.actionPerformed.emit(); this.actionPerformed.emit();

View File

@ -52,15 +52,13 @@
(updateDossier)="updatedDossier($event)" (updateDossier)="updatedDossier($event)"
*ngIf="activeNav === 'dossier-dictionary'" *ngIf="activeNav === 'dossier-dictionary'"
[dossierWrapper]="dossierWrapper" [dossierWrapper]="dossierWrapper"
> ></redaction-edit-dossier-dictionary>
</redaction-edit-dossier-dictionary>
<redaction-edit-dossier-team-members <redaction-edit-dossier-team-members
(updateDossier)="updatedDossier($event)" (updateDossier)="updatedDossier($event)"
*ngIf="activeNav === 'members'" *ngIf="activeNav === 'members'"
[dossierWrapper]="dossierWrapper" [dossierWrapper]="dossierWrapper"
> ></redaction-edit-dossier-team-members>
</redaction-edit-dossier-team-members>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">

View File

@ -41,11 +41,11 @@ export class EditDossierDialogComponent {
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _changeRef: ChangeDetectorRef, private readonly _changeRef: ChangeDetectorRef,
public dialogRef: MatDialogRef<EditDossierDialogComponent>, private readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
@Inject(MAT_DIALOG_DATA) @Inject(MAT_DIALOG_DATA)
public data: { dossierWrapper: DossierWrapper; afterSave: Function } private readonly _data: { dossierWrapper: DossierWrapper; afterSave: Function }
) { ) {
this.dossierWrapper = data.dossierWrapper; this.dossierWrapper = _data.dossierWrapper;
} }
get activeNavItem(): { key: string; title?: string } { get activeNavItem(): { key: string; title?: string } {
@ -67,13 +67,18 @@ export class EditDossierDialogComponent {
null, null,
NotificationType.SUCCESS NotificationType.SUCCESS
); );
if (updatedDossier) { if (updatedDossier) {
this.dossierWrapper = updatedDossier; this.dossierWrapper = updatedDossier;
} }
this._changeRef.detectChanges(); this._changeRef.detectChanges();
if (this.data.afterSave) {
this.data.afterSave(); this.afterSave();
} }
afterSave() {
if (this._data?.afterSave) this._data.afterSave();
} }
async save() { async save() {

View File

@ -67,4 +67,14 @@
<mat-datepicker #picker></mat-datepicker> <mat-datepicker #picker></mat-datepicker>
</div> </div>
</div> </div>
<div class="dialog-actions">
<redaction-icon-button
(action)="openDeleteDossierDialog($event)"
*ngIf="permissionsService.canDeleteDossier(dossierWrapper)"
icon="red:trash"
text="dossier-listing.delete.action"
type="show-bg"
></redaction-icon-button>
</div>
</form> </form>

View File

@ -16,3 +16,8 @@
margin-right: 16px; margin-right: 16px;
} }
} }
.dialog-actions {
border-top: none;
padding: 0;
}

View File

@ -5,6 +5,16 @@ import { AppStateService } from '../../../../../state/app-state.service';
import * as moment from 'moment'; import * as moment from 'moment';
import { DossierWrapper } from '../../../../../state/model/dossier.wrapper'; import { DossierWrapper } from '../../../../../state/model/dossier.wrapper';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface'; import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { PermissionsService } from '../../../../../services/permissions.service';
import { Router } from '@angular/router';
import { MatDialogRef } from '@angular/material/dialog';
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
import {
NotificationService,
NotificationType
} from '../../../../../services/notification.service';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'redaction-edit-dossier-general-info', selector: 'redaction-edit-dossier-general-info',
@ -14,15 +24,21 @@ import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSectionInterface { export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSectionInterface {
dossierForm: FormGroup; dossierForm: FormGroup;
hasDueDate: boolean; hasDueDate: boolean;
reportTypesEnum = Object.values(DossierTemplateModel.ReportTypesEnum); readonly reportTypesEnum = Object.values(DossierTemplateModel.ReportTypesEnum);
dossierTemplates: DossierTemplateModel[]; dossierTemplates: DossierTemplateModel[];
@Input() dossierWrapper: DossierWrapper; @Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>(); @Output() updateDossier = new EventEmitter<any>();
constructor( constructor(
readonly permissionsService: PermissionsService,
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder private readonly _formBuilder: FormBuilder,
private readonly _dialogService: DossiersDialogService,
private readonly _router: Router,
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService
) {} ) {}
get changed() { get changed() {
@ -96,6 +112,22 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
this.updateDossier.emit(updatedDossier); this.updateDossier.emit(updatedDossier);
} }
openDeleteDossierDialog($event: MouseEvent) {
this._dialogService.openDeleteDossierDialog($event, this.dossierWrapper, () => {
this._editDossierDialogRef.componentInstance.afterSave();
this._editDossierDialogRef.close();
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
});
}
private _notifyDossierDeleted() {
this._notificationService.showToastNotification(
this._translateService.instant('edit-dossier-dialog.delete-successful'),
null,
NotificationType.SUCCESS
);
}
private _filterInvalidDossierTemplates() { private _filterInvalidDossierTemplates() {
this.dossierTemplates = this._appStateService.dossierTemplates.filter(r => { this.dossierTemplates = this._appStateService.dossierTemplates.filter(r => {
if (this.dossierWrapper?.dossierTemplateId === r.dossierTemplateId) { if (this.dossierWrapper?.dossierTemplateId === r.dossierTemplateId) {

View File

@ -183,9 +183,9 @@ export class DossierListingScreenComponent
} }
openAddDossierDialog(): void { openAddDossierDialog(): void {
this._dialogService.openAddDossierDialog(addResponse => { this._dialogService.openAddDossierDialog(async addResponse => {
this._calculateData(); this._calculateData();
this._router.navigate([`/main/dossiers/${addResponse.dossier.dossierId}`]); await this._router.navigate([`/main/dossiers/${addResponse.dossier.dossierId}`]);
if (addResponse.addMembers) { if (addResponse.addMembers) {
this.openAssignDossierOwnerDialog(null, addResponse.dossier); this.openAssignDossierOwnerDialog(null, addResponse.dossier);
} }

View File

@ -46,14 +46,6 @@
tooltip="dossier-overview.header-actions.edit" tooltip="dossier-overview.header-actions.edit"
tooltipPosition="below" tooltipPosition="below"
></redaction-circle-button> ></redaction-circle-button>
<!-- Commented because users miss-click this shit !-->
<!-- <redaction-circle-button-->
<!-- (action)="openDeleteDossierDialog($event)"-->
<!-- *ngIf="permissionsService.canDeleteDossier()"-->
<!-- icon="red:trash"-->
<!-- tooltip="dossier-overview.header-actions.delete"-->
<!-- tooltipPosition="below"-->
<!-- ></redaction-circle-button>-->
<redaction-file-download-btn <redaction-file-download-btn
[disabled]="areSomeEntitiesSelected" [disabled]="areSomeEntitiesSelected"

View File

@ -270,12 +270,6 @@ export class DossierOverviewScreenComponent
this._dialogService.openEditDossierDialog($event, this.activeDossier); this._dialogService.openEditDossierDialog($event, this.activeDossier);
} }
openDeleteDossierDialog($event: MouseEvent) {
this._dialogService.openDeleteDossierDialog($event, this.activeDossier, () => {
this._router.navigate(['/main/dossiers']);
});
}
openAssignDossierMembersDialog(): void { openAssignDossierMembersDialog(): void {
this._dialogService.openAssignTeamMembersDialog(null, this.activeDossier, () => { this._dialogService.openAssignTeamMembersDialog(null, this.activeDossier, () => {
this.reloadDossiers(); this.reloadDossiers();

View File

@ -14,7 +14,8 @@ import { ForceRedactionDialogComponent } from '../dialogs/force-redaction-dialog
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { import {
ConfirmationDialogComponent, ConfirmationDialogComponent,
ConfirmationDialogInput ConfirmationDialogInput,
TitleColors
} from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component'; } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { DossierWrapper } from '@state/model/dossier.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component'; import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
@ -28,6 +29,7 @@ import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-
import { FileStatusWrapper } from '../../../models/file/file-status.wrapper'; import { FileStatusWrapper } from '../../../models/file/file-status.wrapper';
import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component'; import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
import { TeamMembersDialogComponent } from '../dialogs/team-members-dialog/team-members-dialog.component'; import { TeamMembersDialogComponent } from '../dialogs/team-members-dialog/team-members-dialog.component';
import { AppConfigService } from '../../app-config/app-config.service';
const dialogConfig = { const dialogConfig = {
width: '662px', width: '662px',
@ -46,7 +48,8 @@ export class DossiersDialogService {
private readonly _fileManagementControllerService: FileManagementControllerService, private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _manualAnnotationService: ManualAnnotationService, private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _manualRedactionControllerService: ManualRedactionControllerService private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _appConfigService: AppConfigService
) {} ) {}
openDeleteFilesDialog( openDeleteFilesDialog(
@ -192,11 +195,18 @@ export class DossiersDialogService {
cb?: Function cb?: Function
): MatDialogRef<ConfirmationDialogComponent> { ): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation(); $event.stopPropagation();
const period = this._appConfigService.getConfig('DELETE_RETENTION_HOURS');
const ref = this._dialog.open(ConfirmationDialogComponent, { const ref = this._dialog.open(ConfirmationDialogComponent, {
...dialogConfig, ...dialogConfig,
data: new ConfirmationDialogInput({ data: new ConfirmationDialogInput({
title: 'confirmation-dialog.delete-dossier.title', title: 'confirmation-dialog.delete-dossier.title',
question: 'confirmation-dialog.delete-dossier.question' titleColor: TitleColors.PRIMARY,
question: 'confirmation-dialog.delete-dossier.question',
details: 'confirmation-dialog.delete-dossier.details',
confirmationText: 'confirmation-dialog.delete-dossier.confirmation-text',
requireInput: true,
denyText: 'confirmation-dialog.delete-dossier.deny-text',
translateParams: { dossierName: dossier.dossierName, period: period }
}) })
}); });

View File

@ -1,30 +1,30 @@
<section class="dialog"> <section class="dialog">
<div class="dialog-header heading-l"> <div [class.primary]="isDeleteAction" class="dialog-header heading-l">
{{ confirmationDialogInput.title | translate: confirmationDialogInput.translateParams }} {{ config.title }}
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<p <p [class.heading]="isDeleteAction" [innerHTML]="config.question" class="mt-0 mb-8"></p>
[innerHTML]=" <p *ngIf="config.details" [innerHTML]="config.details" class="mt-0"></p>
confirmationDialogInput.question
| translate: confirmationDialogInput.translateParams <div *ngIf="config.requireInput" class="red-input-group required w-300 mt-24">
" <label>{{ inputLabel }}</label>
></p> <input [(ngModel)]="inputValue" />
</div>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<button (click)="confirm()" color="primary" mat-flat-button> <button
{{ (click)="confirm()"
confirmationDialogInput.confirmationText [disabled]="config.requireInput && confirmationDoesNotMatch()"
| translate: confirmationDialogInput.translateParams color="primary"
}} mat-flat-button
</button> >
<button (click)="deny()" color="primary" mat-flat-button> {{ config.confirmationText }}
{{
confirmationDialogInput.denyText
| translate: confirmationDialogInput.translateParams
}}
</button> </button>
<div (click)="deny()" class="all-caps-label cancel">
{{ config.denyText }}
</div>
</div> </div>
<redaction-circle-button <redaction-circle-button

View File

@ -1,56 +1,80 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
export type TitleColor = 'default' | 'primary';
export enum TitleColors {
DEFAULT = 'default',
PRIMARY = 'primary'
}
export class ConfirmationDialogInput { export class ConfirmationDialogInput {
title?: string; title?: string;
titleColor?: TitleColor;
question?: string; question?: string;
details?: string;
confirmationText?: string; confirmationText?: string;
requireInput?: boolean;
denyText?: string; denyText?: string;
translateParams?: Record<string, unknown>; translateParams?: Record<string, unknown>;
constructor(options: ConfirmationDialogInput) { constructor(options?: ConfirmationDialogInput) {
this.title = options.title || ConfirmationDialogInput.default().title; this.title = options?.title || 'common.confirmation-dialog.title';
this.question = options.question || ConfirmationDialogInput.default().question; this.titleColor = options?.titleColor || TitleColors.DEFAULT;
this.confirmationText = this.question = options?.question || 'common.confirmation-dialog.description';
options.confirmationText || ConfirmationDialogInput.default().confirmationText; this.details = options?.details || '';
this.denyText = options.denyText || ConfirmationDialogInput.default().denyText; this.confirmationText = options?.confirmationText || 'common.confirmation-dialog.confirm';
this.translateParams = this.requireInput = options?.requireInput || false;
options.translateParams || ConfirmationDialogInput.default().translateParams; this.denyText = options?.denyText || 'common.confirmation-dialog.deny';
} this.translateParams = options?.translateParams || {};
static default() {
return new ConfirmationDialogInput({
title: 'common.confirmation-dialog.title',
question: 'common.confirmation-dialog.description',
confirmationText: 'common.confirmation-dialog.confirm',
denyText: 'common.confirmation-dialog.deny',
translateParams: {}
});
} }
} }
@Component({ @Component({
selector: 'redaction-confirmation-dialog',
templateUrl: './confirmation-dialog.component.html', templateUrl: './confirmation-dialog.component.html',
styleUrls: ['./confirmation-dialog.component.scss'] styleUrls: ['./confirmation-dialog.component.scss']
}) })
export class ConfirmationDialogComponent { export class ConfirmationDialogComponent {
config: ConfirmationDialogInput;
inputValue = '';
readonly inputLabel: string;
private readonly _inputLabelKey = 'confirmation-dialog.delete-dossier.input-label';
constructor( constructor(
private readonly _dialogRef: MatDialogRef<ConfirmationDialogComponent>,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
public dialogRef: MatDialogRef<ConfirmationDialogComponent>, @Inject(MAT_DIALOG_DATA) private readonly _confirmationDialogInput: ConfirmationDialogInput
@Inject(MAT_DIALOG_DATA) public confirmationDialogInput: ConfirmationDialogInput
) { ) {
if (!confirmationDialogInput) { this.config = _confirmationDialogInput ?? new ConfirmationDialogInput();
this.confirmationDialogInput = ConfirmationDialogInput.default(); this.config = this.translate(this.config);
} this.inputLabel =
this.translate(this._inputLabelKey) + ` '${this.config.confirmationText}'`;
} }
deny() { get isDeleteAction() {
this.dialogRef.close(); return this.config?.titleColor === TitleColors.PRIMARY;
} }
confirm() { confirmationDoesNotMatch(): boolean {
this.dialogRef.close(true); return this.inputValue.toLowerCase() !== this.config.confirmationText.toLowerCase();
}
deny(): void {
this._dialogRef.close();
}
confirm(): void {
this._dialogRef.close(true);
}
translate<T extends ConfirmationDialogInput | string>(obj: T): T {
if (typeof obj === 'string')
return this._translateService.instant(obj, this.config.translateParams);
const stringKeys = Object.keys(obj).filter(key => typeof key === 'string' && !!obj[key]);
stringKeys.forEach(key => (obj[key] = this.translate(obj[key])));
return obj;
} }
} }

View File

@ -10,5 +10,6 @@
"LICENSE_START": "01-01-2021", "LICENSE_START": "01-01-2021",
"LICENSE_END": "31-12-2021", "LICENSE_END": "31-12-2021",
"LICENSE_PAGE_COUNT": 1000000, "LICENSE_PAGE_COUNT": 1000000,
"MAX_FILE_SIZE_MB": 100 "MAX_FILE_SIZE_MB": 100,
"DELETE_RETENTION_HOURS": 96
} }

View File

@ -614,8 +614,9 @@
"question": "Möchten Sie fortfahren?" "question": "Möchten Sie fortfahren?"
}, },
"delete-dossier": { "delete-dossier": {
"title": "Dossier löschen", "title": "Dossier {{dossierName}}",
"question": "Möchten Sie fortfahren?" "question": "Möchten Sie fortfahren?",
"confirmation-text": "Lösche Dossier"
} }
}, },
"add-edit-file-attribute": { "add-edit-file-attribute": {

View File

@ -200,7 +200,8 @@
"entries": "{{length}} entries" "entries": "{{length}} entries"
}, },
"unsaved-changes": "You have unsaved changes. Save or revert before changing the tab.", "unsaved-changes": "You have unsaved changes. Save or revert before changing the tab.",
"change-successful": "Dossier was updated." "change-successful": "Dossier was updated.",
"delete-successful": "Dossier was deleted."
}, },
"dossier-details": { "dossier-details": {
"title": "Dossier Details", "title": "Dossier Details",
@ -657,8 +658,12 @@
"question": "Do you wish to proceed?" "question": "Do you wish to proceed?"
}, },
"delete-dossier": { "delete-dossier": {
"title": "Delete Dossier", "title": "Delete {{dossierName}}",
"question": "Do you wish to proceed?" "question": "Are you sure you want to delete this dossier?",
"details": "Deleted dossiers are sent to trash. They can be restored up to {{period}} days from their deletion.",
"input-label": "To proceed please type below",
"confirmation-text": "Delete Dossier",
"deny-text": "Keep Dossier"
} }
}, },
"add-edit-file-attribute": { "add-edit-file-attribute": {

View File

@ -241,10 +241,18 @@ section.settings {
margin-top: 20px; margin-top: 20px;
} }
.mt-24 {
margin-top: 24px;
}
.mt-32 { .mt-32 {
margin-top: 32px; margin-top: 32px;
} }
.mb-8 {
margin-bottom: 8px !important;
}
.pb-24 { .pb-24 {
padding-bottom: 24px; padding-bottom: 24px;
} }

View File

@ -9,6 +9,7 @@ ADMIN_CONTACT_NAME="${ADMIN_CONTACT_NAME:-}"
ADMIN_CONTACT_URL="${ADMIN_CONTACT_URL:-}" ADMIN_CONTACT_URL="${ADMIN_CONTACT_URL:-}"
AUTO_READ_TIME="${AUTO_READ_TIME:-1.5}" AUTO_READ_TIME="${AUTO_READ_TIME:-1.5}"
MAX_FILE_SIZE_MB="${MAX_FILE_SIZE_MB:-50}" MAX_FILE_SIZE_MB="${MAX_FILE_SIZE_MB:-50}"
DELETE_RETENTION_HOURS="${DELETE_RETENTION_HOURS:-96}"
BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}" BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}"
@ -34,6 +35,7 @@ echo '{
"APP_NAME":"'"$APP_NAME"'", "APP_NAME":"'"$APP_NAME"'",
"AUTO_READ_TIME":'"$AUTO_READ_TIME"', "AUTO_READ_TIME":'"$AUTO_READ_TIME"',
"MAX_FILE_SIZE_MB":"'"$MAX_FILE_SIZE_MB"'", "MAX_FILE_SIZE_MB":"'"$MAX_FILE_SIZE_MB"'",
"DELETE_RETENTION_HOURS":"'"$DELETE_RETENTION_HOURS"'",
"API_URL":"'"$API_URL"'" "API_URL":"'"$API_URL"'"
}' > /usr/share/nginx/html/ui/assets/config/config.json }' > /usr/share/nginx/html/ui/assets/config/config.json