Implement Landlord and Apartment CRUD features with secure endpoints, coverage tests, and responsive UI

This commit is contained in:
nessar committed 2026-08-17 14:18:14 +02:00
1 parent 60f0377f97
commit 62a0bd5db3
18 files changed
+1419 -31

No files matched your search

+155 -26
View File
@@ -37,35 +37,164 @@
</div>
} @else {
<div class="auth-state unlocked">
<div class="user-profile">
<div class="avatar">
{{ username.charAt(0).toUpperCase() }}
</div>
<div class="user-details">
<h3>{{ username }}</h3>
<p class="user-email">{{ email }}</p>
<span class="badge badge-success">Connecté</span>
</div>
</div>
<!-- Multi-Tab Navigation Menu -->
<nav class="tab-nav">
<button (click)="activeTab.set('dashboard')" [class.active]="activeTab() === 'dashboard'" class="tab-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
<rect width="7" height="9" x="3" y="3" rx="1"/>
<rect width="7" height="5" x="14" y="3" rx="1"/>
<rect width="7" height="9" x="14" y="12" rx="1"/>
<rect width="7" height="5" x="3" y="16" rx="1"/>
</svg>
<span>Tableau de Bord</span>
</button>
<button (click)="activeTab.set('apartments')" [class.active]="activeTab() === 'apartments'" class="tab-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
<path d="M2 22h20"/>
<path d="M4 22V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v18"/>
<path d="M12 18h.01"/>
<path d="M8 6h2"/>
<path d="M14 6h2"/>
<path d="M8 10h2"/>
<path d="M14 10h2"/>
<path d="M8 14h2"/>
<path d="M14 14h2"/>
</svg>
<span>Appartements</span>
</button>
</nav>
<div class="api-demo-section">
<h4>Vérification API Sécurisée</h4>
<p>Testez la transmission et la validation du jeton JWT auprès du backend Java 25.</p>
<div class="api-actions">
<button (click)="callHelloApi()" class="btn btn-secondary">
Appeler /api/hello
</button>
</div>
@if (helloMessage()) {
<div class="api-response-wrapper">
<span class="response-label">Réponse du backend :</span>
<code class="api-response-code">{{ helloMessage() }}</code>
<!-- Tab Content: Dashboard -->
@if (activeTab() === 'dashboard') {
<div class="tab-pane">
<div class="user-profile">
<div class="avatar">
{{ username.charAt(0).toUpperCase() }}
</div>
<div class="user-details">
<h3>{{ username }}</h3>
<p class="user-email">{{ email }}</p>
<span class="badge badge-success">Bailleur Connecté</span>
</div>
</div>
}
</div>
<button (click)="logout()" class="btn btn-outline">
<div class="api-demo-section">
<h4>Vérification API Sécurisée</h4>
<p>Testez la transmission et la validation du jeton JWT auprès du backend Java 25.</p>
<div class="api-actions">
<button (click)="callHelloApi()" class="btn btn-secondary">
Appeler /api/hello
</button>
</div>
@if (helloMessage()) {
<div class="api-response-wrapper">
<span class="response-label">Réponse du backend :</span>
<code class="api-response-code">{{ helloMessage() }}</code>
</div>
}
</div>
</div>
}
<!-- Tab Content: Apartments -->
@if (activeTab() === 'apartments') {
<div class="tab-pane">
@if (!showForm()) {
<div class="section-header">
<h2>Mon Portefeuille de Biens</h2>
<button (click)="openAddForm()" class="btn btn-primary btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="btn-icon">
<path d="M5 12h14"/>
<path d="M12 5v14"/>
</svg>
<span>Ajouter un appartement</span>
</button>
</div>
@if (apartments().length === 0) {
<div class="empty-state">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="empty-icon">
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</svg>
<h3>Aucun appartement enregistré</h3>
<p>Commencez par ajouter votre premier bien immobilier LMNP à votre portefeuille.</p>
</div>
} @else {
<div class="apartments-grid">
@for (apt of apartments(); track apt.id) {
<div class="apartment-card">
<div class="apt-info">
<h3>{{ apt.name }}</h3>
<p class="apt-address">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="inline-icon">
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
{{ apt.address }}
</p>
<p class="apt-price">{{ apt.purchasePrice.toLocaleString('fr-FR') }} &euro;</p>
@if (apt.description) {
<p class="apt-desc">{{ apt.description }}</p>
}
</div>
<div class="apt-actions">
<button (click)="openEditForm(apt)" class="btn btn-outline btn-xs btn-warning">
Modifier
</button>
@if (apt.id) {
<button (click)="deleteApartment(apt.id)" class="btn btn-outline btn-xs btn-danger">
Supprimer
</button>
}
</div>
</div>
}
</div>
}
} @else {
<div class="form-container">
<h2>{{ editingApartment() ? 'Modifier l\'appartement' : 'Ajouter un appartement' }}</h2>
@if (formError) {
<div class="alert alert-error">
{{ formError }}
</div>
}
<form (submit)="saveApartment(); $event.preventDefault()" class="crud-form">
<div class="form-group">
<label for="aptName">Nom de l'appartement *</label>
<input type="text" id="aptName" name="name" [(ngModel)]="formName" placeholder="Ex. T2 Cosy Paris 15" required>
</div>
<div class="form-group">
<label for="aptAddress">Adresse complète *</label>
<input type="text" id="aptAddress" name="address" [(ngModel)]="formAddress" placeholder="Ex. 45 rue des Entrepreneurs, 75015 Paris" required>
</div>
<div class="form-group">
<label for="aptPrice">Prix d'acquisition (&euro;) *</label>
<input type="number" id="aptPrice" name="purchasePrice" [(ngModel)]="formPurchasePrice" min="1" placeholder="Ex. 180000" required>
</div>
<div class="form-group">
<label for="aptDesc">Description / Notes</label>
<textarea id="aptDesc" name="description" [(ngModel)]="formDescription" rows="3" placeholder="Ex. T2 en rez-de-chaussée, meublé, cuisine équipée."></textarea>
</div>
<div class="form-actions">
<button type="button" (click)="closeForm()" class="btn btn-outline">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
}
</div>
}
<button (click)="logout()" class="btn btn-outline btn-logout">
Se déconnecter
</button>
</div>