mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
better toasts, better dashboard, first implementation of consumer status
This commit is contained in:
@@ -6,59 +6,11 @@
|
||||
|
||||
<div class='row'>
|
||||
<div class="col-lg">
|
||||
<ng-container *ngFor="let v of savedDashboardViews">
|
||||
<h4>{{v.viewConfig.title}}</h4>
|
||||
|
||||
<table class="table table-sm table-hover table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date created</th>
|
||||
<th scope="col">Document</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let doc of v.documents" routerLink="/documents/{{doc.id}}">
|
||||
<td>{{doc.created | date}}</td>
|
||||
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</ng-container>
|
||||
<ng-container *ngIf="savedDashboardViews.length == 0">
|
||||
<h4>Saved views</h4>
|
||||
<p>This space is reserved to display your saved views. Go to your documents and save a view to have it displayed here!</p>
|
||||
</ng-container>
|
||||
|
||||
<app-saved-view-widget [viewConfig]="conf" *ngFor="let conf of savedViewConfigService.getDashboardConfigs()"></app-saved-view-widget>
|
||||
</div>
|
||||
<div class="col-lg">
|
||||
<h4>Statistics</h4>
|
||||
<p>Documents in inbox: {{statistics.documents_inbox}}</p>
|
||||
<p>Total documents: {{statistics.documents_total}}</p>
|
||||
<h4>Upload new Document</h4>
|
||||
<form>
|
||||
<ngx-file-drop
|
||||
dropZoneLabel="Drop documents here"
|
||||
(onFileDrop)="dropped($event)"
|
||||
(onFileOver)="fileOver($event)"
|
||||
(onFileLeave)="fileLeave($event)"
|
||||
dropZoneClassName="bg-light mt-4 card">
|
||||
|
||||
</ngx-file-drop>
|
||||
</form>
|
||||
<h5 class="mt-3">Document conumser status</h5>
|
||||
<p>This is what it might look like in the future.</p>
|
||||
<div class="card bg-light mb-2">
|
||||
<div class="card-body">
|
||||
<p class="card-text"><strong>Filename.pdf:</strong> Running tesseract on page 4/8...</p>
|
||||
<p><ngb-progressbar type="info" [value]="50"></ngb-progressbar></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-light mb-2">
|
||||
<div class="card-body">
|
||||
<p class="card-text"><strong>Filename2.pdf:</strong> Completed.</p>
|
||||
<p><ngb-progressbar type="success" [value]="100"></ngb-progressbar></p>
|
||||
</div>
|
||||
</div>
|
||||
<app-statistics-widget></app-statistics-widget>
|
||||
<app-file-upload-widget></app-file-upload-widget>
|
||||
<app-consumer-status-widget></app-consumer-status-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -4,14 +4,9 @@ import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DocumentService } from 'src/app/services/rest/document.service';
|
||||
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface Statistics {
|
||||
documents_total?: number
|
||||
documents_inbox?: number
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
@@ -19,53 +14,9 @@ export interface Statistics {
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
constructor(private documentService: DocumentService, private toastService: ToastService,
|
||||
public savedViewConfigService: SavedViewConfigService, private http: HttpClient) { }
|
||||
|
||||
|
||||
savedDashboardViews = []
|
||||
statistics: Statistics = {}
|
||||
constructor(public savedViewConfigService: SavedViewConfigService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.savedViewConfigService.getDashboardConfigs().forEach(config => {
|
||||
this.documentService.list(1,10,config.sortField,config.sortDirection,config.filterRules).subscribe(result => {
|
||||
this.savedDashboardViews.push({viewConfig: config, documents: result.results})
|
||||
})
|
||||
})
|
||||
this.getStatistics().subscribe(statistics => {
|
||||
this.statistics = statistics
|
||||
})
|
||||
}
|
||||
|
||||
getStatistics(): Observable<Statistics> {
|
||||
return this.http.get(`${environment.apiBaseUrl}statistics/`)
|
||||
}
|
||||
|
||||
|
||||
public fileOver(event){
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
public fileLeave(event){
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
public dropped(files: NgxFileDropEntry[]) {
|
||||
for (const droppedFile of files) {
|
||||
if (droppedFile.fileEntry.isFile) {
|
||||
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
|
||||
console.log(fileEntry)
|
||||
fileEntry.file((file: File) => {
|
||||
console.log(file)
|
||||
const formData = new FormData()
|
||||
formData.append('document', file, file.name)
|
||||
this.documentService.uploadDocument(formData).subscribe(result => {
|
||||
this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly."))
|
||||
}, error => {
|
||||
this.toastService.showToast(Toast.makeError("An error has occured while uploading the document. Sorry!"))
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<h4 class="mt-3">Document consumer status</h4>
|
||||
|
||||
<div class="p-2 mb-1 border bg-light" *ngFor="let s of getStatus()">
|
||||
<div class="mb-1"><strong>{{s.filename}}:</strong> {{s.message}}</div>
|
||||
<ngb-progressbar [type]="getType(s.status)" [value]="s.current_progress" [max]="s.max_progress" class="mb-2"></ngb-progressbar>
|
||||
<div *ngIf="isFinished(s)">
|
||||
<button *ngIf="s.document_id" class="btn btn-sm btn-outline-secondary mr-2" routerLink="/documents/{{s.document_id}}" (click)="dismiss(s)">Open document</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="dismiss(s)">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConsumerStatusWidgetComponent } from './consumer-status-widget.component';
|
||||
|
||||
describe('ConsumerStatusWidgetComponent', () => {
|
||||
let component: ConsumerStatusWidgetComponent;
|
||||
let fixture: ComponentFixture<ConsumerStatusWidgetComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ConsumerStatusWidgetComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConsumerStatusWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ConsumerStatusService, FileStatus } from 'src/app/services/consumer-status.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-consumer-status-widget',
|
||||
templateUrl: './consumer-status-widget.component.html',
|
||||
styleUrls: ['./consumer-status-widget.component.css']
|
||||
})
|
||||
export class ConsumerStatusWidgetComponent implements OnInit {
|
||||
|
||||
constructor(private consumerStatusService: ConsumerStatusService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return this.consumerStatusService.consumerStatus
|
||||
}
|
||||
|
||||
isFinished(status: FileStatus) {
|
||||
return status.status == "FAILED" || status.status == "SUCCESS"
|
||||
}
|
||||
|
||||
getType(status) {
|
||||
switch (status) {
|
||||
case "WORKING": return "primary"
|
||||
case "FAILED": return "danger"
|
||||
case "SUCCESS": return "success"
|
||||
}
|
||||
}
|
||||
|
||||
dismiss(status: FileStatus) {
|
||||
this.consumerStatusService.dismiss(status)
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
<h4>Upload new Document</h4>
|
||||
<form>
|
||||
<ngx-file-drop
|
||||
dropZoneLabel="Drop documents here"
|
||||
(onFileDrop)="dropped($event)"
|
||||
(onFileOver)="fileOver($event)"
|
||||
(onFileLeave)="fileLeave($event)"
|
||||
dropZoneClassName="bg-light mt-4 card">
|
||||
|
||||
</ngx-file-drop>
|
||||
</form>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FileUploadWidgetComponent } from './file-upload-widget.component';
|
||||
|
||||
describe('FileUploadWidgetComponent', () => {
|
||||
let component: FileUploadWidgetComponent;
|
||||
let fixture: ComponentFixture<FileUploadWidgetComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FileUploadWidgetComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileUploadWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,44 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
|
||||
import { DocumentService } from 'src/app/services/rest/document.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-upload-widget',
|
||||
templateUrl: './file-upload-widget.component.html',
|
||||
styleUrls: ['./file-upload-widget.component.css']
|
||||
})
|
||||
export class FileUploadWidgetComponent implements OnInit {
|
||||
|
||||
constructor(private documentService: DocumentService, private toastService: ToastService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public fileOver(event){
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
public fileLeave(event){
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
public dropped(files: NgxFileDropEntry[]) {
|
||||
for (const droppedFile of files) {
|
||||
if (droppedFile.fileEntry.isFile) {
|
||||
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
|
||||
console.log(fileEntry)
|
||||
fileEntry.file((file: File) => {
|
||||
console.log(file)
|
||||
const formData = new FormData()
|
||||
formData.append('document', file, file.name)
|
||||
this.documentService.uploadDocument(formData).subscribe(result => {
|
||||
this.toastService.showInfo("The document has been uploaded and will be processed by the consumer shortly.")
|
||||
}, error => {
|
||||
this.toastService.showError("An error has occured while uploading the document. Sorry!")
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<h4>{{viewConfig.title}}</h4>
|
||||
|
||||
<table class="table table-sm table-hover table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date created</th>
|
||||
<th scope="col">Document</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}">
|
||||
<td>{{doc.created | date}}</td>
|
||||
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SavedViewWidgetComponent } from './saved-view-widget.component';
|
||||
|
||||
describe('SavedViewWidgetComponent', () => {
|
||||
let component: SavedViewWidgetComponent;
|
||||
let fixture: ComponentFixture<SavedViewWidgetComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SavedViewWidgetComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SavedViewWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,41 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
||||
import { SavedViewConfig } from 'src/app/data/saved-view-config';
|
||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
|
||||
import { DocumentService } from 'src/app/services/rest/document.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-saved-view-widget',
|
||||
templateUrl: './saved-view-widget.component.html',
|
||||
styleUrls: ['./saved-view-widget.component.css']
|
||||
})
|
||||
export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(private documentService: DocumentService, private consumerStatusService: ConsumerStatusService) { }
|
||||
|
||||
@Input()
|
||||
viewConfig: SavedViewConfig
|
||||
|
||||
documents: PaperlessDocument[]
|
||||
|
||||
subscription: Subscription
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload()
|
||||
this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
|
||||
this.reload()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe()
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.documentService.list(1,10,this.viewConfig.sortField,this.viewConfig.sortDirection,this.viewConfig.filterRules).subscribe(result => {
|
||||
this.documents = result.results
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<h4>Statistics</h4>
|
||||
<p>Documents in inbox: {{statistics.documents_inbox}}</p>
|
||||
<p>Total documents: {{statistics.documents_total}}</p>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StatisticsWidgetComponent } from './statistics-widget.component';
|
||||
|
||||
describe('StatisticsWidgetComponent', () => {
|
||||
let component: StatisticsWidgetComponent;
|
||||
let fixture: ComponentFixture<StatisticsWidgetComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ StatisticsWidgetComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StatisticsWidgetComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,32 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface Statistics {
|
||||
documents_total?: number
|
||||
documents_inbox?: number
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-statistics-widget',
|
||||
templateUrl: './statistics-widget.component.html',
|
||||
styleUrls: ['./statistics-widget.component.css']
|
||||
})
|
||||
export class StatisticsWidgetComponent implements OnInit {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
statistics: Statistics = {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getStatistics().subscribe(statistics => {
|
||||
this.statistics = statistics
|
||||
})
|
||||
}
|
||||
|
||||
getStatistics(): Observable<Statistics> {
|
||||
return this.http.get(`${environment.apiBaseUrl}statistics/`)
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user