Initial incident record

This commit is contained in:
Daniel Ponte 2024-12-31 00:18:29 -05:00
parent 086bb90064
commit b16b3810cc
8 changed files with 147 additions and 13 deletions

View file

@ -65,6 +65,14 @@ export const routes: Routes = [
), ),
data: { title: 'Incidents' }, data: { title: 'Incidents' },
}, },
{
path: 'incidents/:id',
loadComponent: () =>
import('./incidents/incident/incident.component').then(
(m) => m.IncidentComponent,
),
data: { title: 'View Incident' },
},
{ {
path: 'alerts', path: 'alerts',
loadComponent: () => loadComponent: () =>

View file

@ -1 +1,15 @@
<p>incident works!</p> @let inc = inc$ | async;
<mat-card class="incident" appearance="outlined">
<h1>{{ inc?.name }}</h1>
<div class="inc-heading">
<div class="field field-start field-label">Start</div>
<div class="field field-data field-start">
{{ inc?.startTime | fmtDate }}
</div>
<div class="field field-end field-label">End</div>
<div class="field field-data field-end">{{ inc?.endTime | fmtDate }}</div>
</div>
<div class="inc-description">
{{ inc?.description }}
</div>
</mat-card>

View file

@ -0,0 +1,32 @@
.incident {
margin: 50px 50px 50px 50px;
padding: 50px 50px 50px 50px;
display: flex;
flex-flow: column;
width: 50%;
margin-left: auto;
margin-right: auto;
}
.inc-heading,
.inc-description {
display: flex;
flex-flow: row wrap;
flex: 1 1;
}
.inc-heading,
.inc-description {
margin-bottom: 30px;
}
.field {
flex: 1 1;
}
.field-label {
font-weight: bolder;
}
.field-label::after {
content: ":";
}

View file

@ -1,9 +1,73 @@
import { Component } from '@angular/core'; import { Component, computed, inject, ViewChild } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime } from 'rxjs/operators';
import {
Talkgroup,
TalkgroupUpdate,
IconMap,
iconMapping,
} from '../../talkgroup';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { TalkgroupService } from '../../talkgroups/talkgroups.service';
import {
MatAutocomplete,
MatAutocompleteModule,
MatAutocompleteSelectedEvent,
MatAutocompleteActivatedEvent,
} from '@angular/material/autocomplete';
import { CommonModule, DatePipe } from '@angular/common';
import { BehaviorSubject, catchError, of, Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import {
ReactiveFormsModule,
FormGroup,
FormControl,
FormsModule,
} from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { IncidentsService } from '../incidents.service';
import { IncidentRecord } from '../../incidents';
import { MatCardModule } from '@angular/material/card';
import { FmtDatePipe } from '../incidents.component';
@Component({ @Component({
selector: 'app-incident', selector: 'app-incident',
imports: [], imports: [
CommonModule,
ReactiveFormsModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatCheckboxModule,
MatIconModule,
MatCardModule,
FmtDatePipe,
],
templateUrl: './incident.component.html', templateUrl: './incident.component.html',
styleUrl: './incident.component.scss', styleUrl: './incident.component.scss',
}) })
export class IncidentComponent {} export class IncidentComponent {
inc$!: Observable<IncidentRecord>;
subscriptions: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private incSvc: IncidentsService,
) {}
saveIncName(ev: Event) {}
ngOnInit() {
const incID = this.route.snapshot.paramMap.get('id')!;
this.inc$ = this.incSvc.getIncident(incID);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}

View file

@ -34,12 +34,17 @@ import { ToolbarContextService } from '../navigation/toolbar-context.service';
standalone: true, standalone: true,
pure: true, pure: true,
}) })
export class DatePipe implements PipeTransform { export class FmtDatePipe implements PipeTransform {
transform(ts: string, args?: any): string { transform(ts: string | Date | null | undefined, args?: any): string {
if (!ts) { if (!ts) {
return '\u2014'; return '\u2014';
} }
const timestamp = new Date(ts); let timestamp: Date;
if (ts instanceof Date) {
timestamp = ts;
} else {
timestamp = new Date(ts);
}
return ( return (
timestamp.getMonth() + timestamp.getMonth() +
1 + 1 +
@ -61,7 +66,7 @@ const reqPageSize = 200;
selector: 'app-incidents', selector: 'app-incidents',
imports: [ imports: [
MatIconModule, MatIconModule,
DatePipe, FmtDatePipe,
MatPaginatorModule, MatPaginatorModule,
MatTableModule, MatTableModule,
AsyncPipe, AsyncPipe,

View file

@ -47,4 +47,8 @@ export class IncidentsService {
updateIncident(id: string, inp: IncidentRecord): Observable<IncidentRecord> { updateIncident(id: string, inp: IncidentRecord): Observable<IncidentRecord> {
return this.http.patch<IncidentRecord>('/api/incident/' + id, inp); return this.http.patch<IncidentRecord>('/api/incident/' + id, inp);
} }
getIncident(id: string): Observable<IncidentRecord> {
return this.http.get<IncidentRecord>('/api/incident/' + id);
}
} }

View file

@ -17,7 +17,7 @@ import {
MatAutocompleteActivatedEvent, MatAutocompleteActivatedEvent,
} from '@angular/material/autocomplete'; } from '@angular/material/autocomplete';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { BehaviorSubject, catchError, of, Subscription } from 'rxjs'; import { catchError, of, Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators'; import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import {

View file

@ -26,6 +26,7 @@ export interface TalkgroupsPaginated {
providedIn: 'root', providedIn: 'root',
}) })
export class TalkgroupService { export class TalkgroupService {
private readonly _getTalkgroup = new Map<string, ReplaySubject<Talkgroup>>();
private tgs$: Observable<Talkgroup[]>; private tgs$: Observable<Talkgroup[]>;
private tags$!: Observable<string[]>; private tags$!: Observable<string[]>;
private fetchAll = new BehaviorSubject<'fetch'>('fetch'); private fetchAll = new BehaviorSubject<'fetch'>('fetch');
@ -45,13 +46,19 @@ export class TalkgroupService {
} }
getTalkgroups(): Observable<Talkgroup[]> { getTalkgroups(): Observable<Talkgroup[]> {
return this.http.get<Talkgroup[]>('/api/talkgroup/'); return this.http.get<Talkgroup[]>('/api/talkgroup/').pipe(shareReplay());
} }
getTalkgroup(sys: number, tg: number): Observable<Talkgroup> { getTalkgroup(sys: number, tg: number): Observable<Talkgroup> {
return this.tgs$.pipe( const key = this.tgKey(sys, tg);
switchMap((tgs) => tgs.filter(t => t.system_id === sys && t.tgid === tg)) if (!this._getTalkgroup.get(key)) {
); return this.tgs$.pipe(
switchMap((talkg) =>
talkg.filter((tgv) => tgv.tgid == tg && tgv.system_id == sys),
),
);
}
return this._getTalkgroup.get(key)!;
} }
putTalkgroup(tu: TalkgroupUpdate): Observable<Talkgroup> { putTalkgroup(tu: TalkgroupUpdate): Observable<Talkgroup> {