Files
ROLAC/APP/src/app/features/meal-attendance/services/attendance-signalr.service.ts
T
2026-06-20 19:43:15 -07:00

69 lines
2.4 KiB
TypeScript

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import {
HubConnection, HubConnectionBuilder, HubConnectionState, LogLevel,
} from '@microsoft/signalr';
import { environment } from '../../../../environments/environment';
import { AttendanceCounts, AttendanceCategory } from '../models/attendance.model';
/**
* Thin wrapper around the AttendanceHub SignalR connection. Exposes the latest
* authoritative counts as an observable and a fire-and-forget increment() that
* sends a batched delta to the server. The server broadcasts the new totals
* back to every connected client.
*/
@Injectable({ providedIn: 'root' })
export class AttendanceSignalrService {
// Hub lives at the host root; environment.apiUrl is e.g. http://localhost:42019/api
private readonly hubUrl = environment.apiUrl.replace(/\/api\/?$/, '') + '/hubs/attendance';
private connection?: HubConnection;
private readonly counts$$ = new BehaviorSubject<AttendanceCounts | null>(null);
/** Latest authoritative counts pushed by the server (null until first message). */
get counts$(): Observable<AttendanceCounts | null> {
return this.counts$$.asObservable();
}
async start(): Promise<void> {
if (this.connection) {
return;
}
this.connection = new HubConnectionBuilder()
.withUrl(this.hubUrl)
.withAutomaticReconnect()
.configureLogging(LogLevel.Warning)
.build();
this.connection.on('ReceiveCounts', (counts: AttendanceCounts) => {
this.counts$$.next(counts);
});
await this.connection.start();
}
async stop(): Promise<void> {
if (this.connection) {
await this.connection.stop();
this.connection = undefined;
}
}
/** Send a batched delta (may be negative) for one age group. */
async increment(category: AttendanceCategory, delta: number): Promise<void> {
if (delta === 0 || this.connection?.state !== HubConnectionState.Connected) {
return;
}
await this.connection.invoke('Increment', category, delta);
}
/** Overwrite one age group with an absolute value (clamped at zero server-side). */
async setCount(category: AttendanceCategory, value: number): Promise<void> {
if (this.connection?.state !== HubConnectionState.Connected) {
throw new Error('Not connected to the attendance hub.');
}
await this.connection.invoke('SetCount', category, value);
}
}