wip
This commit is contained in:
+81
-6
@@ -1,7 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, from, of, map, switchMap } from 'rxjs';
|
||||
import { buildProofPdf } from '../../services/proof-pdf.builder';
|
||||
import { GridModule } from '@progress/kendo-angular-grid';
|
||||
import { InputsModule } from '@progress/kendo-angular-inputs';
|
||||
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||
@@ -56,6 +57,10 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
showQuickAdd = false;
|
||||
submitting = false;
|
||||
|
||||
// Paper-proof attachments staged client-side; merged into one PDF on submit.
|
||||
pendingProofFiles: File[] = [];
|
||||
proofBusy = false;
|
||||
|
||||
sessions: OfferingSessionListItemDto[] = [];
|
||||
editingSessionId: number | null = null;
|
||||
|
||||
@@ -99,6 +104,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
this.viewSession = null;
|
||||
this.buffer = [];
|
||||
this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
||||
this.pendingProofFiles = [];
|
||||
this.resetEntry();
|
||||
this.mode = 'workspace';
|
||||
}
|
||||
@@ -167,6 +173,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
this.cashTotal = dto.cashTotal;
|
||||
this.checkTotal = dto.checkTotal;
|
||||
this.notes = dto.notes;
|
||||
this.pendingProofFiles = [];
|
||||
this.buffer = dto.givings.map(g => ({
|
||||
memberId: g.memberId, givingCategoryId: g.givingCategoryId, amount: g.amount,
|
||||
paymentMethod: g.paymentMethod, checkNumber: g.checkNumber,
|
||||
@@ -254,13 +261,21 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
notes: l.notes,
|
||||
})),
|
||||
};
|
||||
const obs: Observable<unknown> = this.editingSessionId != null
|
||||
? this.api.replace(this.editingSessionId, req)
|
||||
: this.api.create(req);
|
||||
obs.subscribe({
|
||||
const isEdit = this.editingSessionId != null;
|
||||
// Save the session first, then resolve to its id so the proof PDF can be attached.
|
||||
const savedId$: Observable<number> = isEdit
|
||||
? this.api.replace(this.editingSessionId!, req).pipe(map(() => this.editingSessionId!))
|
||||
: this.api.create(req).pipe(map(r => r.id));
|
||||
|
||||
savedId$.pipe(
|
||||
switchMap(id => this.pendingProofFiles.length === 0
|
||||
? of(void 0)
|
||||
: from(buildProofPdf(this.pendingProofFiles)).pipe(
|
||||
switchMap(({ blob }) => this.api.uploadProof(id, blob)))),
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.submitting = false;
|
||||
alert(this.editingSessionId != null ? 'Offering session updated.' : 'Offering session submitted.');
|
||||
alert(isEdit ? 'Offering session updated.' : 'Offering session submitted.');
|
||||
this.resetSession();
|
||||
this.mode = 'landing';
|
||||
this.loadSessions();
|
||||
@@ -272,6 +287,65 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Paper proof ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Stage selected attachment files (appends, so multiple picks accumulate). */
|
||||
onProofFilesSelected(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const files = Array.from(input.files ?? []);
|
||||
if (files.length) this.pendingProofFiles = [...this.pendingProofFiles, ...files];
|
||||
input.value = ''; // allow re-selecting the same file
|
||||
}
|
||||
|
||||
removeProofFile(i: number): void {
|
||||
this.pendingProofFiles = this.pendingProofFiles.filter((_, idx) => idx !== i);
|
||||
}
|
||||
|
||||
/** Open the stored proof PDF in a new tab (authenticated blob fetch). */
|
||||
openProof(id: number): void {
|
||||
this.api.downloadProof(id).subscribe(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, '_blank');
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
});
|
||||
}
|
||||
|
||||
/** Replace the stored proof from the read-only view (rebuild + upload + refresh). */
|
||||
replaceProof(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const files = Array.from(input.files ?? []);
|
||||
input.value = '';
|
||||
if (!files.length || !this.viewSession) return;
|
||||
const id = this.viewSession.id;
|
||||
this.proofBusy = true;
|
||||
from(buildProofPdf(files)).pipe(
|
||||
switchMap(({ blob }) => this.api.uploadProof(id, blob)),
|
||||
switchMap(() => this.api.getById(id)),
|
||||
).subscribe({
|
||||
next: dto => { this.viewSession = dto; this.proofBusy = false; },
|
||||
error: (err: { error?: { message?: string } }) => {
|
||||
this.proofBusy = false;
|
||||
alert(err?.error?.message ?? 'Proof upload failed.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeProof(): void {
|
||||
if (!this.viewSession) return;
|
||||
if (!confirm('Remove the paper proof for this session? / 移除此 session 的紙本證明?')) return;
|
||||
const id = this.viewSession.id;
|
||||
this.proofBusy = true;
|
||||
this.api.deleteProof(id).pipe(
|
||||
switchMap(() => this.api.getById(id)),
|
||||
).subscribe({
|
||||
next: dto => { this.viewSession = dto; this.proofBusy = false; },
|
||||
error: (err: { error?: { message?: string } }) => {
|
||||
this.proofBusy = false;
|
||||
alert(err?.error?.message ?? 'Remove failed.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Clear the whole working session back to a fresh state (today, empty buffer). */
|
||||
private resetSession(): void {
|
||||
this.editingSessionId = null;
|
||||
@@ -279,6 +353,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
||||
this.viewSession = null;
|
||||
this.buffer = [];
|
||||
this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
||||
this.pendingProofFiles = [];
|
||||
this.sessionDate = new Date();
|
||||
this.resetEntry();
|
||||
this.checkDate();
|
||||
|
||||
Reference in New Issue
Block a user