feat(giving): giving categories management page

Add GivingCategoriesPageComponent — standalone Angular 18 component with Kendo Grid (list/deactivate) and Kendo Dialog (add/edit form) for managing giving types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-28 17:04:55 -07:00
parent 91247a7c69
commit 7260e5c115
3 changed files with 163 additions and 0 deletions
@@ -0,0 +1,58 @@
<div class="page">
<header class="page-header">
<h2>Giving Types / 奉獻類型</h2>
<div class="header-actions">
<label class="inactive-toggle">
<input type="checkbox" [(ngModel)]="includeInactive" (change)="load()" /> Show inactive
</label>
<button kendoButton themeColor="primary" (click)="openAdd()">+ Add</button>
</div>
</header>
<kendo-grid [data]="data" [loading]="isLoading">
<kendo-grid-column field="sortOrder" title="#" [width]="60"></kendo-grid-column>
<kendo-grid-column field="name_en" title="Name (EN)"></kendo-grid-column>
<kendo-grid-column field="name_zh" title="名稱 (中)"></kendo-grid-column>
<kendo-grid-column field="isActive" title="Active" [width]="90">
<ng-template kendoGridCellTemplate let-c>{{ c.isActive ? 'Yes' : 'No' }}</ng-template>
</kendo-grid-column>
<kendo-grid-column title="Actions" [width]="160">
<ng-template kendoGridCellTemplate let-c>
<button kendoButton fillMode="flat" (click)="openEdit(c)">Edit</button>
<button kendoButton fillMode="flat" *ngIf="c.isActive" (click)="deactivate(c)">Deactivate</button>
</ng-template>
</kendo-grid-column>
</kendo-grid>
<kendo-dialog *ngIf="showDialog" [title]="editing ? 'Edit Giving Type' : 'Add Giving Type'" (close)="showDialog=false" [width]="480">
<div class="form-grid">
<label>
Name (EN) *
<kendo-textbox [(ngModel)]="form.name_en"></kendo-textbox>
</label>
<label>
名稱 (中)
<kendo-textbox [(ngModel)]="form.name_zh"></kendo-textbox>
</label>
<label>
Description (EN)
<kendo-textbox [(ngModel)]="form.description_en"></kendo-textbox>
</label>
<label>
說明 (中)
<kendo-textbox [(ngModel)]="form.description_zh"></kendo-textbox>
</label>
<label>
Sort order
<kendo-numerictextbox [(ngModel)]="form.sortOrder" [format]="'n0'" [decimals]="0" [min]="0"></kendo-numerictextbox>
</label>
<label *ngIf="editing">
<input type="checkbox" [(ngModel)]="form.isActive" /> Active
</label>
</div>
<kendo-dialog-actions>
<button kendoButton (click)="showDialog=false">Cancel</button>
<button kendoButton themeColor="primary" [disabled]="!form.name_en" (click)="save()">Save</button>
</kendo-dialog-actions>
</kendo-dialog>
</div>
@@ -0,0 +1,31 @@
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.inactive-toggle {
display: flex;
align-items: center;
gap: 0.25rem;
cursor: pointer;
}
.form-grid {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.form-grid label {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
@@ -0,0 +1,74 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { GridModule } from '@progress/kendo-angular-grid';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { DialogsModule } from '@progress/kendo-angular-dialog';
import { GivingCategoryApiService } from '../../services/giving-category-api.service';
import {
GivingCategoryDto, CreateGivingCategoryRequest, UpdateGivingCategoryRequest,
} from '../../models/giving.model';
@Component({
selector: 'app-giving-categories-page',
standalone: true,
imports: [CommonModule, FormsModule, GridModule, InputsModule, ButtonsModule, DialogsModule],
templateUrl: './giving-categories-page.component.html',
styleUrls: ['./giving-categories-page.component.scss'],
})
export class GivingCategoriesPageComponent implements OnInit {
data: GivingCategoryDto[] = [];
isLoading = false;
includeInactive = false;
showDialog = false;
editing: GivingCategoryDto | null = null;
form: UpdateGivingCategoryRequest = this.blankForm();
constructor(private api: GivingCategoryApiService) {}
ngOnInit(): void { this.load(); }
load(): void {
this.isLoading = true;
this.api.getAll(this.includeInactive).subscribe({
next: rows => { this.data = rows; this.isLoading = false; },
error: () => { this.isLoading = false; },
});
}
openAdd(): void { this.editing = null; this.form = this.blankForm(); this.showDialog = true; }
openEdit(c: GivingCategoryDto): void {
this.editing = c;
this.form = {
name_en: c.name_en, name_zh: c.name_zh,
description_en: c.description_en, description_zh: c.description_zh,
isActive: c.isActive, sortOrder: c.sortOrder,
};
this.showDialog = true;
}
save(): void {
if (this.editing) {
this.api.update(this.editing.id, this.form).subscribe(() => { this.showDialog = false; this.load(); });
} else {
const create: CreateGivingCategoryRequest = {
name_en: this.form.name_en, name_zh: this.form.name_zh,
description_en: this.form.description_en, description_zh: this.form.description_zh,
sortOrder: this.form.sortOrder,
};
this.api.create(create).subscribe(() => { this.showDialog = false; this.load(); });
}
}
deactivate(c: GivingCategoryDto): void {
if (!confirm(`Deactivate "${c.name_en}"?`)) return;
this.api.deactivate(c.id).subscribe(() => this.load());
}
private blankForm(): UpdateGivingCategoryRequest {
return { name_en: '', name_zh: null, description_en: null, description_zh: null, isActive: true, sortOrder: 0 };
}
}