138 lines
5.9 KiB
HTML
138 lines
5.9 KiB
HTML
<div class="page">
|
|
<!-- 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 / check #" [(ngModel)]="filter.search"
|
|
(keydown.enter)="applyFilter()">
|
|
</kendo-textbox>
|
|
</label>
|
|
|
|
<label class="flex flex-col gap-1">
|
|
Ministry
|
|
<kendo-dropdownlist [data]="ministries" textField="label" valueField="id" [valuePrimitive]="true"
|
|
[(ngModel)]="filter.ministryId" [defaultItem]="{ id: null, label: 'All Ministries/全部事工' }">
|
|
</kendo-dropdownlist>
|
|
</label>
|
|
|
|
<label class="flex flex-col gap-1">
|
|
Status
|
|
<kendo-dropdownlist [data]="statuses" textField="label" valueField="value" [valuePrimitive]="true"
|
|
[(ngModel)]="filter.status" [defaultItem]="{ value: null, label: 'All Status/全部狀態' }">
|
|
</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]="{ data: rows, total: total }" [loading]="loading" [pageable]="true" [skip]="skip"
|
|
[pageSize]="pageSize" (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="ministryName" title="Ministry" [width]="280"></kendo-grid-column>
|
|
|
|
<kendo-grid-column title="Category" [width]="360">
|
|
<ng-template kendoGridCellTemplate let-dataItem>
|
|
{{ dataItem.categoryGroupName }} / {{ dataItem.subCategoryName }}
|
|
</ng-template>
|
|
</kendo-grid-column>
|
|
|
|
<kendo-grid-column field="description" title="Description"></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="Check #" [width]="90">
|
|
<ng-template kendoGridCellTemplate let-dataItem>
|
|
{{ dataItem.status === 'Paid' && dataItem.checkNumber ? dataItem.checkNumber : '—' }}
|
|
</ng-template>
|
|
</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]="160">
|
|
<ng-template kendoGridCellTemplate let-dataItem>
|
|
<button *ngIf="canEdit(dataItem)" kendoButton fillMode="flat" (click)="openEdit(dataItem)">Edit</button>
|
|
<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>
|
|
|
|
<!-- Edit dialog -->
|
|
<app-expense-form-dialog *ngIf="editRow" [mode]="editMode" [expense]="editRow"
|
|
[title]="editMode === 'vendor' ? 'Edit Vendor Payment' : 'Edit Reimbursement'"
|
|
(save)="onEditSave($event)" (cancel)="closeEdit()">
|
|
</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>
|
|
|
|
<!-- Transient save confirmation (sits above the open dialog during continuous entry) -->
|
|
<div *ngIf="toast" class="save-toast">{{ toast }}</div>
|
|
|
|
</div> |