Tenant Rent Ledger
blockreal-estateProperty management resident portal with tenant header, 4 financial overview cards (monthly rent, next due date, autopay status, escrow deposit), autopay toggle with date schedule selection, and a complete 6-record payment ledger with receipt actions.
Installation
$pnpm dlx uipkge-ng@latest add tenant-rent-ledger$npx uipkge-ng@latest add tenant-rent-ledger$yarn dlx uipkge-ng@latest add tenant-rent-ledger$bunx uipkge-ng@latest add tenant-rent-ledgerManifest:
https://uipkge.dev/r/angular/tenant-rent-ledger.jsonInstalls to:src/app/components/blocks/tenant-rent-ledger/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
PaymentRecordinterface PaymentRecord {
id: string
period: string
description: string
dueDate: string
paidDate: string
method: string
methodType: 'ach' | 'card' | 'wire'
amount: string
status: 'paid' | 'waived'
statusLabel: string
}FinancialCardinterface FinancialCard {
title: string
value: string
unit: string
description: string
icon: typeof Home
iconColor: string
}Files installed (2)
src/app/components/blocks/tenant-rent-ledger/tenant-rent-ledger.component.ts20.5 kB
// Property management resident portal with tenant header, 4 financial // overview cards (monthly rent, next due date, autopay status, escrow deposit), // autopay toggle with date schedule selection, and a complete 6-record payment // ledger with receipt actions. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { Building2, Calendar, CheckCircle2, Clock, CreditCard, Download, Home, Landmark, LucideAngularModule, ShieldCheck, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSwitchComponent } from '@/ui/switch/switch.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export interface PaymentRecord { id: string period: string description: string dueDate: string paidDate: string method: string methodType: 'ach' | 'card' | 'wire' amount: string status: 'paid' | 'waived' statusLabel: string } interface FinancialCard { title: string value: string unit: string description: string icon: typeof Home iconColor: string } const financialCards: FinancialCard[] = [ { title: 'Monthly Base Rent', value: '$3,450.00', unit: '/ mo', description: 'Fixed lease rate through Jul 2027', icon: Home, iconColor: 'bg-primary/10 text-primary', }, { title: 'Next Due Date', value: 'Sep 01, 2026', unit: '', description: 'Due in 11 days · Grace period to Sep 5', icon: Calendar, iconColor: 'bg-info/10 text-info', }, { title: 'Autopay Status', value: 'Active', unit: '', description: 'Scheduled for 1st of month with Visa •••• 4892', icon: Zap, iconColor: 'bg-success/10 text-success', }, { title: 'Security Deposit on File', value: '$3,450.00', unit: '', description: 'Held in Escrow (Interest Bearing)', icon: ShieldCheck, iconColor: 'bg-warning/10 text-warning', }, ] const paymentRecords: PaymentRecord[] = [ { id: 'pay-2026-08', period: 'August 2026 Rent', description: 'Base Rent ($3,400.00) + High-Speed Fiber ($50.00)', dueDate: 'Aug 01, 2026', paidDate: 'Aug 01, 2026', method: 'ACH Transfer · Chase •••• 4892', methodType: 'ach', amount: '$3,450.00', status: 'paid', statusLabel: 'Paid on Time', }, { id: 'pay-2026-07', period: 'July 2026 Rent', description: 'Base Rent ($3,400.00) + Assigned Parking Spot #4B ($50.00)', dueDate: 'Jul 01, 2026', paidDate: 'Jul 01, 2026', method: 'ACH Transfer · Chase •••• 4892', methodType: 'ach', amount: '$3,450.00', status: 'paid', statusLabel: 'Paid on Time', }, { id: 'pay-2026-06', period: 'June 2026 Rent', description: 'Base Rent ($3,400.00) + Water & Sewer Allocation ($50.00)', dueDate: 'Jun 01, 2026', paidDate: 'Jun 03, 2026', method: 'Visa •••• 4892', methodType: 'card', amount: '$3,450.00', status: 'waived', statusLabel: 'Late Fee Waived', }, { id: 'pay-2026-05', period: 'May 2026 Rent', description: 'Base Rent ($3,400.00) + Storage Locker #12 ($50.00)', dueDate: 'May 01, 2026', paidDate: 'May 01, 2026', method: 'ACH Transfer · Chase •••• 4892', methodType: 'ach', amount: '$3,450.00', status: 'paid', statusLabel: 'Paid on Time', }, { id: 'pay-2026-04', period: 'April 2026 Rent', description: 'First Full Month Base Rent ($3,400.00) + Pet Rent ($50.00)', dueDate: 'Apr 01, 2026', paidDate: 'Apr 01, 2026', method: 'ACH Transfer · Chase •••• 4892', methodType: 'ach', amount: '$3,450.00', status: 'paid', statusLabel: 'Paid on Time', }, { id: 'pay-2026-03-dep', period: 'Security Deposit & Move-In Fee', description: '100% Refundable Security Escrow Deposit + Key Fob Activation', dueDate: 'Mar 15, 2026', paidDate: 'Mar 15, 2026', method: 'Wire Transfer · Wells Fargo •••• 1044', methodType: 'wire', amount: '$3,450.00', status: 'paid', statusLabel: 'Paid on Time', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-tenant-rent-ledger, [ui-tenant-rent-ledger]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiSeparatorComponent, UiSwitchComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="tenant-rent-ledger" [class]="rootClass"> <!-- Resident & Unit Header --> <div class="bg-card flex flex-col gap-4 rounded-xl border p-5 shadow-xs sm:flex-row sm:items-center sm:justify-between" > <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2.5"> <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">Elena Rostova</h2> <span ui-badge variant="secondary" class="text-xs font-semibold"> Unit 4B </span> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium"> <lucide-icon [img]="CheckCircle2" class="text-success size-3" /> $0.00 Paid in Full </span> </div> <div class="text-muted-foreground flex items-center gap-1.5 text-xs sm:text-sm"> <lucide-icon [img]="Building2" class="text-muted-foreground size-4 shrink-0" /> <span>Pacific Heights Luxury Residences, San Francisco</span> </div> </div> <div class="flex items-center gap-3 self-start sm:self-auto"> <button ui-button class="font-medium shadow-xs"> <lucide-icon [img]="CreditCard" class="size-4" /> Pay Rent Now </button> </div> </div> <!-- 4 Financial Overview Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> @for (card of financeCards; track card.title) { <div ui-card class="shadow-xs"> <div ui-card-content class="space-y-2 p-5"> <div class="flex items-center justify-between gap-2"> <p class="text-muted-foreground text-xs font-medium">{{ card.title }}</p> <div [class]="cardIconClass(card)"> <lucide-icon [img]="card.icon" class="size-4" /> </div> </div> <div> <div class="flex items-baseline gap-1"> <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums">{{ card.value }}</p> @if (card.unit) { <span class="text-muted-foreground text-xs font-normal">{{ card.unit }}</span> } </div> <p class="text-muted-foreground mt-1 text-xs">{{ card.description }}</p> </div> </div> </div> } </div> <!-- Autopay & Payment Method Card --> <div ui-card class="shadow-xs"> <div ui-card-header> <div class="flex items-center justify-between"> <div> <h3 ui-card-title class="text-base font-semibold">Autopay & Payment Preferences</h3> <p ui-card-description class="text-xs"> Configure automatic monthly rent deduction and manage saved funding methods. </p> </div> @if (autopayEnabled()) { <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium"> Autopay Active </span> } @else { <span ui-badge variant="secondary" class="text-xs font-medium"> Autopay Paused </span> } </div> </div> <div ui-card-content class="space-y-5"> <!-- Toggle Autopay Switch --> <div class="bg-muted/30 flex flex-col gap-3 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between" > <div class="space-y-0.5"> <label for="autopay-toggle" class="text-foreground cursor-pointer text-sm font-medium"> Enable Automatic Rent Deductions </label> <p class="text-muted-foreground text-xs"> Automatically charge your primary payment method on your selected schedule before due date. </p> </div> <button ui-switch id="autopay-toggle" [checked]="autopayEnabled()" (checkedChange)="autopayEnabled.set($event)" ></button> </div> <div ui-separator></div> <!-- Primary Bank Account / Card --> <div class="space-y-3"> <div class="flex items-center justify-between"> <h4 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Primary Payment Method </h4> <button ui-button variant="ghost" size="xs" class="text-primary hover:text-primary text-xs font-medium"> Change Method </button> </div> <div class="flex flex-col gap-3 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-center gap-3.5"> <div class="bg-background flex size-10 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="CreditCard" class="text-foreground size-5" /> </div> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-semibold tabular-nums">Visa •••• 4892</span> <span ui-badge variant="secondary" class="text-xs font-normal">Default</span> </div> <p class="text-muted-foreground text-xs">Elena Rostova · Expires 08/28 · Bank of America</p> </div> </div> <div class="flex items-center gap-2"> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs"> <lucide-icon [img]="CheckCircle2" class="text-success size-3" /> Verified for ACH & Debit </span> </div> </div> </div> <div ui-separator></div> <!-- Autopay Date Selector (1st vs 5th) --> <div class="space-y-3"> <div class="space-y-0.5"> <h4 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Scheduled Deduction Date </h4> <p class="text-muted-foreground text-xs"> Select when rent will be debited from your account each cycle. </p> </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <button type="button" [disabled]="!autopayEnabled()" [class]="scheduleOptionClass('1st')" (click)="autopayDay.set('1st')" > <div [class]="scheduleRadioClass('1st')"> @if (autopayDay() === '1st' && autopayEnabled()) { <span class="bg-background size-1.5 rounded-full"></span> } </div> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">1st of the month</span> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium" > Recommended </span> </div> <p class="text-muted-foreground text-xs"> Debited promptly on the rent due date. Ensures zero delinquency risk. </p> </div> </button> <button type="button" [disabled]="!autopayEnabled()" [class]="scheduleOptionClass('5th')" (click)="autopayDay.set('5th')" > <div [class]="scheduleRadioClass('5th')"> @if (autopayDay() === '5th' && autopayEnabled()) { <span class="bg-background size-1.5 rounded-full"></span> } </div> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">5th of the month</span> <span ui-badge variant="secondary" class="text-xs font-normal"> Grace Period </span> </div> <p class="text-muted-foreground text-xs"> Processes within the standard 5-day building grace window before late fees. </p> </div> </button> </div> </div> </div> </div> <!-- Payment History Ledger Table --> <div class="space-y-3"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 class="text-foreground text-base font-semibold tracking-tight">Payment History & Ledger</h3> <p class="text-muted-foreground text-xs"> Complete transaction record of monthly rent charges, utility allocations, and security deposits. </p> </div> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="self-start sm:self-auto" > <lucide-icon [img]="Download" class="size-4" /> Download Statement (PDF) </button> </div> <div class="bg-card overflow-x-auto rounded-lg border shadow-xs"> <table ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head>Billing Period & Description</th> <th ui-table-head>Due Date</th> <th ui-table-head>Paid Date</th> <th ui-table-head>Payment Method</th> <th ui-table-head class="text-right">Amount Paid</th> <th ui-table-head>Status</th> <th ui-table-head class="text-right"><span class="sr-only">Actions</span></th> </tr> </thead> <tbody ui-table-body> @for (record of records; track record.id) { <tr ui-table-row class="hover:bg-muted/50"> <td ui-table-cell> <div class="text-foreground text-sm font-medium">{{ record.period }}</div> <div class="text-muted-foreground text-xs">{{ record.description }}</div> </td> <td ui-table-cell class="text-muted-foreground text-sm tabular-nums">{{ record.dueDate }}</td> <td ui-table-cell class="text-foreground text-sm font-medium tabular-nums">{{ record.paidDate }}</td> <td ui-table-cell> <div class="text-foreground flex items-center gap-1.5 text-xs sm:text-sm"> @if (record.methodType === 'ach') { <lucide-icon [img]="Landmark" class="text-muted-foreground size-3.5 shrink-0" /> } @else if (record.methodType === 'card') { <lucide-icon [img]="CreditCard" class="text-muted-foreground size-3.5 shrink-0" /> } @else { <lucide-icon [img]="ShieldCheck" class="text-muted-foreground size-3.5 shrink-0" /> } <span class="truncate">{{ record.method }}</span> </div> </td> <td ui-table-cell class="text-foreground text-right text-sm font-bold tabular-nums"> {{ record.amount }} </td> <td ui-table-cell> @if (record.status === 'paid') { <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium whitespace-normal" > <lucide-icon [img]="CheckCircle2" class="text-success size-3" /> {{ record.statusLabel }} </span> } @else { <span ui-badge variant="outline" class="border-info/20 bg-info/10 text-info text-xs font-medium"> <lucide-icon [img]="Clock" class="text-info size-3" /> {{ record.statusLabel }} </span> } </td> <td ui-table-cell class="text-right"> <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground h-8 gap-1.5 text-xs" aria-label="Download receipt PDF" > <lucide-icon [img]="Download" class="size-3.5" /> <span>Receipt</span> </button> </td> </tr> } </tbody> </table> </div> <div class="text-muted-foreground flex flex-col gap-3 pt-1 text-xs sm:flex-row sm:items-center sm:justify-between" > <p> Showing <span class="text-foreground font-medium">6</span> of <span class="text-foreground font-medium">6</span> lease payments · Lease Ref: <span class="font-mono">PH-4B-2026</span> </p> <div class="flex items-center gap-2"> <button ui-button variant="outline" size="xs" disabled>Previous</button> <button ui-button variant="outline" size="xs" disabled>Next</button> </div> </div> </div> </div> `, }) export class UiTenantRentLedgerComponent { protected readonly Building2 = Building2 protected readonly Calendar = Calendar protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly CreditCard = CreditCard protected readonly Download = Download protected readonly Home = Home protected readonly Landmark = Landmark protected readonly ShieldCheck = ShieldCheck protected readonly Zap = Zap protected readonly financeCards = financialCards protected readonly records = paymentRecords @Input('class') className?: string readonly autopayEnabled = signal(true) readonly autopayDay = signal<'1st' | '5th'>('1st') get rootClass(): string { return cn('w-full space-y-6', this.className) } cardIconClass(card: FinancialCard): string { return cn('flex size-8 shrink-0 items-center justify-center rounded-lg', card.iconColor) } scheduleOptionClass(day: '1st' | '5th'): string { return cn( 'focus-visible:ring-ring flex cursor-pointer items-start gap-3 rounded-lg border p-3.5 text-left transition-colors duration-150 focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50', this.autopayDay() === day && this.autopayEnabled() ? 'border-primary bg-primary/5 ring-primary ring-1' : 'border-border hover:bg-muted/50', ) } scheduleRadioClass(day: '1st' | '5th'): string { return cn( 'mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full border transition-colors', this.autopayDay() === day && this.autopayEnabled() ? 'border-primary bg-primary text-primary-foreground' : 'border-muted-foreground/50', ) } }src/app/components/blocks/tenant-rent-ledger/index.ts0.1 kB
export { UiTenantRentLedgerComponent, type PaymentRecord } from './tenant-rent-ledger.component'
Raw manifest:https://uipkge.dev/r/angular/tenant-rent-ledger.json