Implement Landlord and Apartment CRUD features with secure endpoints, coverage tests, and responsive UI
This commit is contained in:
1 parent
60f0377f97
commit
62a0bd5db3
18 files changed
+1419
-31
No files matched your search
+155
-26
@@ -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') }} €</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 (€) *</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>
|
||||
|
||||
Reference in new issue
Block a user