Refactor application structure to use WorkspaceService for state management
- Removed direct state management from components and centralized it in WorkspaceService. - Updated ApartmentsPage, TenantsPage, LeasesPage, and DashboardPage to utilize WorkspaceService for data handling. - Introduced SessionService to manage user session state and authentication. - Simplified component templates to reflect new service-based architecture. - Removed unused ConfigService and its tests. - Added lease.html template for lease document generation. - Created app.routes.spec.ts for route mapping tests.
This commit is contained in:
1 parent
be1f680a62
commit
82f339e209
33 files changed
+292
-929
No files matched your search
+8
-226
@@ -1,21 +1,9 @@
|
||||
import { Component, computed, signal, inject, OnInit, effect } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ApartmentService, Apartment } from './services/apartment.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { Component, computed, inject, OnInit } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { BrandMark } from './components/brand-mark';
|
||||
import { SignInPage } from './components/sign-in-page/sign-in-page';
|
||||
import {
|
||||
WorkspaceNavigation,
|
||||
WorkspaceTab,
|
||||
} from './components/workspace-navigation/workspace-navigation';
|
||||
import { DashboardPage } from './components/dashboard-page/dashboard-page';
|
||||
import { ApartmentsPage } from './components/apartments-page/apartments-page';
|
||||
import { ApartmentSaveRequest } from './components/apartments-page/apartments-page';
|
||||
import { SettingsPage } from './components/settings-page/settings-page';
|
||||
import { Tenant, TenantService } from './services/tenant.service';
|
||||
import { Lease, LeaseService } from './services/lease.service';
|
||||
import { TenantsPage, TenantSaveRequest } from './components/tenants-page/tenants-page';
|
||||
import { LeasesPage } from './components/leases-page/leases-page';
|
||||
import { WorkspaceNavigation } from './components/workspace-navigation/workspace-navigation';
|
||||
import { SessionService } from './services/session.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -23,81 +11,17 @@ import { LeasesPage } from './components/leases-page/leases-page';
|
||||
BrandMark,
|
||||
SignInPage,
|
||||
WorkspaceNavigation,
|
||||
DashboardPage,
|
||||
ApartmentsPage,
|
||||
TenantsPage,
|
||||
LeasesPage,
|
||||
SettingsPage,
|
||||
RouterOutlet,
|
||||
],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.css',
|
||||
})
|
||||
export class App implements OnInit {
|
||||
protected readonly title = signal('LMNP Organizer');
|
||||
readonly helloMessage = signal<string>('');
|
||||
|
||||
// Tab & CRUD States
|
||||
readonly activeTab = signal<WorkspaceTab>('dashboard');
|
||||
readonly apartments = signal<Apartment[]>([]);
|
||||
readonly tenants = signal<Tenant[]>([]);
|
||||
readonly leases = signal<Lease[]>([]);
|
||||
readonly tenantAttachmentError = signal('');
|
||||
private readonly _isLoggedIn = signal<boolean>(false);
|
||||
private readonly _username = signal<string>('');
|
||||
private readonly _email = signal<string>('');
|
||||
readonly userInitial = computed(() => this._username().charAt(0).toUpperCase());
|
||||
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly apartmentService = inject(ApartmentService);
|
||||
private readonly tenantService = inject(TenantService);
|
||||
private readonly leaseService = inject(LeaseService);
|
||||
protected readonly themeService = inject(ThemeService);
|
||||
readonly themePreference = this.themeService.preference;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (this.isLoggedIn) {
|
||||
this.loadApartments();
|
||||
this.loadTenants();
|
||||
this.loadLeases();
|
||||
}
|
||||
});
|
||||
}
|
||||
readonly session = inject(SessionService);
|
||||
readonly userInitial = computed(() => this.session.username().charAt(0).toUpperCase());
|
||||
|
||||
ngOnInit() {
|
||||
this.http
|
||||
.get<{ authenticated: boolean; username?: string; email?: string }>('/api/user')
|
||||
.subscribe({
|
||||
next: (user) => {
|
||||
if (user.authenticated) {
|
||||
this._isLoggedIn.set(true);
|
||||
this._username.set(user.username || 'User');
|
||||
this._email.set(user.email || '');
|
||||
} else {
|
||||
this._isLoggedIn.set(false);
|
||||
this._username.set('');
|
||||
this._email.set('');
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching user authentication status', err);
|
||||
this._isLoggedIn.set(false);
|
||||
this._username.set('');
|
||||
this._email.set('');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get isLoggedIn(): boolean {
|
||||
return this._isLoggedIn();
|
||||
}
|
||||
|
||||
get username(): string {
|
||||
return this._username();
|
||||
}
|
||||
|
||||
get email(): string {
|
||||
return this._email();
|
||||
this.session.refresh();
|
||||
}
|
||||
|
||||
login() {
|
||||
@@ -108,146 +32,4 @@ export class App implements OnInit {
|
||||
window.location.href = '/logout';
|
||||
}
|
||||
|
||||
callHelloApi() {
|
||||
this.http.get('/api/hello', { responseType: 'text' }).subscribe({
|
||||
next: (msg) => this.helloMessage.set(msg),
|
||||
error: (err) => {
|
||||
console.error(err);
|
||||
this.helloMessage.set('Failed to connect to backend: ' + err.statusText);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Apartment CRUD operations
|
||||
loadApartments() {
|
||||
this.apartmentService.getApartments().subscribe({
|
||||
next: (data) => this.apartments.set(data),
|
||||
error: (err) => console.error('Error loading apartments', err),
|
||||
});
|
||||
}
|
||||
|
||||
saveApartment({ apartment, apartmentId }: ApartmentSaveRequest): void {
|
||||
if (apartmentId) {
|
||||
this.apartmentService.updateApartment(apartmentId, apartment).subscribe({
|
||||
next: () => {
|
||||
this.loadApartments();
|
||||
},
|
||||
error: (err) => console.error('Error updating apartment', err),
|
||||
});
|
||||
} else {
|
||||
this.apartmentService.createApartment(apartment).subscribe({
|
||||
next: () => {
|
||||
this.loadApartments();
|
||||
},
|
||||
error: (err) => console.error('Error creating apartment', err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deleteApartment(id: string) {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer cet appartement ?')) {
|
||||
this.apartmentService.deleteApartment(id).subscribe({
|
||||
next: () => this.loadApartments(),
|
||||
error: (err) => console.error('Error deleting apartment', err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadTenants(): void {
|
||||
this.tenantService.getTenants().subscribe({
|
||||
next: (data) => this.tenants.set(data),
|
||||
error: (err) => console.error('Error loading tenants', err),
|
||||
});
|
||||
}
|
||||
|
||||
saveTenant({ tenant, tenantId }: TenantSaveRequest): void {
|
||||
const request = tenantId
|
||||
? this.tenantService.updateTenant(tenantId, tenant)
|
||||
: this.tenantService.createTenant(tenant);
|
||||
request.subscribe({
|
||||
next: () => this.loadTenants(),
|
||||
error: (err) => console.error('Error saving tenant', err),
|
||||
});
|
||||
}
|
||||
|
||||
deleteTenant(id: string): void {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer ce locataire ?')) {
|
||||
this.tenantService.deleteTenant(id).subscribe({
|
||||
next: () => this.loadTenants(),
|
||||
error: (err) => console.error('Error deleting tenant', err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uploadTenantAttachment(request: { tenantId: string; file: File }): void {
|
||||
this.tenantAttachmentError.set('');
|
||||
this.tenantService.uploadAttachment(request.tenantId, request.file).subscribe({
|
||||
next: () => this.loadTenants(),
|
||||
error: (error: unknown) => {
|
||||
console.error('Error uploading tenant attachment', error);
|
||||
this.tenantAttachmentError.set(this.errorMessage(error, 'Impossible d’envoyer cette pièce jointe.'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
downloadTenantAttachment(request: {
|
||||
tenantId: string;
|
||||
attachment: { id: string; fileName: string };
|
||||
}): void {
|
||||
this.tenantService.downloadAttachment(request.tenantId, request.attachment.id).subscribe({
|
||||
next: (blob) => this.saveBlob(blob, request.attachment.fileName),
|
||||
error: (err) => console.error('Error downloading tenant attachment', err),
|
||||
});
|
||||
}
|
||||
|
||||
loadLeases(): void {
|
||||
this.leaseService.getLeases().subscribe({
|
||||
next: (data) => this.leases.set(data),
|
||||
error: (err) => console.error('Error loading leases', err),
|
||||
});
|
||||
}
|
||||
|
||||
saveLease(lease: Lease): void {
|
||||
this.leaseService.createLease(lease).subscribe({
|
||||
next: () => this.loadLeases(),
|
||||
error: (err) => console.error('Error saving lease', err),
|
||||
});
|
||||
}
|
||||
|
||||
deleteLease(id: string): void {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer ce bail ?')) {
|
||||
this.leaseService.deleteLease(id).subscribe({
|
||||
next: () => this.loadLeases(),
|
||||
error: (err) => console.error('Error deleting lease', err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
downloadLeasePdf(lease: Lease): void {
|
||||
if (!lease.id) return;
|
||||
this.leaseService.downloadPdf(lease.id).subscribe({
|
||||
next: (blob) => this.saveBlob(blob, `bail-${lease.id}.pdf`),
|
||||
error: (err) => console.error('Error downloading lease PDF', err),
|
||||
});
|
||||
}
|
||||
|
||||
private saveBlob(blob: Blob, fileName: string): void {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
private errorMessage(error: unknown, fallback: string): string {
|
||||
if (typeof error !== 'object' || error === null || !('error' in error)) {
|
||||
return fallback;
|
||||
}
|
||||
const response = error.error;
|
||||
if (typeof response !== 'object' || response === null || !('message' in response)) {
|
||||
return fallback;
|
||||
}
|
||||
return typeof response.message === 'string' ? response.message : fallback;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user