108 lines
3.7 KiB
TypeScript
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);
|
|
}
|