diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 15b6eba..b6a8e3d 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -8,6 +8,7 @@ import {ReservationComponent} from "./landlord/reservation/reservation.component
import { EmailVerifiedComponent } from './email-verified/email-verified.component';
import { PaymentSuccessComponent } from './tenant/payment-success/payment-success.component';
import { PaymentCancelComponent } from './tenant/payment-cancel/payment-cancel.component';
+import { LandlordDashboardComponent } from './landlord/landlord-dashboard/landlord-dashboard.component';
export const routes: Routes = [
{
@@ -49,5 +50,11 @@ export const routes: Routes = [
{
path: 'payment-cancel',
component: PaymentCancelComponent
- }
+ },
+ {
+ path: 'landlord/dashboard',
+ component: LandlordDashboardComponent,
+ canActivate: [authorityRouteAccess],
+ data: { authorities: ['ROLE_LANDLORD'] }
+},
];
\ No newline at end of file
diff --git a/src/app/landlord/landlord-dashboard/landlord-dashboard.component.html b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.html
new file mode 100644
index 0000000..fa04a7a
--- /dev/null
+++ b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
Chargement du dashboard...
+
+
+
+
+
❌ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
🏠
+
+
{{ dashboard.totalProperties }}
+
Propriétés
+
+
+
+
+
📅
+
+
{{ dashboard.totalActiveBookings }}
+
Réservations Actives
+
+
+
+
+
+
+
+
💳 Statut des Paiements
+
+
+
+
⏳
+
+
{{ dashboard.paymentStatus.totalPendingPayments }}
+
En attente
+
{{ dashboard.paymentStatus.pendingAmount }} MAD
+
+
+
+
+
✅
+
+
{{ dashboard.paymentStatus.totalCompletedPayments }}
+
Complétés
+
{{ dashboard.paymentStatus.completedAmount }} MAD
+
+
+
+
+
❌
+
+
{{ dashboard.paymentStatus.totalFailedPayments }}
+
Échoués
+
+
+
+
+
+
+
+
+
🏡 Mes Propriétés
+
+
+
+
0">
+
+
+
+
+
![]()
+
+
+
+
{{ property.title }}
+
+
+
+ 📅
+ {{ property.totalBookings }} réservations
+
+
+ ✅
+ {{ property.availableDays }} jours disponibles
+
+
+
+
+ 🗓️ Prochain check-in : {{ property.nextCheckIn | date:'dd/MM/yyyy' }}
+
+
+
+ {{ getStatusLabel(property.status) }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/landlord/landlord-dashboard/landlord-dashboard.component.scss b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.scss
new file mode 100644
index 0000000..9950b4f
--- /dev/null
+++ b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.scss
@@ -0,0 +1,411 @@
+.dashboard-container {
+ padding: 24px;
+ background: #f5f7fa;
+ min-height: 100vh;
+
+ // Header
+ .dashboard-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: white;
+ padding: 20px 30px;
+ border-radius: 12px;
+ margin-bottom: 24px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+ h1 {
+ margin: 0;
+ font-size: 28px;
+ color: #2c3e50;
+ font-weight: 600;
+ }
+
+ .btn-refresh {
+ padding: 12px 24px;
+ background: #ff5a5f;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: 500;
+ transition: all 0.3s;
+
+ &:hover {
+ background: #e04e53;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(255, 90, 95, 0.3);
+ }
+ }
+ }
+
+ // Loading
+ .loading {
+ text-align: center;
+ padding: 100px 20px;
+
+ .spinner {
+ width: 60px;
+ height: 60px;
+ border: 5px solid #f3f3f3;
+ border-top: 5px solid #ff5a5f;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 0 auto 20px;
+ }
+
+ p {
+ font-size: 18px;
+ color: #666;
+ }
+ }
+
+ @keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ // Error
+ .error {
+ text-align: center;
+ padding: 60px 20px;
+ background: white;
+ border-radius: 12px;
+
+ p {
+ font-size: 18px;
+ color: #e74c3c;
+ margin-bottom: 20px;
+ }
+
+ button {
+ padding: 12px 30px;
+ background: #ff5a5f;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 16px;
+ transition: background 0.3s;
+
+ &:hover {
+ background: #e04e53;
+ }
+ }
+ }
+
+ // Global Stats
+ .global-stats {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 20px;
+ margin-bottom: 30px;
+
+ .stat-card {
+ background: white;
+ border-radius: 12px;
+ padding: 28px;
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ transition: all 0.3s;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
+ }
+
+ .icon {
+ font-size: 48px;
+ }
+
+ .info {
+ h2 {
+ margin: 0 0 6px 0;
+ font-size: 32px;
+ font-weight: bold;
+ color: #2c3e50;
+ }
+
+ p {
+ margin: 0;
+ font-size: 15px;
+ color: #7f8c8d;
+ }
+ }
+
+ &.revenue {
+ border-left: 5px solid #9b59b6;
+ }
+
+ &.properties {
+ border-left: 5px solid #e74c3c;
+ }
+
+ &.bookings {
+ border-left: 5px solid #3498db;
+ }
+ }
+ }
+
+ // Section
+ .section {
+ background: white;
+ border-radius: 12px;
+ padding: 30px;
+ margin-bottom: 30px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+ h2 {
+ margin: 0 0 24px 0;
+ color: #2c3e50;
+ font-size: 22px;
+ font-weight: 600;
+ }
+ }
+
+ // Payment Grid
+ .payment-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 20px;
+
+ .payment-card {
+ border-radius: 12px;
+ padding: 24px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ transition: transform 0.3s;
+
+ &:hover {
+ transform: scale(1.02);
+ }
+
+ .payment-icon {
+ font-size: 40px;
+ }
+
+ .payment-info {
+ flex: 1;
+
+ h3 {
+ margin: 0 0 6px 0;
+ font-size: 36px;
+ font-weight: bold;
+ color: #2c3e50;
+ }
+
+ p {
+ margin: 0 0 8px 0;
+ font-size: 15px;
+ color: #555;
+ }
+
+ .amount {
+ font-size: 16px;
+ font-weight: 600;
+ color: #27ae60;
+ }
+ }
+
+ &.pending {
+ background: linear-gradient(135deg, #fff9e6 0%, #ffe69c 100%);
+ }
+
+ &.completed {
+ background: linear-gradient(135deg, #eafaf1 0%, #c3e6cb 100%);
+ }
+
+ &.failed {
+ background: linear-gradient(135deg, #fdf2f2 0%, #f5c6cb 100%);
+ }
+ }
+ }
+
+ // Properties Grid
+ .properties-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 24px;
+
+ .property-card {
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ overflow: hidden;
+ transition: all 0.3s;
+ background: white;
+
+ &:hover {
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
+ transform: translateY(-6px);
+ }
+
+ .property-image {
+ position: relative;
+ height: 200px;
+ overflow: hidden;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 0.3s;
+ }
+
+ &:hover img {
+ transform: scale(1.05);
+ }
+
+ .occupancy-badge {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-weight: bold;
+ font-size: 14px;
+ background: white;
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
+
+ &.high {
+ color: #27ae60;
+ border: 2px solid #27ae60;
+ }
+
+ &.medium {
+ color: #f39c12;
+ border: 2px solid #f39c12;
+ }
+
+ &.low {
+ color: #e74c3c;
+ border: 2px solid #e74c3c;
+ }
+ }
+ }
+
+ .property-details {
+ padding: 20px;
+
+ h3 {
+ margin: 0 0 16px 0;
+ font-size: 19px;
+ color: #2c3e50;
+ font-weight: 600;
+ }
+
+ .property-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 16px;
+
+ .stat {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #666;
+
+ .icon {
+ font-size: 16px;
+ }
+ }
+ }
+
+ .next-checkin {
+ background: #e8f5e9;
+ padding: 12px;
+ border-radius: 8px;
+ margin-bottom: 16px;
+ font-size: 14px;
+ color: #27ae60;
+ font-weight: 500;
+ }
+
+ .status-badge {
+ padding: 10px 16px;
+ border-radius: 20px;
+ font-size: 13px;
+ font-weight: 600;
+ text-align: center;
+
+ &.status-available {
+ background: #d4edda;
+ color: #155724;
+ }
+
+ &.status-occupied {
+ background: #f8d7da;
+ color: #721c24;
+ }
+
+ &.status-booked-soon {
+ background: #fff3cd;
+ color: #856404;
+ }
+ }
+ }
+ }
+ }
+
+ // Empty State
+ .empty {
+ text-align: center;
+ padding: 60px 20px;
+
+ p {
+ font-size: 18px;
+ color: #999;
+ margin-bottom: 20px;
+ }
+
+ a {
+ display: inline-block;
+ padding: 12px 30px;
+ background: #ff5a5f;
+ color: white;
+ border-radius: 8px;
+ text-decoration: none;
+ transition: background 0.3s;
+
+ &:hover {
+ background: #e04e53;
+ }
+ }
+ }
+}
+
+// Responsive
+@media (max-width: 768px) {
+ .dashboard-container {
+ padding: 12px;
+
+ .dashboard-header {
+ flex-direction: column;
+ gap: 16px;
+ padding: 16px;
+
+ h1 {
+ font-size: 22px;
+ }
+
+ .btn-refresh {
+ width: 100%;
+ }
+ }
+
+ .global-stats,
+ .payment-grid,
+ .properties-grid {
+ grid-template-columns: 1fr;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/landlord/landlord-dashboard/landlord-dashboard.component.spec.ts b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.spec.ts
new file mode 100644
index 0000000..eae8724
--- /dev/null
+++ b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LandlordDashboardComponent } from './landlord-dashboard.component';
+
+describe('LandlordDashboardComponent', () => {
+ let component: LandlordDashboardComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LandlordDashboardComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LandlordDashboardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/landlord/landlord-dashboard/landlord-dashboard.component.ts b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.ts
new file mode 100644
index 0000000..c385a67
--- /dev/null
+++ b/src/app/landlord/landlord-dashboard/landlord-dashboard.component.ts
@@ -0,0 +1,61 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { LandlordDashboardService } from '../service/landlord-dashboard.service';
+import { LandlordDashboardDTO } from '../model/landlord-dashboard.model';
+
+@Component({
+ selector: 'app-landlord-dashboard',
+ standalone: true,
+ imports: [CommonModule, RouterModule],
+ templateUrl: './landlord-dashboard.component.html',
+ styleUrls: ['./landlord-dashboard.component.scss']
+})
+export class LandlordDashboardComponent implements OnInit {
+
+ dashboard: LandlordDashboardDTO | null = null;
+ loading = true;
+ error: string | null = null;
+
+ constructor(private landlordDashboardService: LandlordDashboardService) {}
+
+ ngOnInit(): void {
+ this.loadDashboard();
+ }
+
+ loadDashboard(): void {
+ this.loading = true;
+ this.error = null;
+
+ this.landlordDashboardService.getDashboard().subscribe({
+ next: (data) => {
+ this.dashboard = data;
+ this.loading = false;
+ console.log('Dashboard loaded:', data);
+ },
+ error: (err) => {
+ this.error = 'Erreur lors du chargement du dashboard';
+ this.loading = false;
+ console.error('Error:', err);
+ }
+ });
+ }
+
+ getStatusClass(status: string): string {
+ switch (status) {
+ case 'AVAILABLE': return 'status-available';
+ case 'OCCUPIED': return 'status-occupied';
+ case 'BOOKED_SOON': return 'status-booked-soon';
+ default: return '';
+ }
+ }
+
+ getStatusLabel(status: string): string {
+ switch (status) {
+ case 'AVAILABLE': return '✅ Disponible';
+ case 'OCCUPIED': return '🔴 Occupé';
+ case 'BOOKED_SOON': return '🟡 Réservé bientôt';
+ default: return status;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/landlord/model/landlord-dashboard.model.ts b/src/app/landlord/model/landlord-dashboard.model.ts
new file mode 100644
index 0000000..5af8626
--- /dev/null
+++ b/src/app/landlord/model/landlord-dashboard.model.ts
@@ -0,0 +1,27 @@
+export interface PaymentStatusDTO {
+ totalPendingPayments: number;
+ totalCompletedPayments: number;
+ totalFailedPayments: number;
+ pendingAmount: number;
+ completedAmount: number;
+ totalRevenue: number;
+}
+
+export interface PropertyAvailabilityDTO {
+ propertyPublicId: string;
+ title: string;
+ coverUrl: string;
+ totalBookings: number;
+ occupancyRate: number;
+ availableDays: number;
+ nextCheckIn: string | null;
+ status: 'AVAILABLE' | 'OCCUPIED' | 'BOOKED_SOON';
+}
+
+export interface LandlordDashboardDTO {
+ paymentStatus: PaymentStatusDTO;
+ properties: PropertyAvailabilityDTO[];
+ totalRevenue: number;
+ totalProperties: number;
+ totalActiveBookings: number;
+}
\ No newline at end of file
diff --git a/src/app/landlord/model/listing-vo.model.ts b/src/app/landlord/model/listing-vo.model.ts
index c945c56..def8f17 100644
--- a/src/app/landlord/model/listing-vo.model.ts
+++ b/src/app/landlord/model/listing-vo.model.ts
@@ -25,3 +25,15 @@ export interface DescriptionVO {
export interface PriceVO {
value: number
}
+export interface ListingVO {
+ publicId: string;
+ title: string;
+ description: string;
+ location: string;
+ price: number;
+ category: string;
+ bedrooms: number;
+ guests: number;
+ beds: number;
+ bathrooms: number;
+}
diff --git a/src/app/landlord/model/picture.model.ts b/src/app/landlord/model/picture.model.ts
index 29e3718..6edbeea 100644
--- a/src/app/landlord/model/picture.model.ts
+++ b/src/app/landlord/model/picture.model.ts
@@ -2,3 +2,8 @@ export interface NewListingPicture {
file: File,
urlDisplay: string
}
+export interface Picture {
+ file: string; // base64 encoded or URL
+ fileContentType: string;
+ isCover: boolean;
+}
\ No newline at end of file
diff --git a/src/app/landlord/service/landlord-dashboard.service.spec.ts b/src/app/landlord/service/landlord-dashboard.service.spec.ts
new file mode 100644
index 0000000..0ec6408
--- /dev/null
+++ b/src/app/landlord/service/landlord-dashboard.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LandlordDashboardService } from './landlord-dashboard.service';
+
+describe('LandlordDashboardService', () => {
+ let service: LandlordDashboardService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(LandlordDashboardService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/landlord/service/landlord-dashboard.service.ts b/src/app/landlord/service/landlord-dashboard.service.ts
new file mode 100644
index 0000000..b31fa2c
--- /dev/null
+++ b/src/app/landlord/service/landlord-dashboard.service.ts
@@ -0,0 +1,18 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { LandlordDashboardDTO } from '../model/landlord-dashboard.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LandlordDashboardService {
+
+ private apiUrl = 'http://localhost:8080/api/landlord';
+
+ constructor(private http: HttpClient) {}
+
+ getDashboard(): Observable {
+ return this.http.get(`${this.apiUrl}/dashboard`);
+ }
+}
\ No newline at end of file
diff --git a/src/app/landlord/service/landlord.service.ts b/src/app/landlord/service/landlord.service.ts
new file mode 100644
index 0000000..b31fa2c
--- /dev/null
+++ b/src/app/landlord/service/landlord.service.ts
@@ -0,0 +1,18 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { LandlordDashboardDTO } from '../model/landlord-dashboard.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LandlordDashboardService {
+
+ private apiUrl = 'http://localhost:8080/api/landlord';
+
+ constructor(private http: HttpClient) {}
+
+ getDashboard(): Observable {
+ return this.http.get(`${this.apiUrl}/dashboard`);
+ }
+}
\ No newline at end of file
diff --git a/src/app/layout/navbar/navbar.component.ts b/src/app/layout/navbar/navbar.component.ts
index f9c7253..12c3619 100644
--- a/src/app/layout/navbar/navbar.component.ts
+++ b/src/app/layout/navbar/navbar.component.ts
@@ -89,51 +89,58 @@ export class NavbarComponent implements OnInit {
}
private fetchMenu(): MenuItem[] {
- if (this.authService.isAuthenticated()) {
- return [
- {
- label: 'Create',
- command: () => this.openNewListing(),
- visible: this.hasToBeLandlord(),
- },
- {
- label: 'My properties',
- routerLink: 'landlord/properties',
- visible: this.hasToBeLandlord(),
- },
- {
- label: 'My booking',
- routerLink: 'booking',
- },
- {
- label: 'My reservation',
- routerLink: 'landlord/reservation',
- visible: this.hasToBeLandlord(),
- },
- {
- label: 'Become a LandLord',
- command: () => this.becomeLandlord(),
- visible: !this.hasToBeLandlord(),
- },
- {
- label: 'Log out',
- command: this.logout,
- },
- ];
- } else {
- return [
- {
- label: 'Sign up',
- styleClass: 'font-bold',
- command: () => this.signUp(),
- },
- {
- label: 'Log in',
- command: this.login,
- },
- ];
- }
+ if (this.authService.isAuthenticated()) {
+ return [
+ {
+ label: 'Create',
+ command: () => this.openNewListing(),
+ visible: this.hasToBeLandlord(),
+ },
+ {
+ label: 'My properties',
+ routerLink: ['/landlord/properties'],
+ visible: this.hasToBeLandlord(),
+ },
+ {
+ label: 'My Dashboard',
+ routerLink: ['/landlord/dashboard'],
+ visible: this.hasToBeLandlord(),
+ },
+ {
+ label: 'My booking',
+ routerLink: ['/booking'],
+ },
+ {
+ label: 'My reservation',
+ routerLink: ['/landlord/reservation'],
+ visible: this.hasToBeLandlord(),
+ },
+
+ {
+ label: 'Become a LandLord',
+ command: () => this.becomeLandlord(),
+ visible: !this.hasToBeLandlord(),
+ },
+ {
+ label: 'Log out',
+ command: this.logout,
+ },
+ ];
+ } else {
+ return [
+ {
+ label: 'Sign up',
+ styleClass: 'font-bold',
+ command: () => this.signUp(),
+ },
+ {
+ label: 'Log in',
+ command: this.login,
+ },
+ ];
}
+}
+
hasToBeLandlord(): boolean {
return this.authService.hasAnyAuthority('ROLE_LANDLORD');