From 48903bb4a21ca181a5707e2062ea268e320c8570 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Fri, 14 Feb 2025 00:18:49 -0500 Subject: [PATCH] shareUI --- .../stillbox/src/app/share/share.service.ts | 28 +++ .../src/app/shares/shares.component.html | 83 +++++++- .../src/app/shares/shares.component.ts | 189 +++++++++++++++++- client/stillbox/src/proxy.conf.json | 4 +- 4 files changed, 298 insertions(+), 6 deletions(-) diff --git a/client/stillbox/src/app/share/share.service.ts b/client/stillbox/src/app/share/share.service.ts index 75d8a1f..4e43b8b 100644 --- a/client/stillbox/src/app/share/share.service.ts +++ b/client/stillbox/src/app/share/share.service.ts @@ -6,6 +6,26 @@ import { CallRecord } from '../calls'; import { Share, ShareType } from '../shares'; import { ActivatedRoute, Router } from '@angular/router'; +export interface ShareRecord { + id: string; + entityType: string; + entityDate: Date; + owner: string; + entityID: string; + expiration: Date | null; +} + +export interface ShareListParams { + page: number | null; + perPage: number | null; + dir: string | null; +} + +export interface Shares { + shares: ShareRecord[]; + totalCount: number; +} + @Injectable({ providedIn: 'root', }) @@ -28,6 +48,14 @@ export class ShareService { return this.http.get(`/share/${id}`); } + deleteShare(id: string): Observable { + return this.http.delete(`/api/share/${id}`); + } + + getShares(p: ShareListParams): Observable { + return this.http.post('/api/share/', p); + } + getSharedItem(s: Observable): Observable { return s.pipe( map((res) => { diff --git a/client/stillbox/src/app/shares/shares.component.html b/client/stillbox/src/app/shares/shares.component.html index 9f6f00b..d1e5fc7 100644 --- a/client/stillbox/src/app/shares/shares.component.html +++ b/client/stillbox/src/app/shares/shares.component.html @@ -1 +1,82 @@ -

shares works!

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + @switch (share.entityType) { + @case ("incident") { + newspaper + } + @case ("call") { + campaign + } + } + Link + link + Date + {{ share.entityDate | fmtDate }} + Owner + {{ share.owner }} + Delete + + delete + +
+
+
+ + +
+ +
+ +
+
diff --git a/client/stillbox/src/app/shares/shares.component.ts b/client/stillbox/src/app/shares/shares.component.ts index 8bbcdea..c95a668 100644 --- a/client/stillbox/src/app/shares/shares.component.ts +++ b/client/stillbox/src/app/shares/shares.component.ts @@ -1,9 +1,192 @@ -import { Component } from '@angular/core'; +import { Component, Pipe, PipeTransform, ViewChild } from '@angular/core'; +import { CommonModule, AsyncPipe } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTableModule } from '@angular/material/table'; +import { + MatPaginator, + MatPaginatorModule, + PageEvent, +} from '@angular/material/paginator'; +import { PrefsService } from '../prefs/prefs.service'; +import { MatIconModule } from '@angular/material/icon'; +import { SelectionModel } from '@angular/cdk/collections'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +import { TalkgroupService } from '../talkgroups/talkgroups.service'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { debounceTime } from 'rxjs/operators'; +import { ToolbarContextService } from '../navigation/toolbar-context.service'; +import { + ShareListParams, + ShareRecord, + ShareService, +} from '../share/share.service'; +import { FmtDatePipe } from '../incidents/incidents.component'; + +const reqPageSize = 200; @Component({ selector: 'app-shares', - imports: [], + imports: [ + MatIconModule, + MatPaginatorModule, + MatTableModule, + MatFormFieldModule, + ReactiveFormsModule, + FormsModule, + FmtDatePipe, + MatInputModule, + MatCheckboxModule, + CommonModule, + MatProgressSpinnerModule, + ], templateUrl: './shares.component.html', styleUrl: './shares.component.scss', }) -export class SharesComponent {} +export class SharesComponent { + sharesResult = new BehaviorSubject(new Array(0)); + selection = new SelectionModel(true, []); + + @ViewChild('paginator') paginator!: MatPaginator; + count = 0; + curLen = 0; + page = 0; + perPage = 25; + pageSizeOptions = [25, 50, 75, 100, 200]; + columns = ['select', 'type', 'link', 'date', 'owner', 'delete']; + curPage = { pageIndex: 0, pageSize: 0 }; + currentSet!: ShareRecord[]; + currentServerPage = 0; // page is never 0, forces load + isLoading = true; + subscriptions = new Subscription(); + pageWindow = 0; + fetchIncidents = new BehaviorSubject( + this.buildParams(this.curPage, this.curPage.pageIndex), + ); + + constructor(private sharesSvc: ShareService) {} + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.curLen; + return numSelected === numRows; + } + + buildParams(p: PageEvent, serverPage: number): ShareListParams { + const par: ShareListParams = { + page: serverPage, + perPage: reqPageSize, + dir: 'asc', + }; + + return par; + } + + masterToggle() { + this.isAllSelected() + ? this.selection.clear() + : this.sharesResult.value.forEach((row) => this.selection.select(row)); + } + + setPage(p: PageEvent, force?: boolean) { + this.selection.clear(); + this.curPage = p; + if (p && p!.pageSize != this.perPage) { + this.perPage = p!.pageSize; + } + this.getShares(p, force); + } + + refresh() { + this.selection.clear(); + this.getShares(this.curPage, true); + } + + getShares(p: PageEvent, force?: boolean) { + const pageStart = p.pageIndex * p.pageSize; + const serverPage = Math.floor(pageStart / reqPageSize) + 1; + this.pageWindow = pageStart % reqPageSize; + if (serverPage == this.currentServerPage && !force && this.currentSet) { + this.sharesResult.next( + this.sharesResult + ? this.currentSet.slice(this.pageWindow, this.pageWindow + p.pageSize) + : [], + ); + } else { + this.currentServerPage = serverPage; + this.fetchIncidents.next(this.buildParams(p, serverPage)); + } + } + + zeroPage(): PageEvent { + return { + pageIndex: 0, + pageSize: this.curPage.pageSize, + }; + } + + ngOnDestroy() { + this.subscriptions.unsubscribe(); + } + + ngOnInit() { + let cpp = 25; + this.perPage = cpp; + + this.setPage({ + pageIndex: 0, + pageSize: cpp, + }); + this.subscriptions.add( + this.fetchIncidents + .pipe( + switchMap((params) => { + return this.sharesSvc.getShares(params); + }), + ) + .subscribe((shares) => { + this.isLoading = false; + this.count = shares.totalCount; + this.currentSet = shares.shares; + this.sharesResult.next( + this.currentSet + ? this.currentSet.slice( + this.pageWindow, + this.pageWindow + this.perPage, + ) + : [], + ); + }), + ); + this.subscriptions.add( + this.sharesResult.subscribe((cr) => { + this.curLen = cr.length; + }), + ); + } + + deleteShare(shareID: string) { + if (confirm('Are you sure you want to delete this share?')) { + this.sharesSvc.deleteShare(shareID).subscribe({ + next: () => { + this.fetchIncidents.next( + this.buildParams(this.curPage, this.curPage.pageIndex), + ); + }, + error: (err) => { + alert(err); + }, + }); + } + } +} diff --git a/client/stillbox/src/proxy.conf.json b/client/stillbox/src/proxy.conf.json index 67915a8..0a8777d 100644 --- a/client/stillbox/src/proxy.conf.json +++ b/client/stillbox/src/proxy.conf.json @@ -1,9 +1,9 @@ { - "/api": { + "/api/": { "target": "http://xenon:3050", "secure": false }, - "/share": { + "/share/": { "target": "http://xenon:3050", "secure": false }