add quick add entry.
This commit is contained in:
+76
-5
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Observable, from, of, map, switchMap } from 'rxjs';
|
||||
import { Observable, Subject, from, of, map, switchMap, takeUntil } from 'rxjs';
|
||||
import { buildProofPdf } from '../../services/proof-pdf.builder';
|
||||
import { GridModule } from '@progress/kendo-angular-grid';
|
||||
import { InputsModule } from '@progress/kendo-angular-inputs';
|
||||
@@ -10,13 +10,14 @@ import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
|
||||
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
|
||||
import { DialogsModule } from '@progress/kendo-angular-dialog';
|
||||
import { OfferingSessionApiService } from '../../services/offering-session-api.service';
|
||||
import { OfferingEntrySignalrService } from '../../services/offering-entry-signalr.service';
|
||||
import { GivingCategoryApiService } from '../../services/giving-category-api.service';
|
||||
import { MemberApiService } from '../../../members/services/member-api.service';
|
||||
import { MemberListItemDto, memberDisplayName } from '../../../members/models/member.model';
|
||||
import { MemberQuickAddDialogComponent } from '../../components/member-quick-add-dialog/member-quick-add-dialog.component';
|
||||
import {
|
||||
GivingCategoryDto, PaymentMethod, OfferingBufferLine, CreateOfferingSessionRequest,
|
||||
OfferingSessionListItemDto, OfferingSessionDto,
|
||||
OfferingSessionListItemDto, OfferingSessionDto, OfferingGivingLineDto,
|
||||
} from '../../models/giving.model';
|
||||
import { PAYMENT_METHOD_OPTIONS } from '../../../../shared/i18n/option-lists';
|
||||
|
||||
@@ -34,9 +35,14 @@ type PageMode = 'landing' | 'workspace' | 'view';
|
||||
templateUrl: './offering-session-page.component.html',
|
||||
styleUrls: ['./offering-session-page.component.scss'],
|
||||
})
|
||||
export class OfferingSessionPageComponent implements OnInit {
|
||||
export class OfferingSessionPageComponent implements OnInit, OnDestroy {
|
||||
mode: PageMode = 'landing';
|
||||
|
||||
// The session date currently joined for live mobile-entry updates (yyyy-MM-dd),
|
||||
// and a teardown signal for the SignalR subscription.
|
||||
private liveDate: string | null = null;
|
||||
private readonly destroy$ = new Subject<void>();
|
||||
|
||||
sessionDate: Date = new Date();
|
||||
dateConflict = false;
|
||||
categories: GivingCategoryDto[] = [];
|
||||
@@ -72,6 +78,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
private api: OfferingSessionApiService,
|
||||
private categoryApi: GivingCategoryApiService,
|
||||
private memberApi: MemberApiService,
|
||||
private signalr: OfferingEntrySignalrService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -81,6 +88,67 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
});
|
||||
this.checkDate();
|
||||
this.loadSessions();
|
||||
|
||||
// Live updates: when a volunteer adds a line on the mobile page for the date
|
||||
// we're currently viewing/editing, reflect it here without a manual refresh.
|
||||
this.signalr.lineAdded$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(evt => this.onMobileLineAdded(evt));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.leaveLive();
|
||||
this.signalr.stop();
|
||||
}
|
||||
|
||||
// ── Live mobile-entry sync ─────────────────────────────────────────────────
|
||||
|
||||
private onMobileLineAdded(evt: { sessionDate: string; sessionId: number; line: OfferingGivingLineDto }): void {
|
||||
if (evt.sessionDate !== this.liveDate) {
|
||||
return;
|
||||
}
|
||||
if (this.mode === 'view' && this.viewSession && this.viewSession.id === evt.sessionId) {
|
||||
// Re-fetch so the read-only Lines list (and totals/status) stays authoritative.
|
||||
this.api.getById(this.viewSession.id).subscribe(dto => this.viewSession = dto);
|
||||
} else if (this.mode === 'workspace' && this.editingSessionId === evt.sessionId) {
|
||||
// Append to the open editor's buffer so it includes lines added from phones.
|
||||
this.buffer = [...this.buffer, this.bufferLineFromDto(evt.line)];
|
||||
}
|
||||
}
|
||||
|
||||
private bufferLineFromDto(g: OfferingGivingLineDto): OfferingBufferLine {
|
||||
return {
|
||||
memberId: g.memberId, givingCategoryId: g.givingCategoryId, amount: g.amount,
|
||||
paymentMethod: g.paymentMethod, checkNumber: g.checkNumber,
|
||||
zelleReferenceCode: g.zelleReferenceCode, payPalTransactionId: g.payPalTransactionId,
|
||||
isAnonymous: g.isAnonymous, notes: g.notes,
|
||||
memberName: g.memberName, categoryName: g.categoryName,
|
||||
};
|
||||
}
|
||||
|
||||
/** Subscribe to live updates for one session date, leaving any previous one. */
|
||||
private joinLive(date: string): void {
|
||||
this.signalr.start()
|
||||
.then(() => {
|
||||
if (this.liveDate && this.liveDate !== date) {
|
||||
return this.signalr.leaveDate(this.liveDate).then(() => undefined);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.then(() => {
|
||||
this.liveDate = date;
|
||||
return this.signalr.joinDate(date);
|
||||
})
|
||||
.catch(() => { /* offline is fine — the page still works without live sync */ });
|
||||
}
|
||||
|
||||
private leaveLive(): void {
|
||||
if (this.liveDate) {
|
||||
this.signalr.leaveDate(this.liveDate).catch(() => undefined);
|
||||
this.liveDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
get systemTotal(): number { return this.buffer.reduce((s, l) => s + (l.amount || 0), 0); }
|
||||
@@ -106,6 +174,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
||||
this.pendingProofFiles = [];
|
||||
this.resetEntry();
|
||||
this.joinLive(this.toIso(this.sessionDate));
|
||||
this.mode = 'workspace';
|
||||
}
|
||||
|
||||
@@ -125,7 +194,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
/** Open a session read-only (from a Recent Sessions row or a resolved date). */
|
||||
openView(s: OfferingSessionListItemDto): void {
|
||||
this.api.getById(s.id).subscribe({
|
||||
next: dto => { this.viewSession = dto; this.mode = 'view'; },
|
||||
next: dto => { this.viewSession = dto; this.joinLive(dto.sessionDate); this.mode = 'view'; },
|
||||
error: (err: { error?: { message?: string } }) => alert(err?.error?.message ?? 'Load failed.'),
|
||||
});
|
||||
}
|
||||
@@ -161,6 +230,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
|
||||
/** Leave workspace/view and return to the date-first landing screen. */
|
||||
backToLanding(): void {
|
||||
this.leaveLive();
|
||||
this.resetSession();
|
||||
this.mode = 'landing';
|
||||
this.loadSessions();
|
||||
@@ -276,6 +346,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
next: () => {
|
||||
this.submitting = false;
|
||||
alert(isEdit ? 'Offering session updated.' : 'Offering session submitted.');
|
||||
this.leaveLive();
|
||||
this.resetSession();
|
||||
this.mode = 'landing';
|
||||
this.loadSessions();
|
||||
|
||||
Reference in New Issue
Block a user