This commit is contained in:
Chris Chen
2026-05-28 22:29:13 -07:00
parent a2d394029a
commit 0639d1fe83
23 changed files with 896 additions and 158 deletions
@@ -1,10 +1,10 @@
<kendo-dialog title="Quick add member" (close)="cancelled.emit()" [width]="420">
<div style="display:flex;flex-direction:column;gap:0.75rem;">
<label>First name (EN) *<kendo-textbox [(ngModel)]="firstName_en"></kendo-textbox></label>
<label>Last name (EN) *<kendo-textbox [(ngModel)]="lastName_en"></kendo-textbox></label>
<label>名 (中)<kendo-textbox [(ngModel)]="firstName_zh"></kendo-textbox></label>
<label>姓 (中)<kendo-textbox [(ngModel)]="lastName_zh"></kendo-textbox></label>
<label>Cell phone<kendo-textbox [(ngModel)]="phoneCell"></kendo-textbox></label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<label class="flex flex-col gap-1">First name (EN) *<kendo-textbox [(ngModel)]="firstName_en"></kendo-textbox></label>
<label class="flex flex-col gap-1">Last name (EN) *<kendo-textbox [(ngModel)]="lastName_en"></kendo-textbox></label>
<label class="flex flex-col gap-1">名 (中)<kendo-textbox [(ngModel)]="firstName_zh"></kendo-textbox></label>
<label class="flex flex-col gap-1">姓 (中)<kendo-textbox [(ngModel)]="lastName_zh"></kendo-textbox></label>
<label class="flex flex-col gap-1 md:col-span-2">Cell phone<kendo-textbox [(ngModel)]="phoneCell"></kendo-textbox></label>
</div>
<kendo-dialog-actions>
<button kendoButton (click)="cancelled.emit()">Cancel</button>
@@ -25,28 +25,28 @@
</kendo-grid>
<kendo-dialog *ngIf="showDialog" [title]="editing ? 'Edit Giving Type' : 'Add Giving Type'" (close)="showDialog=false" [width]="480">
<div class="form-grid">
<label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<label class="flex flex-col gap-1">
Name (EN) *
<kendo-textbox [(ngModel)]="form.name_en"></kendo-textbox>
</label>
<label>
<label class="flex flex-col gap-1">
名稱 (中)
<kendo-textbox [(ngModel)]="form.name_zh"></kendo-textbox>
</label>
<label>
<label class="flex flex-col gap-1">
Description (EN)
<kendo-textbox [(ngModel)]="form.description_en"></kendo-textbox>
</label>
<label>
<label class="flex flex-col gap-1">
說明 (中)
<kendo-textbox [(ngModel)]="form.description_zh"></kendo-textbox>
</label>
<label>
<label class="flex flex-col gap-1">
Sort order
<kendo-numerictextbox [(ngModel)]="form.sortOrder" [format]="'n0'" [decimals]="0" [min]="0"></kendo-numerictextbox>
</label>
<label *ngIf="editing">
<label *ngIf="editing" class="flex items-center gap-2 md:col-span-2">
<input type="checkbox" [(ngModel)]="form.isActive" /> Active
</label>
</div>
@@ -17,15 +17,3 @@
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;
}
@@ -30,12 +30,12 @@
</kendo-grid>
<kendo-dialog *ngIf="showDialog" title="Add Giving" (close)="showDialog=false" [width]="520">
<div class="form-grid">
<label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<label class="flex items-center gap-2 md:col-span-2">
<input type="checkbox" [ngModel]="form.isAnonymous" (ngModelChange)="toggleAnonymous()" /> Anonymous
</label>
<label *ngIf="!form.isAnonymous">Giver
<label *ngIf="!form.isAnonymous" class="flex flex-col gap-1 md:col-span-2">Giver
<kendo-dropdownlist [data]="memberResults" textField="displayName" valueField="id"
[valuePrimitive]="true" [filterable]="true"
(filterChange)="onMemberFilter($event)"
@@ -44,34 +44,34 @@
placeholder="Search member by name"></kendo-dropdownlist>
</label>
<label>Type
<label class="flex flex-col gap-1">Type
<kendo-dropdownlist [data]="categories" textField="name_en" valueField="id"
[valuePrimitive]="true" [(ngModel)]="form.givingCategoryId"></kendo-dropdownlist>
</label>
<label>Payment method
<label class="flex flex-col gap-1">Payment method
<kendo-dropdownlist [data]="paymentMethods" [(ngModel)]="form.paymentMethod"></kendo-dropdownlist>
</label>
<label *ngIf="form.paymentMethod === 'Check'">Check #
<label *ngIf="form.paymentMethod === 'Check'" class="flex flex-col gap-1">Check #
<kendo-textbox [(ngModel)]="form.checkNumber"></kendo-textbox>
</label>
<label *ngIf="form.paymentMethod === 'Zelle'">Zelle ref
<label *ngIf="form.paymentMethod === 'Zelle'" class="flex flex-col gap-1">Zelle ref
<kendo-textbox [(ngModel)]="form.zelleReferenceCode"></kendo-textbox>
</label>
<label *ngIf="form.paymentMethod === 'PayPal'">PayPal txn
<label *ngIf="form.paymentMethod === 'PayPal'" class="flex flex-col gap-1">PayPal txn
<kendo-textbox [(ngModel)]="form.payPalTransactionId"></kendo-textbox>
</label>
<label>Amount
<label class="flex flex-col gap-1">Amount
<kendo-numerictextbox [(ngModel)]="form.amount" [min]="0" [format]="'c2'"></kendo-numerictextbox>
</label>
<label>Date
<label class="flex flex-col gap-1">Date
<kendo-datepicker [(ngModel)]="givingDateValue"></kendo-datepicker>
</label>
<label>Notes
<label class="flex flex-col gap-1 md:col-span-2">Notes
<kendo-textbox [(ngModel)]="form.notes"></kendo-textbox>
</label>
</div>
@@ -1,4 +1,2 @@
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.filters { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.form-grid { display: flex; flex-direction: column; gap: 0.75rem; }
.form-grid label { display: flex; flex-direction: column; gap: 0.25rem; }
@@ -120,9 +120,7 @@ export class GivingsPageComponent implements OnInit {
save(): void {
// Sync the datepicker Date back to the ISO string field.
this.form.givingDate = this.givingDateValue
? this.givingDateValue.toISOString().slice(0, 10)
: new Date().toISOString().slice(0, 10);
this.form.givingDate = this.toIso(this.givingDateValue ?? new Date());
if (this.editingId) {
this.api.update(this.editingId, this.form).subscribe(() => { this.showDialog = false; this.load(); });
@@ -149,9 +147,18 @@ export class GivingsPageComponent implements OnInit {
checkNumber: null,
zelleReferenceCode: null,
payPalTransactionId: null,
givingDate: new Date().toISOString().slice(0, 10),
givingDate: this.toIso(new Date()),
isAnonymous: false,
notes: null,
};
}
// Format using LOCAL date components — NOT toISOString(), which converts to UTC and
// rolls the date forward a day for behind-UTC users when the Date carries an evening time.
private toIso(d: Date): string {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
}
@@ -1,92 +1,142 @@
<div class="page">
<header class="page-header">
<h2>Sunday Offering Entry / 主日奉獻錄入</h2>
<label>Date
<kendo-datepicker [(ngModel)]="sessionDate" (valueChange)="checkDate()" [disabled]="editingSessionId != null"></kendo-datepicker>
</label>
</header>
<!-- Card A — Session header -->
<section class="card">
<header class="page-header">
<h2 style="margin:0">Sunday Offering Entry / 主日奉獻錄入</h2>
<label class="flex flex-col gap-1">Date
<kendo-datepicker [(ngModel)]="sessionDate" (valueChange)="checkDate()"
[disabled]="editingSessionId != null"></kendo-datepicker>
</label>
</header>
<div *ngIf="editingSessionId != null" class="edit-banner">
Editing submitted session — make changes and click "Update Session".
<button kendoButton fillMode="flat" (click)="cancelEdit()">Cancel edit</button>
</div>
<div *ngIf="editingSessionId != null" class="edit-banner">
<span class="badge badge--draft">Editing</span>
Editing session — make changes and click "Update Session".
<button kendoButton fillMode="flat" (click)="cancelEdit()">Cancel edit</button>
</div>
<div *ngIf="dateConflict && editingSessionId == null" class="warn">
An offering session for this date already exists. Pick another date, or reopen the existing session to edit.
</div>
<section class="entry-row">
<label *ngIf="!entry.isAnonymous">Giver
<kendo-dropdownlist [data]="memberResults" textField="displayName" valueField="id"
[valuePrimitive]="true" [filterable]="true"
(filterChange)="onMemberFilter($event)" [(ngModel)]="selectedMemberId"
(valueChange)="onMemberSelected($event)" placeholder="Search by name"></kendo-dropdownlist>
</label>
<span *ngIf="entry.isAnonymous" class="anon-chip">Anonymous</span>
<label>Type
<kendo-dropdownlist [data]="categories" textField="name_en" valueField="id"
[valuePrimitive]="true" [(ngModel)]="entry.givingCategoryId"></kendo-dropdownlist>
</label>
<label>Method
<kendo-dropdownlist [data]="paymentMethods" [(ngModel)]="entry.paymentMethod"></kendo-dropdownlist>
</label>
<label *ngIf="entry.paymentMethod === 'Check'">Check #<kendo-textbox [(ngModel)]="entry.checkNumber"></kendo-textbox></label>
<label>Amount
<kendo-numerictextbox [(ngModel)]="entry.amount" [min]="0" [format]="'c2'" (keydown.enter)="addLine()"></kendo-numerictextbox>
</label>
<label>Notes<kendo-textbox [(ngModel)]="entry.notes" (keydown.enter)="addLine()"></kendo-textbox></label>
<div class="entry-actions">
<button kendoButton (click)="markAnonymous()">Anonymous</button>
<button kendoButton (click)="showQuickAdd = true">+ Quick add member</button>
<button kendoButton themeColor="primary" (click)="addLine()">+ Add (Enter)</button>
<div *ngIf="dateConflict && editingSessionId == null" class="warn">
An offering session for this date already exists. Pick another date, or reopen the existing session to edit.
</div>
</section>
<kendo-grid [data]="buffer">
<kendo-grid-column title="Giver">
<ng-template kendoGridCellTemplate let-l>{{ l.isAnonymous ? '(Anonymous)' : l.memberName }}</ng-template>
</kendo-grid-column>
<kendo-grid-column field="categoryName" title="Type"></kendo-grid-column>
<kendo-grid-column field="paymentMethod" title="Method" [width]="90"></kendo-grid-column>
<kendo-grid-column field="checkNumber" title="Check #" [width]="90"></kendo-grid-column>
<kendo-grid-column field="amount" title="Amount" [width]="110" format="c2"></kendo-grid-column>
<kendo-grid-column title="" [width]="120">
<ng-template kendoGridCellTemplate let-l let-i="rowIndex">
<button kendoButton fillMode="flat" (click)="editLine(i)">Edit</button>
<button kendoButton fillMode="flat" (click)="removeLine(i)">×</button>
</ng-template>
</kendo-grid-column>
</kendo-grid>
<!-- Card B — Add giving -->
<section class="card">
<h3 class="section-title">Add Giving / 錄入奉獻</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-x-4 gap-y-3">
<label class="flex flex-col gap-1 md:col-span-2">Giver
<kendo-dropdownlist *ngIf="!entry.isAnonymous" [data]="memberResults" textField="displayName" valueField="id"
[valuePrimitive]="true" [filterable]="true" (filterChange)="onMemberFilter($event)"
[(ngModel)]="selectedMemberId" (valueChange)="onMemberSelected($event)"
placeholder="Search by name"></kendo-dropdownlist>
<span *ngIf="entry.isAnonymous" class="flex items-center gap-2">
<span class="anon-chip">Anonymous</span>
<button kendoButton fillMode="flat" size="small" (click)="clearAnonymous()">Clear</button>
</span>
</label>
<section class="reconcile">
<div>Lines: {{ buffer.length }} | System total: {{ systemTotal | currency }}</div>
<label>Cash counted<kendo-numerictextbox [(ngModel)]="cashTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox></label>
<label>Check counted<kendo-numerictextbox [(ngModel)]="checkTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox></label>
<div [class.ok]="difference === 0" [class.bad]="difference !== 0">Difference: {{ difference | currency }}</div>
<button kendoButton themeColor="primary"
[disabled]="buffer.length === 0 || (editingSessionId == null && dateConflict) || submitting"
(click)="submit()">{{ editingSessionId != null ? 'Update Session' : 'Submit' }}</button>
<label class="flex flex-col gap-1">Type
<kendo-dropdownlist [data]="categories" textField="name_en" valueField="id" [valuePrimitive]="true"
[(ngModel)]="entry.givingCategoryId"></kendo-dropdownlist>
</label>
<label class="flex flex-col gap-1">Method
<kendo-dropdownlist [data]="paymentMethods" [(ngModel)]="entry.paymentMethod"></kendo-dropdownlist>
</label>
<label *ngIf="entry.paymentMethod === 'Check'" class="flex flex-col gap-1">Check #
<kendo-textbox [(ngModel)]="entry.checkNumber"></kendo-textbox>
</label>
<label class="flex flex-col gap-1">Amount
<kendo-numerictextbox [(ngModel)]="entry.amount" [min]="0" [format]="'c2'"
(keydown.enter)="addLine()"></kendo-numerictextbox>
</label>
<label class="flex flex-col gap-1 md:col-span-2">Notes
<kendo-textbox [(ngModel)]="entry.notes" (keydown.enter)="addLine()"></kendo-textbox>
</label>
<div class="flex flex-wrap gap-2 md:col-span-2">
<button kendoButton (click)="markAnonymous()">Anonymous</button>
<button kendoButton (click)="showQuickAdd = true">+ Quick add member</button>
<button kendoButton themeColor="primary" (click)="addLine()">{{ editingIndex !== null ? 'Update line' : '+ Add
(Enter)' }}</button>
</div>
</div>
</section>
<section class="sessions-list">
<h3>Recent Sessions</h3>
<!-- Card C — Lines -->
<section class="card">
<h3 class="section-title">Lines / 已加入明細</h3>
<div *ngIf="buffer.length === 0" class="empty">No lines yet — add givings above.</div>
<kendo-grid *ngIf="buffer.length > 0" [data]="buffer">
<kendo-grid-column title="Giver">
<ng-template kendoGridCellTemplate let-l>{{ l.isAnonymous ? '(Anonymous)' : l.memberName }}</ng-template>
</kendo-grid-column>
<kendo-grid-column field="categoryName" title="Type"></kendo-grid-column>
<kendo-grid-column field="paymentMethod" title="Method" [width]="90"></kendo-grid-column>
<kendo-grid-column field="checkNumber" title="Check #" [width]="90"></kendo-grid-column>
<kendo-grid-column field="amount" title="Amount" [width]="110" format="c2"></kendo-grid-column>
<kendo-grid-column title="" [width]="120">
<ng-template kendoGridCellTemplate let-l let-i="rowIndex">
<button kendoButton fillMode="flat" (click)="editLine(i)">Edit</button>
<button kendoButton fillMode="flat" (click)="removeLine(i)">×</button>
</ng-template>
</kendo-grid-column>
</kendo-grid>
<div *ngIf="buffer.length > 0" class="lines-footer">Lines: {{ buffer.length }} · System total: {{ systemTotal |
currency }}</div>
</section>
<!-- Card D — Reconcile & submit -->
<section class="card">
<h3 class="section-title">Reconcile &amp; Submit / 對帳</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<label class="flex flex-col gap-1">Cash counted
<kendo-numerictextbox [(ngModel)]="cashTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox>
</label>
<label class="flex flex-col gap-1">Check counted
<kendo-numerictextbox [(ngModel)]="checkTotal" [min]="0" [format]="'c2'"></kendo-numerictextbox>
</label>
<label class="flex flex-col gap-1 md:col-span-2">Session notes
<kendo-textarea [(ngModel)]="notes" [rows]="2"></kendo-textarea>
</label>
</div>
<div class="flex flex-wrap items-center gap-4 mt-4">
<span>System total: {{ systemTotal | currency }}</span>
<span [class.ok]="difference === 0" [class.bad]="difference !== 0">Difference: {{ difference | currency }}</span>
<button kendoButton themeColor="primary"
[disabled]="buffer.length === 0 || (editingSessionId == null && dateConflict) || submitting"
(click)="submit()">{{ editingSessionId != null ? 'Update Session' : 'Submit' }}</button>
</div>
</section>
<!-- Card E — Recent sessions -->
<section class="card">
<h3 class="section-title">Recent Sessions</h3>
<kendo-grid [data]="sessions">
<kendo-grid-column field="sessionDate" title="Date" [width]="120"></kendo-grid-column>
<kendo-grid-column field="status" title="Status" [width]="110"></kendo-grid-column>
<kendo-grid-column title="Status" [width]="120">
<ng-template kendoGridCellTemplate let-s>
<span class="badge" [class.badge--draft]="s.status === 'Draft'"
[class.badge--submitted]="s.status === 'Submitted'" [class.badge--reconciled]="s.status === 'Reconciled'">{{
s.status }}</span>
</ng-template>
</kendo-grid-column>
<kendo-grid-column field="lineCount" title="Lines" [width]="80"></kendo-grid-column>
<kendo-grid-column field="systemTotal" title="System" [width]="110" format="c2"></kendo-grid-column>
<kendo-grid-column field="difference" title="Diff" [width]="100" format="c2"></kendo-grid-column>
<kendo-grid-column title="" [width]="140">
<kendo-grid-column title="" [width]="150">
<ng-template kendoGridCellTemplate let-s>
<button kendoButton fillMode="flat" *ngIf="s.status === 'Submitted'" (click)="reopenAndEdit(s)">Reopen &amp; Edit</button>
<button kendoButton fillMode="flat" *ngIf="s.status === 'Submitted'" (click)="reopenAndEdit(s)">Reopen &amp;
Edit</button>
<button kendoButton fillMode="flat" *ngIf="s.status === 'Draft'" (click)="continueEditDraft(s)">Continue
editing</button>
</ng-template>
</kendo-grid-column>
</kendo-grid>
</section>
<app-member-quick-add-dialog *ngIf="showQuickAdd"
(created)="onMemberQuickCreated($event)"
(cancelled)="showQuickAdd = false"></app-member-quick-add-dialog>
</div>
<app-member-quick-add-dialog *ngIf="showQuickAdd" (created)="onMemberQuickCreated($event)"
(cancelled)="showQuickAdd = false"></app-member-quick-add-dialog>
</div>
@@ -1,11 +1,32 @@
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.page-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; flex-wrap: wrap; }
.card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1rem;
}
.section-title { font-size: 1rem; font-weight: 600; margin: 0 0 0.75rem; }
.warn { background: #fff3cd; padding: 0.5rem 1rem; border-radius: 4px; margin-bottom: 1rem; }
.entry-row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end; margin-bottom: 1rem; }
.entry-row label { display: flex; flex-direction: column; gap: 0.25rem; }
.entry-actions { display: flex; gap: 0.5rem; }
.anon-chip { padding: 0.25rem 0.5rem; background: #eee; border-radius: 4px; }
.reconcile { display: flex; gap: 1rem; align-items: flex-end; margin-top: 1rem; }
.reconcile .ok { color: green; font-weight: 600; }
.reconcile .bad { color: #c00; font-weight: 600; }
.edit-banner { display: flex; align-items: center; gap: 1rem; background: #fff3cd; border-left: 4px solid #f0a500; padding: 0.5rem 1rem; border-radius: 4px; margin-bottom: 1rem; font-weight: 500; }
.sessions-list { margin-top: 2rem; }
.ok { color: green; font-weight: 600; }
.bad { color: #c00; font-weight: 600; }
.empty { color: #6b7280; padding: 1rem 0; }
.lines-footer { margin-top: 0.5rem; color: #374151; font-weight: 500; }
.badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
line-height: 1.4;
}
.badge--draft { background: #fef3c7; color: #92400e; }
.badge--submitted { background: #dcfce7; color: #166534; }
.badge--reconciled { background: #e2e8f0; color: #334155; }
@@ -87,6 +87,15 @@ export class OfferingSessionPageComponent implements OnInit {
});
}
// 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;
this.api.getById(s.id).subscribe({
next: dto => this.loadIntoBuffer(dto),
error: (err: { error?: { message?: string } }) => alert(err?.error?.message ?? 'Load failed.'),
});
}
private loadIntoBuffer(dto: OfferingSessionDto): void {
this.editingSessionId = dto.id;
this.sessionDate = new Date(dto.sessionDate + 'T00:00:00');
@@ -110,6 +119,8 @@ export class OfferingSessionPageComponent implements OnInit {
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 {
@@ -130,6 +141,10 @@ export class OfferingSessionPageComponent implements OnInit {
this.selectedMemberId = null; this.selectedMemberName = null;
}
clearAnonymous(): void {
this.entry.isAnonymous = false;
}
addLine(): void {
if (this.entry.amount <= 0) return;
if (this.entry.paymentMethod === 'Check' && !this.entry.checkNumber) return;
@@ -221,5 +236,12 @@ export class OfferingSessionPageComponent implements OnInit {
};
}
private toIso(d: Date): string { return d.toISOString().slice(0, 10); }
// Format using LOCAL date components — NOT toISOString(), which converts to UTC and
// rolls the date forward a day for behind-UTC users when the Date carries an evening time.
private toIso(d: Date): string {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
}
@@ -12,7 +12,7 @@
<div class="branding-section">
<div class="branding-content">
<div class="logo-container">
<img src="assets/rbj-logo.svg" alt="RBJ Logo" class="logo-image">
<img src="assets/images/ROLCC-Logo-Color.png" alt="ROLCC Logo" class="logo-image">
<div class="logo-text">
<h1>ROLCC AC</h1>
<span class="tagline">Church Management Portal</span>
@@ -5,7 +5,8 @@
<!-- TAB 1: Basic Info -->
<kendo-tabstrip-tab title="Basic Info" [selected]="true">
<ng-template kendoTabContent>
<div class="k-form k-form-horizontal k-mt-4">
<div class="k-form k-form-vertical k-mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<kendo-formfield>
<kendo-label text="Legal First Name *"></kendo-label>
@@ -17,7 +18,7 @@
<kendo-textbox formControlName="lastName_en"></kendo-textbox>
</kendo-formfield>
<kendo-formfield>
<kendo-formfield class="md:col-span-2">
<kendo-label text="Nick Name (Common Name)"></kendo-label>
<kendo-textbox formControlName="nickName" placeholder="e.g. Chris"></kendo-textbox>
</kendo-formfield>
@@ -63,6 +64,7 @@
</kendo-dropdownlist>
</kendo-formfield>
</div>
</div>
</ng-template>
</kendo-tabstrip-tab>
@@ -70,9 +72,10 @@
<!-- TAB 2: Contact -->
<kendo-tabstrip-tab title="Contact">
<ng-template kendoTabContent>
<div class="k-form k-form-horizontal k-mt-4">
<div class="k-form k-form-vertical k-mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<kendo-formfield>
<kendo-formfield class="md:col-span-2">
<kendo-label text="Email"></kendo-label>
<kendo-textbox formControlName="email"></kendo-textbox>
</kendo-formfield>
@@ -87,7 +90,7 @@
<kendo-textbox formControlName="phoneHome"></kendo-textbox>
</kendo-formfield>
<kendo-formfield>
<kendo-formfield class="md:col-span-2">
<kendo-label text="Address"></kendo-label>
<kendo-textbox formControlName="address"></kendo-textbox>
</kendo-formfield>
@@ -112,6 +115,7 @@
<kendo-textbox formControlName="country"></kendo-textbox>
</kendo-formfield>
</div>
</div>
</ng-template>
</kendo-tabstrip-tab>
@@ -119,7 +123,8 @@
<!-- TAB 3: Church Info -->
<kendo-tabstrip-tab title="Church Info">
<ng-template kendoTabContent>
<div class="k-form k-form-horizontal k-mt-4">
<div class="k-form k-form-vertical k-mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
<kendo-formfield>
<kendo-label text="Join Date"></kendo-label>
@@ -131,16 +136,17 @@
<kendo-datepicker formControlName="baptismDate"></kendo-datepicker>
</kendo-formfield>
<kendo-formfield>
<kendo-formfield class="md:col-span-2">
<kendo-label text="Baptism Church"></kendo-label>
<kendo-textbox formControlName="baptismChurch"></kendo-textbox>
</kendo-formfield>
<kendo-formfield>
<kendo-formfield class="md:col-span-2">
<kendo-label text="Notes"></kendo-label>
<kendo-textarea formControlName="notes" [rows]="4"></kendo-textarea>
</kendo-formfield>
</div>
</div>
</ng-template>
</kendo-tabstrip-tab>
@@ -1,5 +1,6 @@
<kendo-dialog title="Add New User" (close)="onCancel()" [minWidth]="460" [width]="500">
<form [formGroup]="form" class="k-form k-form-vertical k-p-2">
<div class="grid grid-cols-1 gap-y-3">
<kendo-formfield>
<kendo-label text="Email *"></kendo-label>
@@ -8,14 +9,14 @@
<kendo-formerror *ngIf="form.get('email')?.errors?.['email']">Invalid email.</kendo-formerror>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-formfield>
<kendo-label text="Roles *"></kendo-label>
<kendo-multiselect formControlName="roles" [data]="roleOptions"
placeholder="Select roles"></kendo-multiselect>
<kendo-formerror>At least one role is required.</kendo-formerror>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-formfield>
<kendo-label text="Language"></kendo-label>
<kendo-dropdownlist formControlName="languagePreference"
[data]="langOptions" textField="text" valueField="value"
@@ -23,12 +24,13 @@
</kendo-dropdownlist>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-formfield>
<kendo-label text="Member ID (optional)"></kendo-label>
<kendo-numerictextbox formControlName="memberId" [format]="'0'"
placeholder="Link to a member record"></kendo-numerictextbox>
</kendo-formfield>
</div>
</form>
<kendo-dialog-actions>
@@ -1,5 +1,6 @@
<kendo-dialog title="Edit User" (close)="onCancel()" [minWidth]="460" [width]="500">
<form [formGroup]="form" class="k-form k-form-vertical k-p-2">
<div class="grid grid-cols-1 gap-y-3">
<kendo-formfield>
<kendo-label text="Email *"></kendo-label>
@@ -8,13 +9,13 @@
<kendo-formerror *ngIf="form.get('email')?.errors?.['email']">Invalid email.</kendo-formerror>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-formfield>
<kendo-label text="Roles *"></kendo-label>
<kendo-multiselect formControlName="roles" [data]="roleOptions"
placeholder="Select roles"></kendo-multiselect>
</kendo-formfield>
<kendo-formfield class="k-mt-3">
<kendo-formfield>
<kendo-label text="Language"></kendo-label>
<kendo-dropdownlist formControlName="languagePreference"
[data]="langOptions" textField="text" valueField="value"
@@ -22,11 +23,12 @@
</kendo-dropdownlist>
</kendo-formfield>
<div class="k-d-flex k-align-items-center k-gap-2 k-mt-4">
<div class="flex items-center gap-2">
<input kendoCheckBox type="checkbox" formControlName="isActive" id="isActiveCheck" />
<kendo-label for="isActiveCheck" text="Account Active"></kendo-label>
</div>
</div>
</form>
<kendo-dialog-actions>
@@ -16,10 +16,10 @@
<aside class="sidebar" [class.collapsed]="sidebarCollapsed">
<div class="sidebar-header">
<div class="logo-section">
<img src="assets/rbj-logo.svg" alt="RBJ Logo" class="logo-image">
<img src="assets/images/ROLCC-Logo-Color.png" alt="ROLCC Logo" class="logo-image">
<div class="logo-text" *ngIf="!sidebarCollapsed">
<h2>ROLCC AC</h2>
<span class="tagline">Escrow Portal</span>
<span class="tagline">Church Portal</span>
</div>
</div>
<button class="sidebar-toggle" (click)="toggleSidebar()" title="Toggle sidebar">
@@ -1,7 +1,7 @@
<div class="token-verification-container">
<div class="verification-card">
<div class="logo-section">
<img src="assets/rbj-logo.svg" alt="RBJ Logo" class="logo">
<img src="assets/images/ROLCC-Logo-Color.png" alt="ROLCC Logo" class="logo">
</div>
<div class="verification-content">
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+19
View File
@@ -0,0 +1,19 @@
/*
* Tailwind v4 — utilities + theme only. NO preflight: Kendo UI owns base element
* styling and styles.scss already ships its own reset. Loading Tailwind's base reset
* would fight Kendo.
*
* theme -> kept in its own layer (design tokens / CSS vars only, no output by itself)
* utilities -> imported UNLAYERED so layout utilities (grid/col-span/gap/flex) reliably
* win over Kendo's unlayered CDN theme when they ever land on the same node.
*
* Used by the shared form-layout convention: a neutral wrapper <div> carries
* grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3
* and full-width fields use md:col-span-2.
*/
@layer theme;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css";
/* Explicit source scanning (auto-detection also covers these). */
@source "./";