feat(expense): add finance expenses overview + review page

This commit is contained in:
Chris Chen
2026-05-29 18:53:39 -07:00
parent 4704d33b4a
commit aa77f2051a
3 changed files with 332 additions and 0 deletions
@@ -0,0 +1,149 @@
<div class="page">
<header class="page-header">
<h2>Expenses</h2>
</header>
<!-- Filter toolbar -->
<div class="flex flex-wrap gap-3 items-end mb-4">
<label class="flex flex-col gap-1">
Search
<kendo-textbox placeholder="Search description / vendor / member"
[(ngModel)]="filter.search"
(keydown.enter)="applyFilter()">
</kendo-textbox>
</label>
<label class="flex flex-col gap-1">
Ministry
<kendo-dropdownlist
[data]="ministries"
textField="name_en"
valueField="id"
[valuePrimitive]="true"
[(ngModel)]="filter.ministryId"
[defaultItem]="{ id: null, name_en: 'All Ministries' }">
</kendo-dropdownlist>
</label>
<label class="flex flex-col gap-1">
Status
<kendo-dropdownlist
[data]="statuses"
[(ngModel)]="filter.status"
[defaultItem]="null">
</kendo-dropdownlist>
</label>
<button kendoButton (click)="applyFilter()">Apply</button>
<div class="ml-auto flex gap-2">
<button kendoButton themeColor="primary" (click)="vendorDialogOpen = true">+ Vendor Payment</button>
<button kendoButton themeColor="primary" (click)="reimbDialogOpen = true">+ Reimbursement</button>
</div>
</div>
<!-- Main grid -->
<kendo-grid
[data]="rows"
[loading]="loading"
[pageable]="true"
[skip]="skip"
[pageSize]="pageSize"
[total]="total"
(pageChange)="onPageChange($event)">
<kendo-grid-column field="expenseDate" title="Date" [width]="110"></kendo-grid-column>
<kendo-grid-column field="type" title="Type" [width]="140"></kendo-grid-column>
<kendo-grid-column field="description" title="Description"></kendo-grid-column>
<kendo-grid-column field="ministryName" title="Ministry" [width]="140"></kendo-grid-column>
<kendo-grid-column title="Category" [width]="180">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.categoryGroupName }} / {{ dataItem.subCategoryName }}
</ng-template>
</kendo-grid-column>
<kendo-grid-column title="Payee" [width]="150">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.vendorName || dataItem.memberName || '—' }}
</ng-template>
</kendo-grid-column>
<kendo-grid-column field="amount" title="Amount" [width]="110" format="c2"></kendo-grid-column>
<kendo-grid-column title="Status" [width]="140">
<ng-template kendoGridCellTemplate let-dataItem>
<span [class]="statusClass(dataItem.status)">{{ dataItem.status }}</span>
</ng-template>
</kendo-grid-column>
<kendo-grid-column title="Actions" [width]="240">
<ng-template kendoGridCellTemplate let-dataItem>
<ng-container *ngIf="canApproveOrReject(dataItem)">
<button kendoButton themeColor="success" fillMode="flat" (click)="approve(dataItem)">Approve</button>
<button kendoButton themeColor="error" fillMode="flat" (click)="openReject(dataItem)">Reject</button>
</ng-container>
<button *ngIf="canPay(dataItem)" kendoButton themeColor="primary" fillMode="flat"
(click)="openPay(dataItem)">Pay</button>
<button *ngIf="dataItem.hasReceipt" kendoButton fillMode="flat"
(click)="openReceipt(dataItem.id)" class="receipt-link">Receipt</button>
</ng-template>
</kendo-grid-column>
</kendo-grid>
<!-- Vendor Payment dialog -->
<app-expense-form-dialog
*ngIf="vendorDialogOpen"
mode="vendor"
title="Vendor Payment"
(save)="onVendorSave($event)"
(cancel)="vendorDialogOpen = false">
</app-expense-form-dialog>
<!-- Reimbursement (on behalf) dialog -->
<app-expense-form-dialog
*ngIf="reimbDialogOpen"
mode="reimbursement"
[allowMemberPick]="true"
title="Reimbursement (on behalf)"
(save)="onReimbSave($event)"
(cancel)="reimbDialogOpen = false">
</app-expense-form-dialog>
<!-- Mark Paid dialog -->
<kendo-dialog *ngIf="payRow" title="Mark Paid" [width]="400" (close)="payRow = null">
<div class="grid grid-cols-1 gap-3 p-2">
<label class="flex flex-col gap-1">
Check #
<kendo-textbox [(ngModel)]="payCheckNumber" placeholder="Optional"></kendo-textbox>
</label>
<label class="flex flex-col gap-1">
Payment Date
<kendo-datepicker [(ngModel)]="payDate"></kendo-datepicker>
</label>
</div>
<kendo-dialog-actions>
<button kendoButton (click)="payRow = null">Cancel</button>
<button kendoButton themeColor="primary" (click)="confirmPay()">Confirm</button>
</kendo-dialog-actions>
</kendo-dialog>
<!-- Reject dialog -->
<kendo-dialog *ngIf="rejectRow" title="Reject Expense" [width]="400" (close)="rejectRow = null">
<div class="grid grid-cols-1 gap-3 p-2">
<label class="flex flex-col gap-1">
Review Notes
<kendo-textbox [(ngModel)]="rejectNotes" placeholder="Optional notes for submitter"></kendo-textbox>
</label>
</div>
<kendo-dialog-actions>
<button kendoButton (click)="rejectRow = null">Cancel</button>
<button kendoButton themeColor="error" (click)="confirmReject()">Reject</button>
</kendo-dialog-actions>
</kendo-dialog>
</div>