Files
lmnp-organizer/frontend/src/app/components/rent-payments-page/rent-payments-page.ts
T

108 lines
3.7 KiB
TypeScript

import { Component, computed, inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { RentDue } from '../../services/lease.service';
import { WorkspaceService } from '../../services/workspace.service';
import { DatePipe } from '@angular/common';
@Component({
selector: 'app-rent-payments-page',
imports: [MatButtonModule, MatProgressBarModule, DatePipe],
templateUrl: './rent-payments-page.html',
styleUrl: './rent-payments-page.css',
})
export class RentPaymentsPage {
readonly workspace = inject(WorkspaceService);
readonly showFullSchedule = signal(false);
readonly displayedRentDues = computed(() =>
this.showFullSchedule()
? sortRentDues(this.workspace.rentDues())
: currentRentDues(this.workspace.rentDues()),
);
readonly contentReady = computed(
() =>
this.workspace.rentDueLoadState().loaded &&
this.workspace.leaseLoadState().loaded &&
this.workspace.apartmentLoadState().loaded,
);
readonly contentLoading = computed(
() =>
this.workspace.rentDueLoadState().loading ||
this.workspace.leaseLoadState().loading ||
this.workspace.apartmentLoadState().loading,
);
readonly loadError = computed(
() =>
this.workspace.rentDueLoadState().error ||
this.workspace.leaseLoadState().error ||
this.workspace.apartmentLoadState().error,
);
constructor() {
this.load();
}
apartmentName(rentDue: RentDue): string {
const lease = this.workspace.leases().find((item) => item.id === rentDue.leaseId);
return (
this.workspace.apartments().find((item) => item.id === lease?.apartmentId)?.name ??
'Bien inconnu'
);
}
monthLabel(month: string): string {
return new Intl.DateTimeFormat('fr-FR', { month: 'long', year: 'numeric' }).format(
new Date(`${month}-01T12:00:00`),
);
}
settlementLabel(settlement: RentDue['settlement']): string {
return { UNPAID: 'Non réglée', PARTIALLY_PAID: 'Partiellement réglée', PAID: 'Réglée' }[
settlement
];
}
timingLabel(timing: RentDue['timing']): string {
return { UPCOMING: 'À venir', OVERDUE: 'En retard' }[timing];
}
load(): void {
this.workspace.loadRentDues();
this.workspace.loadLeases();
this.workspace.loadApartments();
}
toggleSchedule(): void {
this.showFullSchedule.update((showFullSchedule) => !showFullSchedule);
}
}
function sortRentDues(rentDues: readonly RentDue[]): RentDue[] {
return [...rentDues].sort((left, right) => left.dueDate.localeCompare(right.dueDate));
}
function currentRentDues(rentDues: readonly RentDue[]): RentDue[] {
const rentDuesByLease = new Map<string, RentDue[]>();
for (const rentDue of sortRentDues(rentDues)) {
const schedule = rentDuesByLease.get(rentDue.leaseId);
if (schedule) schedule.push(rentDue);
else rentDuesByLease.set(rentDue.leaseId, [rentDue]);
}
return sortRentDues([...rentDuesByLease.values()].flatMap(currentSchedule));
}
function currentSchedule(schedule: RentDue[]): RentDue[] {
const nextDueIndex = schedule.findIndex((rentDue) => rentDue.timing === 'UPCOMING');
const throughNextDue = nextDueIndex === -1 ? schedule : schedule.slice(0, nextDueIndex + 1);
const lastPaidIndex = throughNextDue.reduce(
(lastPaid, rentDue, index) => (rentDue.settlement === 'PAID' ? index : lastPaid),
-1,
);
const firstUnpaidIndex = throughNextDue.findIndex((rentDue) => rentDue.settlement !== 'PAID');
const firstVisibleIndex = Math.min(
...[lastPaidIndex, firstUnpaidIndex].filter((index) => index !== -1),
);
return firstVisibleIndex === Infinity ? [] : throughNextDue.slice(firstVisibleIndex);
}