Some of the lists etc

This commit is contained in:
shamoon 2024-12-05 00:22:34 -08:00
parent f0de7cfe02
commit 183425959a
No known key found for this signature in database
22 changed files with 387 additions and 247 deletions

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@
</tr>
}
@for (document of documentsInTrash; track document.id) {
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()">
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()" class="data-row" [class.reveal]="reveal">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">

View File

@ -0,0 +1,8 @@
.data-row {
opacity: 0;
transition: opacity .2s;
}
.reveal {
opacity: 1;
}

View File

@ -69,6 +69,7 @@ describe('TrashComponent', () => {
})
it('should call correct service method on reload', () => {
jest.useFakeTimers()
const trashSpy = jest.spyOn(trashService, 'getTrash')
trashSpy.mockReturnValue(
of({
@ -78,6 +79,7 @@ describe('TrashComponent', () => {
})
)
component.reload()
jest.advanceTimersByTime(100)
expect(trashSpy).toHaveBeenCalled()
expect(component.documentsInTrash).toEqual(documentsInTrash)
})

View File

@ -4,7 +4,7 @@ import { Document } from 'src/app/data/document'
import { ToastService } from 'src/app/services/toast.service'
import { TrashService } from 'src/app/services/trash.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { Subject, takeUntil } from 'rxjs'
import { delay, Subject, takeUntil, tap } from 'rxjs'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { Router } from '@angular/router'
@ -21,6 +21,7 @@ export class TrashComponent implements OnDestroy {
public page: number = 1
public totalDocuments: number
public isLoading: boolean = false
public reveal: boolean = false
unsubscribeNotifier: Subject<void> = new Subject()
constructor(
@ -40,12 +41,20 @@ export class TrashComponent implements OnDestroy {
reload() {
this.isLoading = true
this.trashService.getTrash(this.page).subscribe((r) => {
this.documentsInTrash = r.results
this.totalDocuments = r.count
this.isLoading = false
this.selectedDocuments.clear()
})
this.trashService
.getTrash(this.page)
.pipe(
tap((r) => {
this.documentsInTrash = r.results
this.totalDocuments = r.count
this.selectedDocuments.clear()
}),
delay(100)
)
.subscribe(() => {
this.reveal = true
this.isLoading = false
})
}
delete(document: Document) {

View File

@ -12,7 +12,6 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
import { ToastService } from 'src/app/services/toast.service'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { ManagementListComponent } from '../management-list/management-list.component'
import { takeUntil } from 'rxjs'
@Component({
selector: 'pngx-correspondent-list',
@ -65,24 +64,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo
}
public reloadData(): void {
this.isLoading = true
this.clearSelection()
this.service
.listFiltered(
this.page,
null,
this.sortField,
this.sortReverse,
this._nameFilter,
true,
{ last_correspondence: true }
)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((c) => {
this.data = c.results
this.collectionSize = c.count
this.isLoading = false
})
super.reloadData({ last_correspondence: true })
}
getDeleteMessage(object: Correspondent) {

View File

@ -13,16 +13,23 @@
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="row reveal">
<div class="col" i18n>Name</div>
<div class="col" i18n>Data Type</div>
<div class="col" i18n>Actions</div>
</div>
</li>
@if (loading) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</li>
}
@for (field of fields; track field) {
<li class="list-group-item">
<div class="row">
<div class="row" [class.reveal]="reveal">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editField(field)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.CustomField)">{{field.name}}</button></div>
<div class="col d-flex align-items-center">{{getDataType(field)}}</div>
<div class="col">
@ -59,7 +66,7 @@
</div>
</li>
}
@if (fields.length === 0) {
@if (!loading && fields.length === 0) {
<li class="list-group-item" i18n>No fields defined.</li>
}
</ul>

View File

@ -2,3 +2,12 @@
.d-block.d-sm-none .dropdown-toggle::after {
display: none;
}
.list-group-item .row {
opacity: 0;
transition: opacity .2s;
}
.list-group-item .reveal {
opacity: 1;
}

View File

@ -95,6 +95,8 @@ describe('CustomFieldsComponent', () => {
fixture = TestBed.createComponent(CustomFieldsComponent)
component = fixture.componentInstance
fixture.detectChanges()
jest.useFakeTimers()
jest.advanceTimersByTime(100)
})
it('should support create, show notification on error / success', () => {
@ -119,6 +121,7 @@ describe('CustomFieldsComponent', () => {
editDialog.succeeded.emit(fields[0])
expect(toastInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled()
jest.advanceTimersByTime(100)
})
it('should support edit, show notification on error / success', () => {

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, takeUntil } from 'rxjs'
import { delay, Subject, takeUntil, tap } from 'rxjs'
import { DATA_TYPE_LABELS, CustomField } from 'src/app/data/custom-field'
import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
@ -28,6 +28,9 @@ export class CustomFieldsComponent
{
public fields: CustomField[] = []
public loading: boolean = true
public reveal: boolean = false
private unsubscribeNotifier: Subject<any> = new Subject()
constructor(
private customFieldsService: CustomFieldsService,
@ -47,9 +50,16 @@ export class CustomFieldsComponent
reload() {
this.customFieldsService
.listAll()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((r) => {
this.fields = r.results
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((r) => {
this.fields = r.results
}),
delay(100)
)
.subscribe(() => {
this.reveal = true
this.loading = false
})
}

View File

@ -26,7 +26,7 @@
</h4>
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="row reveal">
<div class="col" i18n>Name</div>
<div class="col" i18n>Server</div>
<div class="col d-none d-sm-block" i18n>Username</div>
@ -34,9 +34,16 @@
</div>
</li>
@if (loadingAccounts) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</li>
}
@for (account of mailAccounts; track account) {
<li class="list-group-item">
<div class="row">
<div class="row" [class.reveal]="revealAccounts">
<div class="col d-flex align-items-center">
<button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">
{{account.name}}@switch (account.account_type) {
@ -76,7 +83,7 @@
</div>
</li>
}
@if (mailAccounts.length === 0) {
@if (!loadingAccounts && mailAccounts.length === 0) {
<li class="list-group-item" i18n>No mail accounts defined.</li>
}
</ul>
@ -92,7 +99,7 @@
</h4>
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="row reveal">
<div class="col" i18n>Name</div>
<div class="col d-none d-sm-block" i18n>Sort Order</div>
<div class="col" i18n>Account</div>
@ -101,9 +108,16 @@
</div>
</li>
@if (loadingRules) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</li>
}
@for (rule of mailRules; track rule) {
<li class="list-group-item">
<div class="row">
<div class="row" [class.reveal]="revealRules">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
<div class="col d-flex align-items-center d-none d-sm-flex">{{rule.order}}</div>
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
@ -151,7 +165,7 @@
</div>
</li>
}
@if (mailRules.length === 0) {
@if (!loadingRules && mailRules.length === 0) {
<li class="list-group-item" i18n>No mail rules defined.</li>
}
</ul>

View File

@ -2,3 +2,12 @@
.d-block.d-sm-none .dropdown-toggle::after {
display: none;
}
.list-group-item .row {
opacity: 0;
transition: opacity .2s;
}
.list-group-item .reveal {
opacity: 1;
}

View File

@ -129,6 +129,8 @@ describe('MailComponent', () => {
fixture = TestBed.createComponent(MailComponent)
component = fixture.componentInstance
fixture.detectChanges()
jest.useFakeTimers()
jest.advanceTimersByTime(100)
})
function completeSetup(excludeService = null) {
@ -386,6 +388,7 @@ describe('MailComponent', () => {
component.oAuthAccountId = 3
const editSpy = jest.spyOn(component, 'editMailAccount')
component.ngOnInit()
jest.advanceTimersByTime(200)
expect(editSpy).toHaveBeenCalled()
})
})

View File

@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, first, takeUntil } from 'rxjs'
import { Subject, delay, first, takeUntil, tap } from 'rxjs'
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import { MailAccount, MailAccountType } from 'src/app/data/mail-account'
import { MailRule } from 'src/app/data/mail-rule'
@ -47,6 +47,11 @@ export class MailComponent
return this.settingsService.get(SETTINGS_KEYS.OUTLOOK_OAUTH_URL)
}
public loadingRules: boolean = true
public revealRules: boolean = false
public loadingAccounts: boolean = true
public revealAccounts: boolean = false
constructor(
public mailAccountService: MailAccountService,
public mailRuleService: MailRuleService,
@ -62,9 +67,10 @@ export class MailComponent
ngOnInit(): void {
this.mailAccountService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (r) => {
.pipe(
first(),
takeUntil(this.unsubscribeNotifier),
tap((r) => {
this.mailAccounts = r.results
if (this.oAuthAccountId) {
this.editMailAccount(
@ -73,6 +79,13 @@ export class MailComponent
)
)
}
}),
delay(100)
)
.subscribe({
next: () => {
this.loadingAccounts = false
this.revealAccounts = true
},
error: (e) => {
this.toastService.showError(
@ -84,10 +97,18 @@ export class MailComponent
this.mailRuleService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.pipe(
first(),
takeUntil(this.unsubscribeNotifier),
tap((r) => {
this.mailRules = r.results
}),
delay(100)
)
.subscribe({
next: (r) => {
this.mailRules = r.results
this.loadingRules = false
this.revealRules = true
},
error: (e) => {
this.toastService.showError($localize`Error retrieving mail rules`, e)

View File

@ -53,7 +53,7 @@
</tr>
}
@for (object of data; track object) {
<tr (click)="toggleSelected(object); $event.stopPropagation();">
<tr (click)="toggleSelected(object); $event.stopPropagation();" class="data-row" [class.reveal]="reveal">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">

View File

@ -10,3 +10,12 @@ tbody tr:last-child td {
.form-check {
min-height: 0;
}
.data-row {
opacity: 0;
transition: opacity .2s;
}
.reveal {
opacity: 1;
}

View File

@ -150,6 +150,7 @@ describe('ManagementListComponent', () => {
fixture.detectChanges()
expect(component.nameFilter).toBeNull()
expect(component.data).toEqual(tags)
tick(100) // load
}))
it('should support create, show notification on error / success', () => {

View File

@ -7,7 +7,13 @@ import {
} from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'
import {
debounceTime,
delay,
distinctUntilChanged,
takeUntil,
tap,
} from 'rxjs/operators'
import {
MatchingModel,
MATCHING_ALGORITHMS,
@ -89,6 +95,8 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
public selectedObjects: Set<number> = new Set()
public togggleAll: boolean = false
public reveal: boolean = false
ngOnInit(): void {
this.reloadData()
@ -132,7 +140,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
this.reloadData()
}
reloadData() {
reloadData(extraParams: { [key: string]: any } = null) {
this.isLoading = true
this.clearSelection()
this.service
@ -142,12 +150,19 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
this.sortField,
this.sortReverse,
this._nameFilter,
true
true,
extraParams
)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((c) => {
this.data = c.results
this.collectionSize = c.count
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((c) => {
this.data = c.results
this.collectionSize = c.count
}),
delay(100)
)
.subscribe(() => {
this.reveal = true
this.isLoading = false
})
}

View File

@ -13,7 +13,7 @@
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="row reveal">
<div class="col" i18n>Name</div>
<div class="col d-none d-sm-flex" i18n>Sort order</div>
<div class="col" i18n>Status</div>
@ -22,9 +22,16 @@
</div>
</li>
@if (loading) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</li>
}
@for (workflow of workflows; track workflow.id) {
<li class="list-group-item">
<div class="row">
<div class="row" [class.reveal]="reveal">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editWorkflow(workflow)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Workflow)">{{workflow.name}}</button></div>
<div class="col d-flex align-items-center d-none d-sm-flex"><code>{{workflow.order}}</code></div>
<div class="col d-flex align-items-center">
@ -69,7 +76,7 @@
</div>
</li>
}
@if (workflows.length === 0) {
<li class="list-group-item" i18n>No workflows defined.</li>
@if (!loading && workflows.length === 0) {
<li class="list-group-item" [class.reveal]="reveal" i18n>No workflows defined.</li>
}
</ul>

View File

@ -2,3 +2,12 @@
.d-block.d-sm-none .dropdown-toggle::after {
display: none;
}
.list-group-item .row {
opacity: 0;
transition: opacity .2s;
}
.list-group-item .reveal {
opacity: 1;
}

View File

@ -119,10 +119,11 @@ describe('WorkflowsComponent', () => {
)
modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService)
jest.useFakeTimers()
fixture = TestBed.createComponent(WorkflowsComponent)
component = fixture.componentInstance
fixture.detectChanges()
jest.advanceTimersByTime(100)
})
it('should support create, show notification on error / success', () => {

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'
import { WorkflowService } from 'src/app/services/rest/workflow.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { Subject, takeUntil } from 'rxjs'
import { delay, Subject, takeUntil, tap } from 'rxjs'
import { Workflow } from 'src/app/data/workflow'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ToastService } from 'src/app/services/toast.service'
@ -26,6 +26,9 @@ export class WorkflowsComponent
private unsubscribeNotifier: Subject<any> = new Subject()
public loading: boolean = false
public reveal: boolean = false
constructor(
private workflowService: WorkflowService,
public permissionsService: PermissionsService,
@ -40,11 +43,17 @@ export class WorkflowsComponent
}
reload() {
this.loading = true
this.workflowService
.listAll()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((r) => {
this.workflows = r.results
.pipe(
takeUntil(this.unsubscribeNotifier),
tap((r) => (this.workflows = r.results)),
delay(100)
)
.subscribe(() => {
this.reveal = true
this.loading = false
})
}