refactor: simplify lease card and rent payments page logic

This commit is contained in:
nessar committed 2026-08-17 14:18:15 +02:00
1 parent a039243d03
commit 6ce0719a20
4 files changed
+10 -177

No files matched your search

@@ -1,7 +1,7 @@
<article class="lease-card" [attr.aria-busy]="deleting() || downloading() || activating()">
<div class="lease-topline">
<span class="status-dot"></span><span>{{ statusLabel() }}</span
><strong>{{ lease().monthlyRent.toLocaleString('fr-FR') }} €</strong>
<span class="status-dot"></span><span>{{ statusLabel() }}</span><strong>{{
lease().monthlyRent.toLocaleString('fr-FR') }} €</strong>
</div>
<h2>{{ apartmentName() }}</h2>
<p class="tenant-name">{{ tenantNames() }}</p>
@@ -30,50 +30,14 @@
}
</div>
}
@if (rentDues().length) {
<section class="rent-schedule" aria-label="Échéancier de loyer">
<h3>Échéancier</h3>
@for (rentDue of rentDues(); track rentDue.id) {
<article class="rent-due">
<strong>{{ rentDue.month }}</strong>
<dl>
<div>
<dt>Loyer</dt>
<dd>{{ rentDue.rent.toLocaleString('fr-FR') }} €</dd>
</div>
<div>
<dt>Charges</dt>
<dd>{{ rentDue.charges.toLocaleString('fr-FR') }} €</dd>
</div>
<div>
<dt>Total</dt>
<dd>{{ rentDue.total.toLocaleString('fr-FR') }} €</dd>
</div>
<div>
<dt>Restant</dt>
<dd>{{ rentDue.remainingAmount.toLocaleString('fr-FR') }} €</dd>
</div>
</dl>
<div class="rent-due-labels">
<span>Règlement : {{ settlementLabel(rentDue.settlement) }}</span>
<span>Temporalité : {{ timingLabel(rentDue.timing) }}</span>
</div>
</article>
}
</section>
}
@if (deleteError() || downloadError() || activationError()) {
<p class="card-error" role="alert">
{{ deleteError() || downloadError() || activationError() }}
</p>
}
<div class="card-actions" aria-live="polite">
<button
mat-button
type="button"
[disabled]="deleting() || downloading() || activating()"
(click)="pdfRequested.emit(lease())"
>
<button mat-button type="button" [disabled]="deleting() || downloading() || activating()"
(click)="pdfRequested.emit(lease())">
@if (downloading()) {
<mat-spinner diameter="16" aria-hidden="true" /> Téléchargement…
} @else {
@@ -81,33 +45,20 @@
}
</button>
@if (lease().id && lease().status === 'DRAFT') {
<button
mat-button
type="button"
[disabled]="deleting() || downloading() || activating()"
(click)="editRequested.emit(lease())"
>
<button mat-button type="button" [disabled]="deleting() || downloading() || activating()"
(click)="editRequested.emit(lease())">
Modifier
</button>
<button
mat-flat-button
type="button"
[disabled]="deleting() || downloading() || activating()"
(click)="activationRequested.emit(lease().id!)"
>
<button mat-flat-button type="button" [disabled]="deleting() || downloading() || activating()"
(click)="activationRequested.emit(lease().id!)">
@if (activating()) {
<mat-spinner diameter="16" aria-hidden="true" /> Activation…
} @else {
Activer
}
</button>
<button
mat-button
type="button"
class="danger-action"
[disabled]="deleting() || downloading() || activating()"
(click)="deleteRequested.emit(lease().id!)"
>
<button mat-button type="button" class="danger-action" [disabled]="deleting() || downloading() || activating()"
(click)="deleteRequested.emit(lease().id!)">
@if (deleting()) {
<mat-spinner diameter="16" aria-hidden="true" /> Suppression…
} @else {
@@ -115,45 +115,4 @@ describe('LeaseCard', () => {
expect(fixture.nativeElement.textContent).not.toContain('Activer');
});
it('shows the resulting rent schedule with separate settlement and timing labels', () => {
const fixture = TestBed.createComponent(LeaseCard);
fixture.componentRef.setInput('apartments', []);
fixture.componentRef.setInput('tenants', []);
fixture.componentRef.setInput('lease', {
id: 'lease-1',
apartmentId: 'apartment-1',
tenantIds: [],
guarantors: [],
startDate: '2026-01-01',
endDate: '2026-01-31',
monthlyRent: 800,
monthlyCharges: 50,
securityDeposit: 800,
status: 'ACTIVE',
});
fixture.componentRef.setInput('rentDues', [
{
id: 'due-1',
leaseId: 'lease-1',
month: '2026-01',
dueDate: '2026-01-01',
rent: 800,
charges: 50,
total: 850,
remainingAmount: 850,
settlement: 'UNPAID',
timing: 'OVERDUE',
},
]);
fixture.detectChanges();
const text = fixture.nativeElement.textContent.replace(/\s+/g, ' ');
const amounts = [...fixture.nativeElement.querySelectorAll('.rent-due dl div')].map((term) =>
[...term.children].map((item) => item.textContent.trim()).join(' '),
);
expect(text).toContain('2026-01');
expect(amounts).toEqual(['Loyer 800 €', 'Charges 50 €', 'Total 850 €', 'Restant 850 €']);
expect(text).toContain('Règlement : Non réglée');
expect(text).toContain('Temporalité : En retard');
});
});
@@ -48,14 +48,4 @@ export class LeaseCard {
this.lease().status
];
}
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];
}
}
@@ -107,71 +107,4 @@ describe('LeasesPage', () => {
expect(workspace.loadRentDues).toHaveBeenCalledOnce();
});
it('renders each échéance only under its matching bail', () => {
workspace.apartmentLoadState.set({ loaded: true, loading: false, error: '' });
workspace.tenantLoadState.set({ loaded: true, loading: false, error: '' });
workspace.leaseLoadState.set({ loaded: true, loading: false, error: '' });
workspace.rentDueLoadState.set({ loaded: true, loading: false, error: '' });
workspace.leases.set([
{
id: 'lease-1',
apartmentId: 'apartment-1',
tenantIds: [],
guarantors: [],
startDate: '2026-01-01',
endDate: '2026-12-31',
monthlyRent: 800,
monthlyCharges: 50,
securityDeposit: 800,
status: 'ACTIVE',
},
{
id: 'lease-2',
apartmentId: 'apartment-2',
tenantIds: [],
guarantors: [],
startDate: '2027-01-01',
endDate: '2027-12-31',
monthlyRent: 900,
monthlyCharges: 60,
securityDeposit: 900,
status: 'ACTIVE',
},
]);
workspace.rentDues.set([
{
id: 'due-1',
leaseId: 'lease-1',
month: '2026-01',
dueDate: '2026-01-01',
rent: 800,
charges: 50,
total: 850,
remainingAmount: 850,
settlement: 'UNPAID',
timing: 'OVERDUE',
},
{
id: 'due-2',
leaseId: 'lease-2',
month: '2027-01',
dueDate: '2027-01-01',
rent: 900,
charges: 60,
total: 960,
remainingAmount: 960,
settlement: 'UNPAID',
timing: 'UPCOMING',
},
]);
const fixture = TestBed.createComponent(LeasesPage);
fixture.detectChanges();
const cards = fixture.nativeElement.querySelectorAll('.lease-card');
expect(cards).toHaveLength(2);
expect(cards[0].textContent).toContain('2026-01');
expect(cards[0].textContent).not.toContain('2027-01');
expect(cards[1].textContent).toContain('2027-01');
expect(cards[1].textContent).not.toContain('2026-01');
});
});