Enhance offering session

This commit is contained in:
Chris Chen
2026-05-30 00:15:10 -07:00
parent 769597d769
commit caed5091f0
4 changed files with 576 additions and 184 deletions
@@ -7,6 +7,7 @@ import { InputsModule } from '@progress/kendo-angular-inputs';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
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 { GivingCategoryApiService } from '../../services/giving-category-api.service';
import { MemberApiService } from '../../../members/services/member-api.service';
@@ -20,17 +21,21 @@ import { PAYMENT_METHOD_OPTIONS } from '../../../../shared/i18n/option-lists';
interface MemberOption { id: number; displayName: string; }
type PageMode = 'landing' | 'workspace' | 'view';
@Component({
selector: 'app-offering-session-page',
standalone: true,
imports: [
CommonModule, FormsModule, GridModule, InputsModule, ButtonsModule,
DropDownsModule, DateInputsModule, MemberQuickAddDialogComponent,
DropDownsModule, DateInputsModule, DialogsModule, MemberQuickAddDialogComponent,
],
templateUrl: './offering-session-page.component.html',
styleUrls: ['./offering-session-page.component.scss'],
})
export class OfferingSessionPageComponent implements OnInit {
mode: PageMode = 'landing';
sessionDate: Date = new Date();
dateConflict = false;
categories: GivingCategoryDto[] = [];
@@ -54,6 +59,10 @@ export class OfferingSessionPageComponent implements OnInit {
sessions: OfferingSessionListItemDto[] = [];
editingSessionId: number | null = null;
// Read-only session shown in `view` mode, and the confirm dialog for reopening it.
viewSession: OfferingSessionDto | null = null;
confirmReopenOpen = false;
constructor(
private api: OfferingSessionApiService,
private categoryApi: GivingCategoryApiService,
@@ -80,23 +89,77 @@ export class OfferingSessionPageComponent implements OnInit {
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.'),
// ── Flow: landing → workspace / view ──────────────────────────────────────
/** Free date chosen on the landing screen — begin a brand-new session. */
startEntry(): void {
if (this.dateConflict) return;
this.editingSessionId = null;
this.editingIndex = null;
this.viewSession = null;
this.buffer = [];
this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
this.resetEntry();
this.mode = 'workspace';
}
/** Existing date chosen on the landing screen — resolve it to its session and view it. */
openViewByDate(): void {
const iso = this.toIso(this.sessionDate);
this.api.getPaged(1, 1, iso, iso).subscribe({
next: r => {
const item = r.items[0];
if (!item) { this.checkDate(); return; } // race: it vanished, refresh the flag
this.openView(item);
},
error: (err: { error?: { message?: string } }) => alert(err?.error?.message ?? 'Load failed.'),
});
}
// Already a Draft (e.g. a session reopened then left) — load it straight back in, no reopen.
continueEditDraft(s: OfferingSessionListItemDto): void {
if (s.status !== 'Draft') return;
/** 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.loadIntoBuffer(dto),
next: dto => { this.viewSession = dto; this.mode = 'view'; },
error: (err: { error?: { message?: string } }) => alert(err?.error?.message ?? 'Load failed.'),
});
}
/** Edit button on the read-only view. Draft edits directly; Submitted confirms a reopen first. */
editFromView(): void {
const dto = this.viewSession;
if (!dto) return;
if (dto.status === 'Draft') {
this.loadIntoBuffer(dto);
this.mode = 'workspace';
} else if (dto.status === 'Submitted') {
this.confirmReopenOpen = true;
}
}
/** Confirmed reopen of a Submitted session (status → Draft) then load it for editing. */
confirmReopen(): void {
const dto = this.viewSession;
if (!dto) { this.confirmReopenOpen = false; return; }
this.api.reopen(dto.id).subscribe({
next: () => this.api.getById(dto.id).subscribe(fresh => {
this.loadIntoBuffer(fresh);
this.confirmReopenOpen = false;
this.mode = 'workspace';
}),
error: (err: { error?: { message?: string } }) => {
this.confirmReopenOpen = false;
alert(err?.error?.message ?? 'Reopen failed.');
},
});
}
/** Leave workspace/view and return to the date-first landing screen. */
backToLanding(): void {
this.resetSession();
this.mode = 'landing';
this.loadSessions();
}
private loadIntoBuffer(dto: OfferingSessionDto): void {
this.editingSessionId = dto.id;
this.sessionDate = new Date(dto.sessionDate + 'T00:00:00');
@@ -114,16 +177,6 @@ export class OfferingSessionPageComponent implements OnInit {
this.resetEntry();
}
cancelEdit(): void {
this.editingSessionId = null;
this.editingIndex = null;
this.buffer = []; this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
this.sessionDate = new Date();
this.checkDate();
// The reopened session is now a server-side Draft — refresh so its "Continue editing" appears.
this.loadSessions();
}
onMemberFilter(term: string): void {
if (!term) { this.memberResults = []; return; }
this.memberApi.getPaged({ search: term, pageSize: 10 }).subscribe(r =>
@@ -208,11 +261,8 @@ export class OfferingSessionPageComponent implements OnInit {
next: () => {
this.submitting = false;
alert(this.editingSessionId != null ? 'Offering session updated.' : 'Offering session submitted.');
this.editingSessionId = null;
this.editingIndex = null;
this.buffer = []; this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
this.sessionDate = new Date();
this.checkDate();
this.resetSession();
this.mode = 'landing';
this.loadSessions();
},
error: (err: { error?: { message?: string } }) => {
@@ -222,6 +272,18 @@ export class OfferingSessionPageComponent implements OnInit {
});
}
/** Clear the whole working session back to a fresh state (today, empty buffer). */
private resetSession(): void {
this.editingSessionId = null;
this.editingIndex = null;
this.viewSession = null;
this.buffer = [];
this.cashTotal = 0; this.checkTotal = 0; this.notes = null;
this.sessionDate = new Date();
this.resetEntry();
this.checkDate();
}
private resetEntry(): void {
this.editingIndex = null;
this.selectedMemberId = null; this.selectedMemberName = null; this.memberResults = [];