Shares #109
4 changed files with 298 additions and 6 deletions
|
@ -6,6 +6,26 @@ import { CallRecord } from '../calls';
|
||||||
import { Share, ShareType } from '../shares';
|
import { Share, ShareType } from '../shares';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
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({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
|
@ -28,6 +48,14 @@ export class ShareService {
|
||||||
return this.http.get<Share>(`/share/${id}`);
|
return this.http.get<Share>(`/share/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteShare(id: string): Observable<void> {
|
||||||
|
return this.http.delete<void>(`/api/share/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getShares(p: ShareListParams): Observable<Shares> {
|
||||||
|
return this.http.post<Shares>('/api/share/', p);
|
||||||
|
}
|
||||||
|
|
||||||
getSharedItem(s: Observable<Share>): Observable<ShareType> {
|
getSharedItem(s: Observable<Share>): Observable<ShareType> {
|
||||||
return s.pipe(
|
return s.pipe(
|
||||||
map((res) => {
|
map((res) => {
|
||||||
|
|
|
@ -1 +1,82 @@
|
||||||
<p>shares works!</p>
|
<div class="tabContainer" *ngIf="!isLoading; else spinner">
|
||||||
|
<table class="sharesTable" mat-table [dataSource]="sharesResult">
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox
|
||||||
|
(change)="$event ? masterToggle() : null"
|
||||||
|
[checked]="selection.hasValue() && isAllSelected()"
|
||||||
|
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
||||||
|
>
|
||||||
|
</mat-checkbox>
|
||||||
|
</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selection.toggle(row) : null"
|
||||||
|
[checked]="selection.isSelected(row)"
|
||||||
|
>
|
||||||
|
</mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="type">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let share">
|
||||||
|
@switch (share.entityType) {
|
||||||
|
@case ("incident") {
|
||||||
|
<mat-icon>newspaper</mat-icon>
|
||||||
|
}
|
||||||
|
@case ("call") {
|
||||||
|
<mat-icon>campaign</mat-icon>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="link">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Link</th>
|
||||||
|
<td mat-cell *matCellDef="let share">
|
||||||
|
<a [href]="'/s/' + share.id"><mat-icon>link</mat-icon></a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="date">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Date</th>
|
||||||
|
<td mat-cell *matCellDef="let share">
|
||||||
|
{{ share.entityDate | fmtDate }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="owner">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Owner</th>
|
||||||
|
<td mat-cell *matCellDef="let share">
|
||||||
|
{{ share.owner }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="delete">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Delete</th>
|
||||||
|
<td mat-cell *matCellDef="let share">
|
||||||
|
<a (click)="deleteShare(share.id)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
|
||||||
|
<tr mat-row *matRowDef="let myRowData; columns: columns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="pagFoot">
|
||||||
|
<mat-paginator
|
||||||
|
#paginator
|
||||||
|
class="paginator"
|
||||||
|
(page)="setPage($event)"
|
||||||
|
[length]="count"
|
||||||
|
showFirstLastButtons="true"
|
||||||
|
[pageSize]="curPage.pageSize"
|
||||||
|
[pageSizeOptions]="pageSizeOptions"
|
||||||
|
[pageIndex]="curPage.pageIndex"
|
||||||
|
aria-label="Select page"
|
||||||
|
>
|
||||||
|
</mat-paginator>
|
||||||
|
</div>
|
||||||
|
<ng-template #spinner>
|
||||||
|
<div class="spinner">
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
|
@ -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({
|
@Component({
|
||||||
selector: 'app-shares',
|
selector: 'app-shares',
|
||||||
imports: [],
|
imports: [
|
||||||
|
MatIconModule,
|
||||||
|
MatPaginatorModule,
|
||||||
|
MatTableModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule,
|
||||||
|
FmtDatePipe,
|
||||||
|
MatInputModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
CommonModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
],
|
||||||
templateUrl: './shares.component.html',
|
templateUrl: './shares.component.html',
|
||||||
styleUrl: './shares.component.scss',
|
styleUrl: './shares.component.scss',
|
||||||
})
|
})
|
||||||
export class SharesComponent {}
|
export class SharesComponent {
|
||||||
|
sharesResult = new BehaviorSubject(new Array<ShareRecord>(0));
|
||||||
|
selection = new SelectionModel<ShareRecord>(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 = <PageEvent>{ pageIndex: 0, pageSize: 0 };
|
||||||
|
currentSet!: ShareRecord[];
|
||||||
|
currentServerPage = 0; // page is never 0, forces load
|
||||||
|
isLoading = true;
|
||||||
|
subscriptions = new Subscription();
|
||||||
|
pageWindow = 0;
|
||||||
|
fetchIncidents = new BehaviorSubject<ShareListParams>(
|
||||||
|
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 <PageEvent>{
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: this.curPage.pageSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.subscriptions.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
let cpp = 25;
|
||||||
|
this.perPage = cpp;
|
||||||
|
|
||||||
|
this.setPage(<PageEvent>{
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"/api": {
|
"/api/": {
|
||||||
"target": "http://xenon:3050",
|
"target": "http://xenon:3050",
|
||||||
"secure": false
|
"secure": false
|
||||||
},
|
},
|
||||||
"/share": {
|
"/share/": {
|
||||||
"target": "http://xenon:3050",
|
"target": "http://xenon:3050",
|
||||||
"secure": false
|
"secure": false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue