refactor: split tenant and lease page modules

This commit is contained in:
nessar committed 2026-08-17 14:18:14 +02:00
1 parent ebfc62a5e2
commit 3ca8514231
27 files changed
+1199 -1143

No files matched your search

@@ -0,0 +1,115 @@
:host {
display: block;
}
.tenant-card {
min-width: 0;
padding: 20px;
border: 1px solid var(--border);
border-radius: 22px;
background: var(--surface);
}
.tenant-identity {
display: flex;
align-items: center;
gap: 14px;
}
.tenant-identity h2 {
margin: 0;
font-size: 1.25rem;
letter-spacing: -0.03em;
}
.tenant-avatar {
display: grid;
width: 46px;
height: 46px;
flex: 0 0 46px;
place-items: center;
border-radius: 15px;
background: var(--surface-accent);
color: var(--accent);
font-weight: 800;
}
.contact-list {
display: grid;
gap: 11px;
margin: 22px 0;
}
.contact-list div {
min-width: 0;
}
.contact-list dt {
color: var(--text-subtle);
font-size: 0.7rem;
font-weight: 750;
text-transform: uppercase;
}
.contact-list dd {
overflow-wrap: anywhere;
margin: 3px 0 0;
font-size: 0.88rem;
}
.attachments {
padding: 15px;
border-radius: 15px;
background: var(--surface-soft);
}
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
font-size: 0.82rem;
}
.section-title span {
display: grid;
min-width: 24px;
height: 24px;
place-items: center;
border-radius: 50%;
background: var(--surface);
color: var(--text-muted);
}
.attachment-row.mat-mdc-button {
--mdc-text-button-label-text-color: var(--text);
width: 100%;
min-width: 0;
justify-content: flex-start;
padding: 0 5px;
font-size: 0.78rem;
}
.upload-action {
display: block;
min-height: 40px;
padding: 11px 5px 4px;
color: var(--accent);
cursor: pointer;
font-size: 0.78rem;
font-weight: 750;
}
.upload-action input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
.upload-action small {
color: var(--text-muted);
font-weight: 500;
}
.attachment-error {
margin: 8px 0 0;
color: var(--danger);
font-size: 0.78rem;
}
.card-actions {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
.card-actions .mat-mdc-button {
--mdc-text-button-label-text-color: var(--accent);
}
.card-actions .danger-action.mat-mdc-button {
--mdc-text-button-label-text-color: var(--danger);
}
@@ -0,0 +1,68 @@
<article class="tenant-card">
<div class="tenant-identity">
<span class="tenant-avatar"
>{{ tenant().firstName.charAt(0) }}{{ tenant().lastName.charAt(0) }}</span
>
<div>
<p class="property-label">Locataire</p>
<h2>{{ tenant().firstName }} {{ tenant().lastName }}</h2>
</div>
</div>
<dl class="contact-list">
<div>
<dt>E-mail</dt>
<dd>{{ tenant().email }}</dd>
</div>
<div>
<dt>Téléphone</dt>
<dd>{{ tenant().phone || 'Non renseigné' }}</dd>
</div>
<div>
<dt>Adresse</dt>
<dd>{{ tenant().address || 'Non renseignée' }}</dd>
</div>
</dl>
<div class="attachments">
<div class="section-title">
<strong>Pièces jointes</strong><span>{{ tenant().attachments?.length ?? 0 }}</span>
</div>
@for (attachment of tenant().attachments ?? []; track attachment.id) {
<button
mat-button
type="button"
class="attachment-row"
[attr.aria-label]="'Télécharger la pièce jointe ' + attachment.fileName"
(click)="attachmentDownloadRequested.emit({ tenantId: tenant().id!, attachment })"
>
<span aria-hidden="true">↧</span>{{ attachment.fileName }}
</button>
}
@if (tenant().id) {
@if (uploadError() || attachmentError()) {
<p class="attachment-error" role="alert">{{ uploadError() || attachmentError() }}</p>
}
<label class="upload-action"
><input
type="file"
[attr.aria-label]="
'Ajouter une pièce jointe pour ' + tenant().firstName + ' ' + tenant().lastName
"
(change)="selectFile($event)"
/>+ Ajouter une pièce jointe <small>· 250 Mo maximum</small></label
>
}
</div>
<div class="card-actions">
<button mat-button type="button" (click)="editRequested.emit(tenant())">Modifier</button>
@if (tenant().id) {
<button
mat-button
type="button"
class="danger-action"
(click)="deleteRequested.emit(tenant().id!)"
>
Supprimer
</button>
}
</div>
</article>
@@ -0,0 +1,24 @@
import { TestBed } from '@angular/core/testing';
import { TenantCard } from './tenant-card';
describe('TenantCard', () => {
it('rejects files larger than the upload limit before emitting', () => {
const fixture = TestBed.createComponent(TenantCard);
fixture.componentRef.setInput('tenant', {
id: 'tenant-1',
firstName: 'Camille',
lastName: 'Martin',
email: 'camille@example.com',
phone: '',
address: '',
});
const file = new File(['identity'], 'archive.zip');
Object.defineProperty(file, 'size', { value: 250 * 1024 * 1024 + 1 });
const input = document.createElement('input');
Object.defineProperty(input, 'files', { value: { item: () => file } });
fixture.componentInstance.selectFile({ target: input } as unknown as Event);
expect(fixture.componentInstance.uploadError()).toContain('250 Mo');
});
});
@@ -0,0 +1,37 @@
import { Component, input, output, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { Tenant, TenantAttachment } from '../../services/tenant.service';
export interface TenantAttachmentRequest {
tenantId: string;
attachment: TenantAttachment;
}
@Component({
selector: 'app-tenant-card',
imports: [MatButtonModule],
templateUrl: './tenant-card.html',
styleUrl: './tenant-card.css',
})
export class TenantCard {
readonly tenant = input.required<Tenant>();
readonly attachmentError = input('');
readonly editRequested = output<Tenant>();
readonly deleteRequested = output<string>();
readonly attachmentUploadRequested = output<{ tenantId: string; file: File }>();
readonly attachmentDownloadRequested = output<TenantAttachmentRequest>();
readonly uploadError = signal('');
selectFile(event: Event): void {
const input = event.target as HTMLInputElement;
const file = input.files?.item(0);
const tenantId = this.tenant().id;
if (file && file.size > 250 * 1024 * 1024) {
this.uploadError.set('Une pièce jointe ne peut pas dépasser 250 Mo.');
} else if (tenantId && file) {
this.uploadError.set('');
this.attachmentUploadRequested.emit({ tenantId, file });
}
input.value = '';
}
}