Updated dashboard
This commit is contained in:
parent
96176589ca
commit
787209d1e4
@ -1,7 +1,9 @@
|
|||||||
<div class="row pt-3 pb-3 pb-md-1 mb-3 border-bottom align-items-center">
|
<div class="row pt-3 pb-3 pb-md-1 mb-3 border-bottom align-items-center">
|
||||||
<div class="col-md text-truncate">
|
<div class="col-md text-truncate">
|
||||||
<p class="h2 text-truncate" style="line-height: 1.4">{{title}}</p>
|
<h3 class="text-truncate" style="line-height: 1.4">
|
||||||
<p *ngIf="subTitle" class="h5 text-truncate" style="line-height: 1.4">{{subTitle}}</p>
|
{{title}}
|
||||||
|
<span *ngIf="subTitle" class="h6 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle}}</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-toolbar col col-md-auto">
|
<div class="btn-toolbar col col-md-auto">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@ -25,7 +25,7 @@ describe('PageHeaderComponent', () => {
|
|||||||
component.title = 'Foo'
|
component.title = 'Foo'
|
||||||
component.subTitle = 'Bar'
|
component.subTitle = 'Bar'
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.nativeElement.textContent).toContain('FooBar')
|
expect(fixture.nativeElement.textContent).toContain('Foo Bar')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set html title', () => {
|
it('should set html title', () => {
|
||||||
|
@ -1,29 +1,36 @@
|
|||||||
<pngx-page-header title="Dashboard" [subTitle]="subtitle" i18n-title>
|
<pngx-page-header title="Dashboard" [subTitle]="subtitle" i18n-title>
|
||||||
<pngx-logo extra_classes="d-none d-md-block"></pngx-logo>
|
<pngx-logo extra_classes="d-none d-md-block mt-n2 p-1" height="4.5rem"></pngx-logo>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8">
|
<div class="col col-md-9 mb-3">
|
||||||
<div tourAnchor="tour.dashboard">
|
<div class="row row-cols-1 g-4" tourAnchor="tour.dashboard">
|
||||||
<ng-container *ngIf="savedViewService.loading">
|
<div *ngIf="savedViewService.loading" class="col">
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
<ng-container i18n>Loading...</ng-container>
|
<ng-container i18n>Loading...</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="settingsService.offerTour()" class="col">
|
||||||
|
<pngx-welcome-widget (dismiss)="completeTour()"></pngx-welcome-widget>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||||
|
<ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
|
||||||
|
<div class="col">
|
||||||
|
<pngx-saved-view-widget [savedView]="v"></pngx-saved-view-widget>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
<pngx-welcome-widget *ngIf="settingsService.offerTour()" (dismiss)="completeTour()"></pngx-welcome-widget>
|
</div>
|
||||||
|
<div class="col">
|
||||||
<div *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
<div class="row row-cols-1 g-4">
|
||||||
<ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
|
<div class="col">
|
||||||
<pngx-saved-view-widget [savedView]="v"></pngx-saved-view-widget>
|
<pngx-statistics-widget></pngx-statistics-widget>
|
||||||
</ng-container>
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<pngx-upload-file-widget></pngx-upload-file-widget>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4">
|
|
||||||
|
|
||||||
<pngx-statistics-widget></pngx-statistics-widget>
|
|
||||||
|
|
||||||
<pngx-upload-file-widget></pngx-upload-file-widget>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,20 +3,28 @@
|
|||||||
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" i18n>Show all</a>
|
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" i18n>Show all</a>
|
||||||
|
|
||||||
|
|
||||||
<table content class="table table-sm table-hover table-borderless mb-0">
|
<table content class="table table-hover mb-0 align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" i18n>Created</th>
|
<th scope="col" i18n>Created</th>
|
||||||
<th scope="col" i18n>Title</th>
|
<th scope="col" i18n>Title</th>
|
||||||
|
<th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
|
||||||
|
<th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
<tbody *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<tr *ngFor="let doc of documents" (mouseleave)="mouseLeaveCard()">
|
<tr *ngFor="let doc of documents" (mouseleave)="mouseLeaveCard()">
|
||||||
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
|
<td class="py-3"><a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
|
||||||
<td class="position-relative">
|
<td class="py-3">
|
||||||
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<pngx-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag></a>
|
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none">{{doc.title | documentTitle}}</a>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 d-none d-md-table-cell">
|
||||||
|
<pngx-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
|
||||||
|
</td>
|
||||||
|
<td class="position-relative py-3 d-none d-md-table-cell">
|
||||||
|
<a *ngIf="doc.correspondent !== null" class="btn-link" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
|
||||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||||
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle"
|
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
|
||||||
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
||||||
autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreview(doc)" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreview(doc)" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||||
<svg class="buttonicon-xs" fill="currentColor">
|
<svg class="buttonicon-xs" fill="currentColor">
|
||||||
@ -26,7 +34,7 @@
|
|||||||
<ng-template #previewContent>
|
<ng-template #previewContent>
|
||||||
<object [data]="getPreviewUrl(doc) | safeUrl" class="preview" width="100%"></object>
|
<object [data]="getPreviewUrl(doc) | safeUrl" class="preview" width="100%"></object>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<a [href]="getDownloadUrl(doc)" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
<a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||||
<svg class="buttonicon-xs" fill="currentColor">
|
<svg class="buttonicon-xs" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -5,9 +5,12 @@ table {
|
|||||||
|
|
||||||
th:first-child {
|
th:first-child {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody app-tag {
|
tbody pngx-tag {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,3 +25,8 @@ tr:hover .btn-group {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.py-3 {
|
||||||
|
padding-top: 0.75em !important;
|
||||||
|
padding-bottom: 0.75em !important;
|
||||||
|
}
|
||||||
|
@ -52,6 +52,7 @@ const documentResults = [
|
|||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'doc3',
|
title: 'doc3',
|
||||||
|
correspondent: 0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -6,18 +6,22 @@ import {
|
|||||||
QueryList,
|
QueryList,
|
||||||
ViewChildren,
|
ViewChildren,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Params, Router } from '@angular/router'
|
||||||
import { Subject, takeUntil } from 'rxjs'
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
import {
|
||||||
|
FILTER_CORRESPONDENT,
|
||||||
|
FILTER_HAS_TAGS_ALL,
|
||||||
|
} from 'src/app/data/filter-rule-type'
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-saved-view-widget',
|
selector: 'pngx-saved-view-widget',
|
||||||
@ -38,7 +42,8 @@ export class SavedViewWidgetComponent
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private list: DocumentListViewService,
|
private list: DocumentListViewService,
|
||||||
private consumerStatusService: ConsumerStatusService,
|
private consumerStatusService: ConsumerStatusService,
|
||||||
public openDocumentsService: OpenDocumentsService
|
public openDocumentsService: OpenDocumentsService,
|
||||||
|
public documentListViewService: DocumentListViewService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@ -141,4 +146,15 @@ export class SavedViewWidgetComponent
|
|||||||
mouseLeaveCard() {
|
mouseLeaveCard() {
|
||||||
this.popover?.close()
|
this.popover?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCorrespondentQueryParams(correspondentId: number): Params {
|
||||||
|
return correspondentId !== undefined
|
||||||
|
? queryParamsFromFilterRules([
|
||||||
|
{
|
||||||
|
rule_type: FILTER_CORRESPONDENT,
|
||||||
|
value: correspondentId.toString(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,5 +42,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="list-group border-light mt-3">
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||||
|
<a *ngIf="statistics?.tag_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
||||||
|
<ng-container i18n>Tags</ng-container>:
|
||||||
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||||
|
<a *ngIf="statistics?.correspondent_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
||||||
|
<ng-container i18n>Correspondents</ng-container>:
|
||||||
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||||
|
<a *ngIf="statistics?.document_type_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||||
|
<ng-container i18n>Document Types</ng-container>:
|
||||||
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||||
|
<a *ngIf="statistics?.storage_path_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||||
|
<ng-container i18n>Storage Paths</ng-container>:
|
||||||
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</pngx-widget-frame>
|
</pngx-widget-frame>
|
||||||
|
@ -11,6 +11,7 @@ import { environment } from 'src/environments/environment'
|
|||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import { routes } from 'src/app/app-routing.module'
|
import { routes } from 'src/app/app-routing.module'
|
||||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||||
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
|
|
||||||
describe('StatisticsWidgetComponent', () => {
|
describe('StatisticsWidgetComponent', () => {
|
||||||
let component: StatisticsWidgetComponent
|
let component: StatisticsWidgetComponent
|
||||||
@ -19,7 +20,11 @@ describe('StatisticsWidgetComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [StatisticsWidgetComponent, WidgetFrameComponent],
|
declarations: [
|
||||||
|
StatisticsWidgetComponent,
|
||||||
|
WidgetFrameComponent,
|
||||||
|
IfPermissionsDirective,
|
||||||
|
],
|
||||||
providers: [PermissionsGuard],
|
providers: [PermissionsGuard],
|
||||||
imports: [
|
imports: [
|
||||||
HttpClientTestingModule,
|
HttpClientTestingModule,
|
||||||
|
@ -6,6 +6,7 @@ import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
|||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import * as mimeTypeNames from 'mime-names'
|
import * as mimeTypeNames from 'mime-names'
|
||||||
|
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
||||||
|
|
||||||
export interface Statistics {
|
export interface Statistics {
|
||||||
documents_total?: number
|
documents_total?: number
|
||||||
@ -13,6 +14,10 @@ export interface Statistics {
|
|||||||
inbox_tag?: number
|
inbox_tag?: number
|
||||||
document_file_type_counts?: DocumentFileType[]
|
document_file_type_counts?: DocumentFileType[]
|
||||||
character_count?: number
|
character_count?: number
|
||||||
|
tag_count?: number
|
||||||
|
correspondent_count?: number
|
||||||
|
document_type_count?: number
|
||||||
|
storage_path_count?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocumentFileType {
|
interface DocumentFileType {
|
||||||
@ -25,14 +30,19 @@ interface DocumentFileType {
|
|||||||
templateUrl: './statistics-widget.component.html',
|
templateUrl: './statistics-widget.component.html',
|
||||||
styleUrls: ['./statistics-widget.component.scss'],
|
styleUrls: ['./statistics-widget.component.scss'],
|
||||||
})
|
})
|
||||||
export class StatisticsWidgetComponent implements OnInit, OnDestroy {
|
export class StatisticsWidgetComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
loading: boolean = true
|
loading: boolean = true
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private consumerStatusService: ConsumerStatusService,
|
private consumerStatusService: ConsumerStatusService,
|
||||||
private documentListViewService: DocumentListViewService
|
private documentListViewService: DocumentListViewService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
statistics: Statistics = {}
|
statistics: Statistics = {}
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div content tourAnchor="tour.upload-widget">
|
<div content tourAnchor="tour.upload-widget" class="h-100">
|
||||||
<form>
|
<form class="h-100">
|
||||||
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
|
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
|
||||||
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
|
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card h-100"
|
||||||
multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
|
multiple="true" contentClassName="justify-content-center d-flex flex-column text-muted align-items-center py-5 px-2 h-100" [showBrowseBtn]=true
|
||||||
browseBtnClassName="btn btn-sm btn-outline-primary ms-2" i18n-dropZoneLabel i18n-browseBtnLabel>
|
browseBtnClassName="btn btn-sm btn-outline-primary mt-2" i18n-dropZoneLabel i18n-browseBtnLabel>
|
||||||
</ngx-file-drop>
|
</ngx-file-drop>
|
||||||
</form>
|
</form>
|
||||||
<p class="mt-3" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
|
<p class="mt-3" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="card mb-3 shadow-sm bg-light">
|
<div class="card h-100 shadow-sm bg-light">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">{{title}}</h5>
|
<h6 class="card-title mb-0">{{title}}</h6>
|
||||||
<ng-container *ngIf="loading">
|
<ng-container *ngIf="loading">
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
<div class="visually-hidden" i18n>Loading...</div>
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
@ -214,6 +214,10 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card .table {
|
||||||
|
border-color: var(--bs-gray-800);
|
||||||
|
}
|
||||||
|
|
||||||
.alert-secondary {
|
.alert-secondary {
|
||||||
background-color: var(--bs-light);
|
background-color: var(--bs-light);
|
||||||
border-color: var(--pngx-bg-darker);
|
border-color: var(--pngx-bg-darker);
|
||||||
|
@ -1571,6 +1571,13 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
tag_inbox = Tag.objects.create(name="t1", is_inbox_tag=True)
|
tag_inbox = Tag.objects.create(name="t1", is_inbox_tag=True)
|
||||||
|
Tag.objects.create(name="t2")
|
||||||
|
Tag.objects.create(name="t3")
|
||||||
|
Correspondent.objects.create(name="c1")
|
||||||
|
Correspondent.objects.create(name="c2")
|
||||||
|
DocumentType.objects.create(name="dt1")
|
||||||
|
StoragePath.objects.create(name="sp1")
|
||||||
|
StoragePath.objects.create(name="sp2")
|
||||||
|
|
||||||
doc1.tags.add(tag_inbox)
|
doc1.tags.add(tag_inbox)
|
||||||
|
|
||||||
@ -1588,6 +1595,10 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
self.assertEqual(response.data["character_count"], 11)
|
self.assertEqual(response.data["character_count"], 11)
|
||||||
|
self.assertEqual(response.data["tag_count"], 3)
|
||||||
|
self.assertEqual(response.data["correspondent_count"], 2)
|
||||||
|
self.assertEqual(response.data["document_type_count"], 1)
|
||||||
|
self.assertEqual(response.data["storage_path_count"], 2)
|
||||||
|
|
||||||
def test_statistics_no_inbox_tag(self):
|
def test_statistics_no_inbox_tag(self):
|
||||||
Document.objects.create(title="none1", checksum="A")
|
Document.objects.create(title="none1", checksum="A")
|
||||||
|
@ -907,6 +907,39 @@ class StatisticsView(APIView):
|
|||||||
if user is None
|
if user is None
|
||||||
else get_objects_for_user_owner_aware(user, "documents.view_tag", Tag)
|
else get_objects_for_user_owner_aware(user, "documents.view_tag", Tag)
|
||||||
)
|
)
|
||||||
|
correspondent_count = (
|
||||||
|
Correspondent.objects.count()
|
||||||
|
if user is None
|
||||||
|
else len(
|
||||||
|
get_objects_for_user_owner_aware(
|
||||||
|
user,
|
||||||
|
"documents.view_correspondent",
|
||||||
|
Correspondent,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
document_type_count = (
|
||||||
|
DocumentType.objects.count()
|
||||||
|
if user is None
|
||||||
|
else len(
|
||||||
|
get_objects_for_user_owner_aware(
|
||||||
|
user,
|
||||||
|
"documents.view_documenttype",
|
||||||
|
DocumentType,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
storage_path_count = (
|
||||||
|
StoragePath.objects.count()
|
||||||
|
if user is None
|
||||||
|
else len(
|
||||||
|
get_objects_for_user_owner_aware(
|
||||||
|
user,
|
||||||
|
"documents.view_storagepath",
|
||||||
|
StoragePath,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
documents_total = documents.count()
|
documents_total = documents.count()
|
||||||
|
|
||||||
@ -941,6 +974,10 @@ class StatisticsView(APIView):
|
|||||||
"inbox_tag": inbox_tag.first().pk if inbox_tag.exists() else None,
|
"inbox_tag": inbox_tag.first().pk if inbox_tag.exists() else None,
|
||||||
"document_file_type_counts": document_file_type_counts,
|
"document_file_type_counts": document_file_type_counts,
|
||||||
"character_count": character_count,
|
"character_count": character_count,
|
||||||
|
"tag_count": len(tags),
|
||||||
|
"correspondent_count": correspondent_count,
|
||||||
|
"document_type_count": document_type_count,
|
||||||
|
"storage_path_count": storage_path_count,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user