feat(giving): add reopen-and-edit flow + recent sessions list to offering page
This commit is contained in:
+25
-3
@@ -2,11 +2,16 @@
|
|||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<h2>Sunday Offering Entry / 主日奉獻錄入</h2>
|
<h2>Sunday Offering Entry / 主日奉獻錄入</h2>
|
||||||
<label>Date
|
<label>Date
|
||||||
<kendo-datepicker [(ngModel)]="sessionDate" (valueChange)="checkDate()"></kendo-datepicker>
|
<kendo-datepicker [(ngModel)]="sessionDate" (valueChange)="checkDate()" [disabled]="editingSessionId != null"></kendo-datepicker>
|
||||||
</label>
|
</label>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div *ngIf="dateConflict" class="warn">
|
<div *ngIf="editingSessionId != null" class="edit-banner">
|
||||||
|
Editing submitted session — make changes and click "Update Session".
|
||||||
|
<button kendoButton fillMode="flat" (click)="cancelEdit()">Cancel edit</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="dateConflict && editingSessionId == null" class="warn">
|
||||||
An offering session for this date already exists. Pick another date, or reopen the existing session to edit.
|
An offering session for this date already exists. Pick another date, or reopen the existing session to edit.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,7 +66,24 @@
|
|||||||
<label>Check counted<kendo-numerictextbox [(ngModel)]="checkTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox></label>
|
<label>Check counted<kendo-numerictextbox [(ngModel)]="checkTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox></label>
|
||||||
<div [class.ok]="difference === 0" [class.bad]="difference !== 0">Difference: {{ difference | currency }}</div>
|
<div [class.ok]="difference === 0" [class.bad]="difference !== 0">Difference: {{ difference | currency }}</div>
|
||||||
<button kendoButton themeColor="primary"
|
<button kendoButton themeColor="primary"
|
||||||
[disabled]="buffer.length === 0 || dateConflict || submitting" (click)="submit()">Submit</button>
|
[disabled]="buffer.length === 0 || (editingSessionId == null && dateConflict) || submitting"
|
||||||
|
(click)="submit()">{{ editingSessionId != null ? 'Update Session' : 'Submit' }}</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sessions-list">
|
||||||
|
<h3>Recent Sessions</h3>
|
||||||
|
<kendo-grid [data]="sessions">
|
||||||
|
<kendo-grid-column field="sessionDate" title="Date" [width]="120"></kendo-grid-column>
|
||||||
|
<kendo-grid-column field="status" title="Status" [width]="110"></kendo-grid-column>
|
||||||
|
<kendo-grid-column field="lineCount" title="Lines" [width]="80"></kendo-grid-column>
|
||||||
|
<kendo-grid-column field="systemTotal" title="System" [width]="110" format="c2"></kendo-grid-column>
|
||||||
|
<kendo-grid-column field="difference" title="Diff" [width]="100" format="c2"></kendo-grid-column>
|
||||||
|
<kendo-grid-column title="" [width]="140">
|
||||||
|
<ng-template kendoGridCellTemplate let-s>
|
||||||
|
<button kendoButton fillMode="flat" *ngIf="s.status === 'Submitted'" (click)="reopenAndEdit(s)">Reopen & Edit</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
</kendo-grid>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<app-member-quick-add-dialog *ngIf="showQuickAdd"
|
<app-member-quick-add-dialog *ngIf="showQuickAdd"
|
||||||
|
|||||||
+2
@@ -7,3 +7,5 @@
|
|||||||
.reconcile { display: flex; gap: 1rem; align-items: flex-end; margin-top: 1rem; }
|
.reconcile { display: flex; gap: 1rem; align-items: flex-end; margin-top: 1rem; }
|
||||||
.reconcile .ok { color: green; font-weight: 600; }
|
.reconcile .ok { color: green; font-weight: 600; }
|
||||||
.reconcile .bad { color: #c00; font-weight: 600; }
|
.reconcile .bad { color: #c00; font-weight: 600; }
|
||||||
|
.edit-banner { display: flex; align-items: center; gap: 1rem; background: #fff3cd; border-left: 4px solid #f0a500; padding: 0.5rem 1rem; border-radius: 4px; margin-bottom: 1rem; font-weight: 500; }
|
||||||
|
.sessions-list { margin-top: 2rem; }
|
||||||
|
|||||||
+51
-3
@@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { GridModule } from '@progress/kendo-angular-grid';
|
import { GridModule } from '@progress/kendo-angular-grid';
|
||||||
import { InputsModule } from '@progress/kendo-angular-inputs';
|
import { InputsModule } from '@progress/kendo-angular-inputs';
|
||||||
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||||
@@ -13,6 +14,7 @@ import { MemberListItemDto, memberDisplayName } from '../../../members/models/me
|
|||||||
import { MemberQuickAddDialogComponent } from '../../components/member-quick-add-dialog/member-quick-add-dialog.component';
|
import { MemberQuickAddDialogComponent } from '../../components/member-quick-add-dialog/member-quick-add-dialog.component';
|
||||||
import {
|
import {
|
||||||
GivingCategoryDto, PaymentMethod, OfferingBufferLine, CreateOfferingSessionRequest,
|
GivingCategoryDto, PaymentMethod, OfferingBufferLine, CreateOfferingSessionRequest,
|
||||||
|
OfferingSessionListItemDto, OfferingSessionDto,
|
||||||
} from '../../models/giving.model';
|
} from '../../models/giving.model';
|
||||||
|
|
||||||
interface MemberOption { id: number; displayName: string; }
|
interface MemberOption { id: number; displayName: string; }
|
||||||
@@ -48,6 +50,9 @@ export class OfferingSessionPageComponent implements OnInit {
|
|||||||
showQuickAdd = false;
|
showQuickAdd = false;
|
||||||
submitting = false;
|
submitting = false;
|
||||||
|
|
||||||
|
sessions: OfferingSessionListItemDto[] = [];
|
||||||
|
editingSessionId: number | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private api: OfferingSessionApiService,
|
private api: OfferingSessionApiService,
|
||||||
private categoryApi: GivingCategoryApiService,
|
private categoryApi: GivingCategoryApiService,
|
||||||
@@ -60,6 +65,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
|||||||
this.entry.givingCategoryId = c[0]?.id ?? 0;
|
this.entry.givingCategoryId = c[0]?.id ?? 0;
|
||||||
});
|
});
|
||||||
this.checkDate();
|
this.checkDate();
|
||||||
|
this.loadSessions();
|
||||||
}
|
}
|
||||||
|
|
||||||
get systemTotal(): number { return this.buffer.reduce((s, l) => s + (l.amount || 0), 0); }
|
get systemTotal(): number { return this.buffer.reduce((s, l) => s + (l.amount || 0), 0); }
|
||||||
@@ -69,6 +75,42 @@ export class OfferingSessionPageComponent implements OnInit {
|
|||||||
this.api.checkDate(this.toIso(this.sessionDate)).subscribe(r => this.dateConflict = r.exists);
|
this.api.checkDate(this.toIso(this.sessionDate)).subscribe(r => this.dateConflict = r.exists);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSessions(): void {
|
||||||
|
this.api.getPaged(1, 20).subscribe(r => this.sessions = r.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
reopenAndEdit(s: OfferingSessionListItemDto): void {
|
||||||
|
if (s.status !== 'Submitted') return;
|
||||||
|
this.api.reopen(s.id).subscribe({
|
||||||
|
next: () => this.api.getById(s.id).subscribe(dto => this.loadIntoBuffer(dto)),
|
||||||
|
error: (err: { error?: { message?: string } }) => alert(err?.error?.message ?? 'Reopen failed.'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadIntoBuffer(dto: OfferingSessionDto): void {
|
||||||
|
this.editingSessionId = dto.id;
|
||||||
|
this.sessionDate = new Date(dto.sessionDate + 'T00:00:00');
|
||||||
|
this.dateConflict = false;
|
||||||
|
this.cashTotal = dto.cashTotal;
|
||||||
|
this.checkTotal = dto.checkTotal;
|
||||||
|
this.notes = dto.notes;
|
||||||
|
this.buffer = dto.givings.map(g => ({
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
this.resetEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEdit(): void {
|
||||||
|
this.editingSessionId = null;
|
||||||
|
this.buffer = []; this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
||||||
|
this.sessionDate = new Date();
|
||||||
|
this.checkDate();
|
||||||
|
}
|
||||||
|
|
||||||
onMemberFilter(term: string): void {
|
onMemberFilter(term: string): void {
|
||||||
if (!term) { this.memberResults = []; return; }
|
if (!term) { this.memberResults = []; return; }
|
||||||
this.memberApi.getPaged({ search: term, pageSize: 10 }).subscribe(r =>
|
this.memberApi.getPaged({ search: term, pageSize: 10 }).subscribe(r =>
|
||||||
@@ -123,7 +165,7 @@ export class OfferingSessionPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit(): void {
|
submit(): void {
|
||||||
if (this.buffer.length === 0 || this.dateConflict) return;
|
if (this.buffer.length === 0 || (this.editingSessionId == null && this.dateConflict)) return;
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
const req: CreateOfferingSessionRequest = {
|
const req: CreateOfferingSessionRequest = {
|
||||||
sessionDate: this.toIso(this.sessionDate),
|
sessionDate: this.toIso(this.sessionDate),
|
||||||
@@ -142,12 +184,18 @@ export class OfferingSessionPageComponent implements OnInit {
|
|||||||
notes: l.notes,
|
notes: l.notes,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
this.api.create(req).subscribe({
|
const obs: Observable<unknown> = this.editingSessionId != null
|
||||||
|
? this.api.replace(this.editingSessionId, req)
|
||||||
|
: this.api.create(req);
|
||||||
|
obs.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.submitting = false;
|
this.submitting = false;
|
||||||
alert('Offering session submitted.');
|
alert(this.editingSessionId != null ? 'Offering session updated.' : 'Offering session submitted.');
|
||||||
|
this.editingSessionId = null;
|
||||||
this.buffer = []; this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
this.buffer = []; this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
|
||||||
|
this.sessionDate = new Date();
|
||||||
this.checkDate();
|
this.checkDate();
|
||||||
|
this.loadSessions();
|
||||||
},
|
},
|
||||||
error: (err: { error?: { message?: string } }) => {
|
error: (err: { error?: { message?: string } }) => {
|
||||||
this.submitting = false;
|
this.submitting = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user