diff --git a/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.html b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.html new file mode 100644 index 0000000..5231ce1 --- /dev/null +++ b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.html @@ -0,0 +1,41 @@ +
+ + + + + + + + + {{ dataItem.categoryGroupName }} / {{ dataItem.subCategoryName }} + + + + + + {{ dataItem.status }} + + + + + + + + + Receipt + + + + + + +
diff --git a/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.scss b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.scss new file mode 100644 index 0000000..cba4215 --- /dev/null +++ b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.scss @@ -0,0 +1,46 @@ +// Status badge pill styles +%badge-base { + display: inline-block; + padding: 2px 10px; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; + white-space: nowrap; +} + +.badge-draft { + @extend %badge-base; + background-color: #e5e7eb; + color: #374151; +} + +.badge-pending { + @extend %badge-base; + background-color: #fef3c7; + color: #92400e; +} + +.badge-approved { + @extend %badge-base; + background-color: #dbeafe; + color: #1e40af; +} + +.badge-paid { + @extend %badge-base; + background-color: #d1fae5; + color: #065f46; +} + +.badge-rejected { + @extend %badge-base; + background-color: #fee2e2; + color: #991b1b; +} + +.receipt-link { + font-size: 0.8rem; + margin-left: 4px; + color: #1d4ed8; + text-decoration: underline; +} diff --git a/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.ts b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.ts new file mode 100644 index 0000000..071ea41 --- /dev/null +++ b/APP/src/app/features/expense/pages/my-reimbursements-page/my-reimbursements-page.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { GridModule } from '@progress/kendo-angular-grid'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { ExpenseApiService } from '../../services/expense-api.service'; +import { ExpenseFormDialogComponent, ExpenseFormResult } from '../../components/expense-form-dialog/expense-form-dialog.component'; +import { ExpenseListItemDto } from '../../models/expense.model'; +import { switchMap, of } from 'rxjs'; + +@Component({ + selector: 'app-my-reimbursements-page', + standalone: true, + imports: [CommonModule, GridModule, ButtonsModule, ExpenseFormDialogComponent], + templateUrl: './my-reimbursements-page.component.html', + styleUrls: ['./my-reimbursements-page.component.scss'], +}) +export class MyReimbursementsPageComponent implements OnInit { + rows: ExpenseListItemDto[] = []; + loading = false; + dialogOpen = false; + + constructor(private api: ExpenseApiService) {} + + ngOnInit(): void { this.load(); } + + load(): void { + this.loading = true; + this.api.getMine().subscribe({ + next: r => { this.rows = r.items; this.loading = false; }, + error: () => { this.loading = false; }, + }); + } + + openNew(): void { this.dialogOpen = true; } + + onSave(result: ExpenseFormResult): void { + this.api.create(result.request).pipe( + switchMap(created => result.receipt + ? this.api.uploadReceipt(created.id, result.receipt).pipe(switchMap(() => of(created))) + : of(created)), + ).subscribe(() => { this.dialogOpen = false; this.load(); }); + } + + submit(row: ExpenseListItemDto): void { this.api.submit(row.id).subscribe(() => this.load()); } + remove(row: ExpenseListItemDto): void { this.api.delete(row.id).subscribe(() => this.load()); } + + canEdit(row: ExpenseListItemDto): boolean { return row.status === 'Draft'; } + statusClass(status: string): string { + return ({ Draft: 'badge-draft', PendingApproval: 'badge-pending', Approved: 'badge-approved', Paid: 'badge-paid', Rejected: 'badge-rejected' } as Record)[status] ?? ''; + } + receiptUrl(id: number): string { return this.api.receiptUrl(id); } +}