Pull request #200: Quick filters
Merge in RED/ui from RED-1548 to master * commit 'da5360bacfec21a8b43527e78f693a3123927458': RED-1549: Dossier overview quick filters Dossier listing quick filters
This commit is contained in:
commit
d3ebe3d9c4
@ -21,9 +21,9 @@ export class ConfirmDeleteUsersDialogComponent {
|
||||
private readonly _appStateService: AppStateService,
|
||||
public dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>
|
||||
) {
|
||||
this.dossiersCount = this._appStateService.allDossiers.filter(pw => {
|
||||
this.dossiersCount = this._appStateService.allDossiers.filter(dw => {
|
||||
for (const user of this.users) {
|
||||
if (pw.memberIds.indexOf(user.userId) !== -1) {
|
||||
if (dw.memberIds.indexOf(user.userId) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import { DictionaryManagerComponent } from '../../../shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '../../../shared/services/dictionary-save.service';
|
||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '@shared/services/dictionary-save.service';
|
||||
import { TypeValueWrapper } from '../../../../models/file/type-value.wrapper';
|
||||
|
||||
@Component({
|
||||
|
||||
@ -17,6 +17,7 @@ export enum AppConfigKey {
|
||||
ADMIN_CONTACT_URL = 'ADMIN_CONTACT_URL',
|
||||
AUTO_READ_TIME = 'AUTO_READ_TIME',
|
||||
MAX_FILE_SIZE_MB = 'MAX_FILE_SIZE_MB',
|
||||
RECENT_PERIOD_IN_HOURS = 'RECENT_PERIOD_IN_HOURS',
|
||||
DELETE_RETENTION_HOURS = 'DELETE_RETENTION_HOURS',
|
||||
APP_NAME = 'APP_NAME',
|
||||
|
||||
|
||||
@ -3,11 +3,11 @@ import { AppStateService } from '@state/app-state.service';
|
||||
import { groupBy } from '@utils/functions';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { Router } from '@angular/router';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-details',
|
||||
|
||||
@ -36,8 +36,8 @@ export class DossierListingActionsComponent {
|
||||
});
|
||||
}
|
||||
|
||||
getDossierStatusConfig(pw: DossierWrapper) {
|
||||
const obj = pw.files.reduce((acc, file) => {
|
||||
getDossierStatusConfig(dw: DossierWrapper) {
|
||||
const obj = dw.files.reduce((acc, file) => {
|
||||
const status = file.status;
|
||||
if (!acc[status]) {
|
||||
acc[status] = 1;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-listing-details',
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
class="all-caps-label primary pointer"
|
||||
translate="file-preview.tabs.annotations.select"
|
||||
></div>
|
||||
<redaction-filter
|
||||
<redaction-popup-filter
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[chevron]="true"
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[primaryFilters]="primaryFilters"
|
||||
[secondaryFilters]="secondaryFilters"
|
||||
></redaction-filter>
|
||||
></redaction-popup-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
|
||||
@ -9,13 +9,13 @@ import {
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
|
||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
|
||||
@ -92,11 +92,11 @@ export class TeamMembersManagerComponent implements OnInit {
|
||||
const ownerId = this.selectedOwnerId;
|
||||
const memberIds = this.selectedMembersList;
|
||||
const approverIds = this.selectedApproversList;
|
||||
const pw = Object.assign({}, this.dossierWrapper);
|
||||
pw.dossier.memberIds = memberIds;
|
||||
pw.dossier.approverIds = approverIds;
|
||||
pw.dossier.ownerId = ownerId;
|
||||
result = await this._appStateService.addOrUpdateDossier(pw.dossier);
|
||||
const dw = Object.assign({}, this.dossierWrapper);
|
||||
dw.dossier.memberIds = memberIds;
|
||||
dw.dossier.approverIds = approverIds;
|
||||
dw.dossier.ownerId = ownerId;
|
||||
result = await this._appStateService.addOrUpdateDossier(dw.dossier);
|
||||
this.save.emit(result);
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-type-filter',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, Inject, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DossierWrapper } from '../../../../state/model/dossier.wrapper';
|
||||
import { DictionaryManagerComponent } from '../../../shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '../../../shared/services/dictionary-save.service';
|
||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '@shared/services/dictionary-save.service';
|
||||
import { AppStateService } from '../../../../state/app-state.service';
|
||||
import { PermissionsService } from '../../../../services/permissions.service';
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core
|
||||
import { AppStateService } from '../../../../../state/app-state.service';
|
||||
import { DossierWrapper } from '../../../../../state/model/dossier.wrapper';
|
||||
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||
import { DictionarySaveService } from '../../../../shared/services/dictionary-save.service';
|
||||
import { DictionaryManagerComponent } from '../../../../shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { PermissionsService } from '../../../../../services/permissions.service';
|
||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '@shared/services/dictionary-save.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-dictionary',
|
||||
|
||||
@ -2,36 +2,36 @@
|
||||
<div class="page-header">
|
||||
<div class="filters">
|
||||
<div translate="filters.filter-by"></div>
|
||||
<redaction-filter
|
||||
<redaction-popup-filter
|
||||
#statusFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.status'"
|
||||
[icon]="'red:status'"
|
||||
[primaryFilters]="statusFilters"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
></redaction-popup-filter>
|
||||
<redaction-popup-filter
|
||||
#peopleFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.people'"
|
||||
[icon]="'red:user'"
|
||||
[primaryFilters]="peopleFilters"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
></redaction-popup-filter>
|
||||
<redaction-popup-filter
|
||||
#needsWorkFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.needs-work'"
|
||||
[filterTemplate]="needsWorkTemplate"
|
||||
[icon]="'red:needs-work'"
|
||||
[primaryFilters]="needsWorkFilters"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
></redaction-popup-filter>
|
||||
<redaction-popup-filter
|
||||
#dossierTemplateFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
*ngIf="dossierTemplateFilters.length > 1"
|
||||
[filterLabel]="'filters.dossier-templates'"
|
||||
[icon]="'red:template'"
|
||||
[primaryFilters]="dossierTemplateFilters"
|
||||
></redaction-filter>
|
||||
></redaction-popup-filter>
|
||||
<redaction-search-input
|
||||
[form]="searchForm"
|
||||
[placeholder]="'dossier-listing.search'"
|
||||
@ -63,6 +63,11 @@
|
||||
| translate: { length: displayedEntities.length || 0 }
|
||||
}}
|
||||
</span>
|
||||
|
||||
<redaction-quick-filters
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filters]="quickFilters"
|
||||
></redaction-quick-filters>
|
||||
</div>
|
||||
|
||||
<div class="table-header" redactionSyncWidth="table-item">
|
||||
@ -107,63 +112,63 @@
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let pw of displayedEntities
|
||||
let dw of displayedEntities
|
||||
| sortBy: sortingOption.order:sortingOption.column
|
||||
"
|
||||
[class.pointer]="canOpenDossier(pw)"
|
||||
[class.pointer]="canOpenDossier(dw)"
|
||||
[routerLink]="[
|
||||
canOpenDossier(pw) ? '/main/dossiers/' + pw.dossier.dossierId : []
|
||||
canOpenDossier(dw) ? '/main/dossiers/' + dw.dossier.dossierId : []
|
||||
]"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="filename">
|
||||
<div class="table-item-title heading">
|
||||
{{ pw.dossier.dossierName }}
|
||||
{{ dw.dossier.dossierName }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:template"></mat-icon>
|
||||
{{ getDossierTemplate(pw).name }}
|
||||
{{ getDossierTemplate(dw).name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:document"></mat-icon>
|
||||
{{ documentCount(pw) }}
|
||||
{{ documentCount(dw) }}
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ pw.totalNumberOfPages }}
|
||||
{{ dw.totalNumberOfPages }}
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
{{ userCount(pw) }}
|
||||
{{ userCount(dw) }}
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
{{ pw.dossier.date | date: 'mediumDate' }}
|
||||
{{ dw.dossier.date | date: 'mediumDate' }}
|
||||
</div>
|
||||
<div *ngIf="pw.dossier.dueDate">
|
||||
<div *ngIf="dw.dossier.dueDate">
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
{{ pw.dossier.dueDate | date: 'mediumDate' }}
|
||||
{{ dw.dossier.dueDate | date: 'mediumDate' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<redaction-needs-work-badge
|
||||
[needsWorkInput]="pw"
|
||||
[needsWorkInput]="dw"
|
||||
></redaction-needs-work-badge>
|
||||
</div>
|
||||
<div class="user-column">
|
||||
<redaction-initials-avatar
|
||||
[userId]="pw.dossier.ownerId"
|
||||
[userId]="dw.dossier.ownerId"
|
||||
[withName]="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="status-container">
|
||||
<redaction-dossier-listing-actions
|
||||
(actionPerformed)="actionPerformed()"
|
||||
[dossier]="pw"
|
||||
[dossier]="dw"
|
||||
></redaction-dossier-listing-actions>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
|
||||
@ -4,14 +4,6 @@ import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { groupBy } from '@utils/functions';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import {
|
||||
annotationFilterChecker,
|
||||
dossierMemberChecker,
|
||||
dossierStatusChecker,
|
||||
dossierTemplateChecker,
|
||||
processFilters
|
||||
} from '@shared/components/filter/utils/filter-utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
@ -21,11 +13,20 @@ import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||
import { FilterComponent } from '@shared/components/filter/filter.component';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
|
||||
import {
|
||||
annotationFilterChecker,
|
||||
dossierMemberChecker,
|
||||
dossierStatusChecker,
|
||||
dossierTemplateChecker,
|
||||
processFilters
|
||||
} from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-listing-screen',
|
||||
@ -47,17 +48,19 @@ export class DossierListingScreenComponent
|
||||
} = {
|
||||
statusFilters: []
|
||||
};
|
||||
quickFilters: FilterModel[];
|
||||
readonly itemSize = 85;
|
||||
@ViewChild(CdkVirtualScrollViewport) scrollBar: CdkVirtualScrollViewport;
|
||||
protected readonly _searchKey = 'name';
|
||||
protected readonly _sortKey = 'dossier-listing';
|
||||
@ViewChild(QuickFiltersComponent) protected _quickFiltersComponent: QuickFiltersComponent;
|
||||
private _dossierAutoUpdateTimer: Subscription;
|
||||
private _lastScrollPosition: number;
|
||||
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
|
||||
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
|
||||
@ViewChild('needsWorkFilter') private _needsWorkFilterComponent: FilterComponent;
|
||||
@ViewChild('dossierTemplateFilter') private _dossierTemplateFilterComponent: FilterComponent;
|
||||
|
||||
@ViewChild('statusFilter') private _statusFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('peopleFilter') private _peopleFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('needsWorkFilter') private _needsWorkFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('dossierTemplateFilter')
|
||||
private _dossierTemplateFilterComponent: PopupFilterComponent;
|
||||
private _routerEventsScrollPositionSub: Subscription;
|
||||
private _fileChangedSub: Subscription;
|
||||
|
||||
@ -92,12 +95,13 @@ export class DossierListingScreenComponent
|
||||
return this.allEntities.length - this.activeDossiersCount;
|
||||
}
|
||||
|
||||
protected get _filterComponents(): FilterComponent[] {
|
||||
protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] {
|
||||
return [
|
||||
this._statusFilterComponent,
|
||||
this._peopleFilterComponent,
|
||||
this._needsWorkFilterComponent,
|
||||
this._dossierTemplateFilterComponent
|
||||
this._dossierTemplateFilterComponent,
|
||||
this._quickFiltersComponent
|
||||
];
|
||||
}
|
||||
|
||||
@ -116,7 +120,12 @@ export class DossierListingScreenComponent
|
||||
matchAll: true,
|
||||
checkerArgs: this.permissionsService
|
||||
},
|
||||
{ values: this.dossierTemplateFilters, checker: dossierTemplateChecker }
|
||||
{ values: this.dossierTemplateFilters, checker: dossierTemplateChecker },
|
||||
{
|
||||
values: this.quickFilters,
|
||||
checker: (dw: DossierWrapper) =>
|
||||
this.quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -174,12 +183,12 @@ export class DossierListingScreenComponent
|
||||
return dossier.numberOfMembers;
|
||||
}
|
||||
|
||||
canOpenDossier(pw: DossierWrapper): boolean {
|
||||
return !!pw;
|
||||
canOpenDossier(dw: DossierWrapper): boolean {
|
||||
return !!dw;
|
||||
}
|
||||
|
||||
getDossierTemplate(pw: DossierWrapper): DossierTemplateModel {
|
||||
return this._appStateService.getDossierTemplateById(pw.dossier.dossierTemplateId);
|
||||
getDossierTemplate(dw: DossierWrapper): DossierTemplateModel {
|
||||
return this._appStateService.getDossierTemplateById(dw.dossier.dossierTemplateId);
|
||||
}
|
||||
|
||||
openAddDossierDialog(): void {
|
||||
@ -303,5 +312,28 @@ export class DossierListingScreenComponent
|
||||
this.dossierTemplateFilters,
|
||||
dossierTemplateFilters
|
||||
);
|
||||
|
||||
this.quickFilters = [
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-listing.quick-filters.my-dossiers',
|
||||
checker: (dw: DossierWrapper) => dw.ownerId === this.user.id
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-listing.quick-filters.to-approve',
|
||||
checker: (dw: DossierWrapper) => dw.approverIds.includes(this.user.id)
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-listing.quick-filters.to-review',
|
||||
checker: (dw: DossierWrapper) => dw.memberIds.includes(this.user.id)
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-listing.quick-filters.other',
|
||||
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id)
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,28 +2,28 @@
|
||||
<div class="page-header">
|
||||
<div class="filters">
|
||||
<div translate="filters.filter-by"></div>
|
||||
<redaction-filter
|
||||
<redaction-popup-filter
|
||||
#statusFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.status'"
|
||||
[icon]="'red:status'"
|
||||
[primaryFilters]="statusFilters"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
></redaction-popup-filter>
|
||||
<redaction-popup-filter
|
||||
#peopleFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.assigned-people'"
|
||||
[icon]="'red:user'"
|
||||
[primaryFilters]="peopleFilters"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
></redaction-popup-filter>
|
||||
<redaction-popup-filter
|
||||
#needsWorkFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.needs-work'"
|
||||
[filterTemplate]="needsWorkTemplate"
|
||||
[icon]="'red:needs-work'"
|
||||
[primaryFilters]="needsWorkFilters"
|
||||
></redaction-filter>
|
||||
></redaction-popup-filter>
|
||||
|
||||
<redaction-search-input
|
||||
[form]="searchForm"
|
||||
@ -106,6 +106,11 @@
|
||||
(reload)="bulkActionPerformed()"
|
||||
[selectedFileIds]="selectedEntitiesIds"
|
||||
></redaction-dossier-overview-bulk-actions>
|
||||
|
||||
<redaction-quick-filters
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filters]="quickFilters"
|
||||
></redaction-quick-filters>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@ -15,15 +15,9 @@ import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import * as moment from 'moment';
|
||||
import { DossierDetailsComponent } from '../../components/dossier-details/dossier-details.component';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import {
|
||||
annotationFilterChecker,
|
||||
keyChecker,
|
||||
processFilters
|
||||
} from '@shared/components/filter/utils/filter-utils';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { FileStatus, UserPreferenceControllerService } from '@redaction/red-ui-http';
|
||||
@ -32,12 +26,20 @@ import { filter, tap } from 'rxjs/operators';
|
||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { convertFiles, handleFileDrop } from '@utils/file-drop-utils';
|
||||
import { FilterComponent } from '@shared/components/filter/filter.component';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
||||
import {
|
||||
annotationFilterChecker,
|
||||
keyChecker,
|
||||
processFilters
|
||||
} from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
|
||||
import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-overview-screen',
|
||||
@ -57,7 +59,9 @@ export class DossierOverviewScreenComponent
|
||||
statusFilters: FilterModel[];
|
||||
} = { needsWorkFilters: [], statusFilters: [] };
|
||||
readonly itemSize = 80;
|
||||
quickFilters: FilterModel[];
|
||||
@ViewChild(CdkVirtualScrollViewport) scrollBar: CdkVirtualScrollViewport;
|
||||
@ViewChild(QuickFiltersComponent) protected _quickFiltersComponent: QuickFiltersComponent;
|
||||
protected readonly _searchKey = 'searchField';
|
||||
protected readonly _selectionKey = 'fileId';
|
||||
protected readonly _sortKey = 'dossier-overview';
|
||||
@ -68,9 +72,9 @@ export class DossierOverviewScreenComponent
|
||||
private _fileChangedSub: Subscription;
|
||||
private _lastScrollPosition: number;
|
||||
private _lastOpenedFileId = '';
|
||||
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
|
||||
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
|
||||
@ViewChild('needsWorkFilter') private _needsWorkFilterComponent: FilterComponent;
|
||||
@ViewChild('statusFilter') private _statusFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('peopleFilter') private _peopleFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('needsWorkFilter') private _needsWorkFilterComponent: PopupFilterComponent;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
|
||||
constructor(
|
||||
@ -85,6 +89,7 @@ export class DossierOverviewScreenComponent
|
||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userPreferenceControllerService: UserPreferenceControllerService,
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
@ -95,11 +100,24 @@ export class DossierOverviewScreenComponent
|
||||
return this._appStateService.activeDossier;
|
||||
}
|
||||
|
||||
protected get _filterComponents(): FilterComponent[] {
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
get checkedRequiredFilters() {
|
||||
return this.quickFilters.filter(f => f.required && f.checked);
|
||||
}
|
||||
|
||||
get checkedNotRequiredFilters() {
|
||||
return this.quickFilters.filter(f => !f.required && f.checked);
|
||||
}
|
||||
|
||||
protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] {
|
||||
return [
|
||||
this._statusFilterComponent,
|
||||
this._peopleFilterComponent,
|
||||
this._needsWorkFilterComponent
|
||||
this._needsWorkFilterComponent,
|
||||
this._quickFiltersComponent
|
||||
];
|
||||
}
|
||||
|
||||
@ -117,6 +135,16 @@ export class DossierOverviewScreenComponent
|
||||
checker: annotationFilterChecker,
|
||||
matchAll: true,
|
||||
checkerArgs: this.permissionsService
|
||||
},
|
||||
{
|
||||
values: this.quickFilters,
|
||||
checker: (file: FileStatusWrapper) =>
|
||||
this.checkedRequiredFilters.reduce((acc, f) => acc && f.checker(file), true) &&
|
||||
(this.checkedNotRequiredFilters.length === 0 ||
|
||||
this.checkedNotRequiredFilters.reduce(
|
||||
(acc, f) => acc || f.checker(file),
|
||||
false
|
||||
))
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -286,6 +314,11 @@ export class DossierOverviewScreenComponent
|
||||
this.collapsedDetails = !this.collapsedDetails;
|
||||
}
|
||||
|
||||
recentlyModifiedChecker = (file: FileStatusWrapper) =>
|
||||
moment(file.lastUpdated)
|
||||
.add(this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS'), 'hours')
|
||||
.isAfter(moment());
|
||||
|
||||
protected _preFilter() {
|
||||
this.detailsContainerFilters = {
|
||||
needsWorkFilters: this.needsWorkFilters.map(f => ({ ...f })),
|
||||
@ -377,5 +410,41 @@ export class DossierOverviewScreenComponent
|
||||
(a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key]
|
||||
);
|
||||
this.needsWorkFilters = processFilters(this.needsWorkFilters, needsWorkFilters);
|
||||
|
||||
this._computeQuickFilters();
|
||||
}
|
||||
|
||||
private _computeQuickFilters() {
|
||||
if (this.allEntities.filter(this.recentlyModifiedChecker).length > 0) {
|
||||
this.quickFilters = [
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-overview.quick-filters.recent',
|
||||
required: true,
|
||||
checker: this.recentlyModifiedChecker
|
||||
}
|
||||
];
|
||||
} else {
|
||||
this.quickFilters = [];
|
||||
}
|
||||
|
||||
this.quickFilters = [
|
||||
...this.quickFilters,
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-overview.quick-filters.assigned-to-me',
|
||||
checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-overview.quick-filters.unassigned',
|
||||
checker: (file: FileStatusWrapper) => !file.currentReviewer
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
label: 'dossier-overview.quick-filters.assigned-to-others',
|
||||
checker: (file: FileStatusWrapper) => file.currentReviewer !== this.user.id
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,13 +20,11 @@ import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
|
||||
import { FileActionService } from '../../services/file-action.service';
|
||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import { handleFilterDelta, processFilters } from '@shared/components/filter/utils/filter-utils';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
@ -41,6 +39,11 @@ import { ViewMode } from '@models/file/view-mode';
|
||||
import { FileWorkloadComponent } from '../../components/file-workload/file-workload.component';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import {
|
||||
handleFilterDelta,
|
||||
processFilters
|
||||
} from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
|
||||
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { FilterModel } from '@shared/components/filter/model/filter.model';
|
||||
import { handleCheckedValue } from '@shared/components/filter/utils/filter-utils';
|
||||
import { SuperTypeSorter } from '@utils/sorters/super-type-sorter';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { handleCheckedValue } from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationProcessingService {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { ChangeDetectorRef, Component, Injector } from '@angular/core';
|
||||
import { FilterModel } from '../components/filter/model/filter.model';
|
||||
import { getFilteredEntities } from '../components/filter/utils/filter-utils';
|
||||
import { FilterComponent } from '../components/filter/filter.component';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { ScreenName, SortingOption, SortingService } from '@services/sorting.service';
|
||||
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
|
||||
import { PopupFilterComponent } from '../components/filters/popup-filter/popup-filter.component';
|
||||
import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils';
|
||||
import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component';
|
||||
|
||||
// Functionalities: Filter, search, select, sort
|
||||
|
||||
@ -68,7 +69,7 @@ export abstract class BaseListingComponent<T = any> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected get _filterComponents(): FilterComponent[] {
|
||||
protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@ -9,4 +9,5 @@ export interface FilterModel {
|
||||
matches?: number;
|
||||
filters?: FilterModel[];
|
||||
checker?: (obj?) => boolean;
|
||||
required?: boolean;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
@import '../../../../../assets/styles/red-variables';
|
||||
@import '../../../../../../assets/styles/red-variables';
|
||||
|
||||
.filter-menu-options,
|
||||
.filter-menu-header {
|
||||
@ -12,9 +12,9 @@ import { handleCheckedValue } from './utils/filter-utils';
|
||||
import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-filter',
|
||||
templateUrl: './filter.component.html',
|
||||
styleUrls: ['./filter.component.scss'],
|
||||
selector: 'redaction-popup-filter',
|
||||
templateUrl: './popup-filter.component.html',
|
||||
styleUrls: ['./popup-filter.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: MAT_CHECKBOX_DEFAULT_OPTIONS,
|
||||
@ -25,7 +25,7 @@ import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class FilterComponent implements OnChanges {
|
||||
export class PopupFilterComponent implements OnChanges {
|
||||
@Output() filtersChanged = new EventEmitter<{
|
||||
primary: FilterModel[];
|
||||
secondary?: FilterModel[];
|
||||
@ -1,7 +1,7 @@
|
||||
import { FilterModel } from '../model/filter.model';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FileStatusWrapper } from '../../../../../../models/file/file-status.wrapper';
|
||||
import { DossierWrapper } from '../../../../../../state/model/dossier.wrapper';
|
||||
import { PermissionsService } from '../../../../../../services/permissions.service';
|
||||
|
||||
export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) {
|
||||
copySettings(oldFilters, newFilters);
|
||||
@ -157,20 +157,23 @@ export const annotationFilterChecker = (
|
||||
}
|
||||
};
|
||||
|
||||
export const dossierStatusChecker = (pw: DossierWrapper, filter: FilterModel) =>
|
||||
pw.hasStatus(filter.key);
|
||||
export const dossierStatusChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.hasStatus(filter.key);
|
||||
|
||||
export const dossierMemberChecker = (pw: DossierWrapper, filter: FilterModel) =>
|
||||
pw.hasMember(filter.key);
|
||||
export const dossierMemberChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.hasMember(filter.key);
|
||||
|
||||
export const dossierTemplateChecker = (pw: DossierWrapper, filter: FilterModel) =>
|
||||
pw.dossierTemplateId === filter.key;
|
||||
export const dossierTemplateChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.dossierTemplateId === filter.key;
|
||||
|
||||
export const dueDateChecker = (pw: DossierWrapper, filter: FilterModel) =>
|
||||
pw.dueDateMatches(filter.key);
|
||||
export const dueDateChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.dueDateMatches(filter.key);
|
||||
|
||||
export const addedDateChecker = (pw: DossierWrapper, filter: FilterModel) =>
|
||||
pw.addedDateMatches(filter.key);
|
||||
export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.addedDateMatches(filter.key);
|
||||
|
||||
export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.approverIds.includes(filter.key);
|
||||
|
||||
export function getFilteredEntities(
|
||||
entities: any[],
|
||||
@ -0,0 +1,8 @@
|
||||
<div
|
||||
(click)="toggle(filter)"
|
||||
*ngFor="let filter of filters"
|
||||
[class.active]="filter.checked"
|
||||
class="quick-filter"
|
||||
>
|
||||
{{ filter.label | translate }}
|
||||
</div>
|
||||
@ -0,0 +1,34 @@
|
||||
@import '../../../../../../assets/styles/red-variables';
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.quick-filter {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $grey-5;
|
||||
border-radius: 17px;
|
||||
background-color: $grey-6;
|
||||
padding: 0 14px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FilterModel } from '../popup-filter/model/filter.model';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-quick-filters',
|
||||
templateUrl: './quick-filters.component.html',
|
||||
styleUrls: ['./quick-filters.component.scss']
|
||||
})
|
||||
export class QuickFiltersComponent {
|
||||
@Output() filtersChanged = new EventEmitter<FilterModel[]>();
|
||||
@Input() filters: FilterModel[];
|
||||
|
||||
constructor() {}
|
||||
|
||||
get hasActiveFilters(): boolean {
|
||||
return this.filters.filter(f => f.checked).length > 0;
|
||||
}
|
||||
|
||||
deactivateAllFilters() {
|
||||
for (const filter of this.filters) {
|
||||
filter.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggle(filter: FilterModel) {
|
||||
filter.checked = !filter.checked;
|
||||
this.filtersChanged.emit(this.filters);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { Color } from '@utils/types';
|
||||
import { FilterModel } from '../filter/model/filter.model';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
|
||||
export class DoughnutChartConfig {
|
||||
value: number;
|
||||
|
||||
@ -24,7 +24,6 @@ import { HasScrollbarDirective } from './directives/has-scrollbar.directive';
|
||||
import { DictionaryAnnotationIconComponent } from './components/dictionary-annotation-icon/dictionary-annotation-icon.component';
|
||||
import { HiddenActionComponent } from './components/hidden-action/hidden-action.component';
|
||||
import { ConfirmationDialogComponent } from './dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { FilterComponent } from './components/filter/filter.component';
|
||||
import { EmptyStateComponent } from './components/empty-state/empty-state.component';
|
||||
import { SortByPipe } from './components/sort-pipe/sort-by.pipe';
|
||||
import { RoundCheckboxComponent } from './components/checkbox/round-checkbox.component';
|
||||
@ -35,6 +34,8 @@ import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-
|
||||
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
|
||||
import { SideNavComponent } from '@shared/components/side-nav/side-nav.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { QuickFiltersComponent } from './components/filters/quick-filters/quick-filters.component';
|
||||
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
|
||||
|
||||
const buttons = [
|
||||
ChevronButtonComponent,
|
||||
@ -55,7 +56,7 @@ const components = [
|
||||
StatusBarComponent,
|
||||
DictionaryAnnotationIconComponent,
|
||||
HiddenActionComponent,
|
||||
FilterComponent,
|
||||
PopupFilterComponent,
|
||||
ConfirmationDialogComponent,
|
||||
EmptyStateComponent,
|
||||
SortByPipe,
|
||||
@ -84,9 +85,9 @@ const modules = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...utils],
|
||||
declarations: [...components, ...utils, QuickFiltersComponent],
|
||||
imports: [CommonModule, ...modules, MonacoEditorModule],
|
||||
exports: [...modules, ...components, ...utils],
|
||||
exports: [...modules, ...components, ...utils, QuickFiltersComponent],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"LICENSE_START": "01-01-2021",
|
||||
"LICENSE_END": "31-12-2021",
|
||||
"LICENSE_PAGE_COUNT": 1000000,
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"MAX_FILE_SIZE_MB": 100,
|
||||
"DELETE_RETENTION_HOURS": 96
|
||||
}
|
||||
|
||||
@ -137,6 +137,12 @@
|
||||
},
|
||||
"no-match": {
|
||||
"title": "No dossiers match your current filters."
|
||||
},
|
||||
"quick-filters": {
|
||||
"my-dossiers": "My Dossiers",
|
||||
"to-approve": "To Approve",
|
||||
"to-review": "To Review",
|
||||
"other": "Other"
|
||||
}
|
||||
},
|
||||
"add-dossier-dialog": {
|
||||
@ -327,6 +333,12 @@
|
||||
"reanalyse-dossier": {
|
||||
"success": "Files scheduled for reanalysis.",
|
||||
"error": "Failed to schedule files for reanalysis. Please try again."
|
||||
},
|
||||
"quick-filters": {
|
||||
"recent": "Recent",
|
||||
"assigned-to-me": "Assigned to me",
|
||||
"unassigned": "Unassigned",
|
||||
"assigned-to-others": "Assigned to others"
|
||||
}
|
||||
},
|
||||
"file-preview": {
|
||||
|
||||
@ -10,6 +10,7 @@ ADMIN_CONTACT_URL="${ADMIN_CONTACT_URL:-}"
|
||||
AUTO_READ_TIME="${AUTO_READ_TIME:-1.5}"
|
||||
MAX_FILE_SIZE_MB="${MAX_FILE_SIZE_MB:-50}"
|
||||
DELETE_RETENTION_HOURS="${DELETE_RETENTION_HOURS:-96}"
|
||||
RECENT_PERIOD_IN_HOURS="${RECENT_PERIOD_IN_HOURS:-24}"
|
||||
|
||||
BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}"
|
||||
|
||||
@ -35,6 +36,7 @@ echo '{
|
||||
"APP_NAME":"'"$APP_NAME"'",
|
||||
"AUTO_READ_TIME":'"$AUTO_READ_TIME"',
|
||||
"MAX_FILE_SIZE_MB":"'"$MAX_FILE_SIZE_MB"'",
|
||||
"RECENT_PERIOD_IN_HOURS":"'"RECENT_PERIOD_IN_HOURS"'",
|
||||
"DELETE_RETENTION_HOURS":"'"$DELETE_RETENTION_HOURS"'",
|
||||
"API_URL":"'"$API_URL"'"
|
||||
}' > /usr/share/nginx/html/ui/assets/config/config.json
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user