chore: add husky pre-commit hook and lint-staged configuration

- Created a pre-commit hook in .husky to run lint-staged.
- Added .lintstagedrc.mjs to format frontend files with Prettier and apply Spotless to backend Java files.
- Introduced .prettierignore to exclude specific directories from formatting.
This commit is contained in:
nessar committed 2026-08-17 14:18:16 +02:00
1 parent 5d2d85aa8c
commit 878da155bd
564 files changed
+61467 -47179

No files matched your search

@@ -1,18 +1,18 @@
:host {
display: block;
display: block;
}
.tenant-grid {
display: grid;
gap: 16px;
display: grid;
gap: 16px;
}
@media (min-width: 700px) {
.tenant-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.tenant-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
}
@media (min-width: 1180px) {
.tenant-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.tenant-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@@ -1,96 +1,124 @@
@if (!showForm()) {
<section class="page-section">
<header class="page-heading">
<div>
<p class="eyebrow">Dossiers</p>
<h1>Mes locataires</h1>
<p>Contacts, pièces jointes et accès rapide aux baux associés.</p>
</div>
<button mat-flat-button type="button" class="primary-action" (click)="openAddForm()">
<span aria-hidden="true">+</span> Ajouter un locataire
</button>
</header>
<div class="content-region" [attr.aria-busy]="workspace.tenantLoadState().loading">
@if (!workspace.tenantLoadState().loaded && workspace.tenantLoadState().loading) {
<div class="tenant-grid" aria-label="Chargement des locataires">
@for (placeholder of [1, 2, 3]; track placeholder) {
<article class="skeleton-card" aria-hidden="true">
<span class="skeleton-line skeleton-line-short"></span>
<span class="skeleton-line skeleton-line-title"></span>
<span class="skeleton-line"></span>
<span class="skeleton-line skeleton-line-medium"></span>
</article>
}
</div>
} @else if (!workspace.tenantLoadState().loaded && workspace.tenantLoadState().error) {
<article class="load-error" role="alert">
<h2>Chargement impossible</h2>
<p>{{ workspace.tenantLoadState().error }}</p>
<button mat-stroked-button type="button" (click)="workspace.loadTenants()">
Réessayer
</button>
</article>
} @else {
@if (workspace.tenantLoadState().loading) {
<mat-progress-bar mode="indeterminate" aria-label="Actualisation des locataires" />
}
@if (workspace.tenantLoadState().error) {
<div class="refresh-error" role="alert">
<p>{{ workspace.tenantLoadState().error }}</p>
<button mat-button type="button" (click)="workspace.loadTenants()">Réessayer</button>
</div>
}
@if (workspace.tenants().length === 0) {
<article class="empty-state">
<p class="eyebrow">Premier dossier</p>
<h2>Aucun locataire enregistré</h2>
<p>Centralisez ici les coordonnées et pièces jointes utiles de vos locataires.</p>
<button mat-stroked-button type="button" (click)="openAddForm()">
Ajouter un locataire
</button>
</article>
} @else {
<div class="tenant-grid">
@for (tenant of workspace.tenants(); track tenant.id) {
<app-tenant-card
[tenant]="tenant"
[attachmentError]="
tenant.id ? (workspace.tenantAttachmentErrors()[tenant.id] ?? '') : ''
"
[uploading]="tenant.id ? workspace.uploadingTenantIds().has(tenant.id) : false"
[uploadProgress]="
tenant.id ? (workspace.tenantUploadProgress()[tenant.id] ?? null) : null
"
[deleting]="!!tenant.id && workspace.deletingTenantIds().has(tenant.id)"
[deleteError]="tenant.id ? (workspace.tenantDeleteErrors()[tenant.id] ?? '') : ''"
[deletingAttachmentIds]="workspace.deletingAttachmentIds()"
[attachmentDeleteErrors]="workspace.attachmentDeleteErrors()"
[downloadingAttachmentIds]="workspace.downloadingAttachmentIds()"
[attachmentDownloadErrors]="workspace.attachmentDownloadErrors()"
(editRequested)="openEditForm($event)"
(deleteRequested)="workspace.deleteTenant($event)"
(attachmentUploadRequested)="
workspace.uploadTenantAttachment($event.tenantId, $event.file)
"
(attachmentDownloadRequested)="
workspace.downloadTenantAttachment($event.tenantId, $event.attachment)
"
(attachmentDeleteRequested)="
workspace.deleteTenantAttachment($event.tenantId, $event.attachment)
"
/>
}
</div>
}
}
</div>
</section>
<section class="page-section">
<header class="page-heading">
<div>
<p class="eyebrow">Dossiers</p>
<h1>Mes locataires</h1>
<p>Contacts, pièces jointes et accès rapide aux baux associés.</p>
</div>
<button mat-flat-button type="button" class="primary-action" (click)="openAddForm()">
<span aria-hidden="true">+</span> Ajouter un locataire
</button>
</header>
<div class="content-region" [attr.aria-busy]="workspace.tenantLoadState().loading">
@if (!workspace.tenantLoadState().loaded && workspace.tenantLoadState().loading) {
<div class="tenant-grid" aria-label="Chargement des locataires">
@for (placeholder of [1, 2, 3]; track placeholder) {
<article class="skeleton-card" aria-hidden="true">
<span class="skeleton-line skeleton-line-short"></span>
<span class="skeleton-line skeleton-line-title"></span>
<span class="skeleton-line"></span>
<span class="skeleton-line skeleton-line-medium"></span>
</article>
}
</div>
} @else if (!workspace.tenantLoadState().loaded && workspace.tenantLoadState().error) {
<article class="load-error" role="alert">
<h2>Chargement impossible</h2>
<p>{{ workspace.tenantLoadState().error }}</p>
<button mat-stroked-button type="button" (click)="workspace.loadTenants()">
Réessayer
</button>
</article>
} @else {
@if (workspace.tenantLoadState().loading) {
<mat-progress-bar
mode="indeterminate"
aria-label="Actualisation des locataires"
/>
}
@if (workspace.tenantLoadState().error) {
<div class="refresh-error" role="alert">
<p>{{ workspace.tenantLoadState().error }}</p>
<button mat-button type="button" (click)="workspace.loadTenants()">
Réessayer
</button>
</div>
}
@if (workspace.tenants().length === 0) {
<article class="empty-state">
<p class="eyebrow">Premier dossier</p>
<h2>Aucun locataire enregistré</h2>
<p>
Centralisez ici les coordonnées et pièces jointes utiles de vos
locataires.
</p>
<button mat-stroked-button type="button" (click)="openAddForm()">
Ajouter un locataire
</button>
</article>
} @else {
<div class="tenant-grid">
@for (tenant of workspace.tenants(); track tenant.id) {
<app-tenant-card
[tenant]="tenant"
[attachmentError]="
tenant.id
? (workspace.tenantAttachmentErrors()[tenant.id] ?? '')
: ''
"
[uploading]="
tenant.id
? workspace.uploadingTenantIds().has(tenant.id)
: false
"
[uploadProgress]="
tenant.id
? (workspace.tenantUploadProgress()[tenant.id] ?? null)
: null
"
[deleting]="
!!tenant.id && workspace.deletingTenantIds().has(tenant.id)
"
[deleteError]="
tenant.id
? (workspace.tenantDeleteErrors()[tenant.id] ?? '')
: ''
"
[deletingAttachmentIds]="workspace.deletingAttachmentIds()"
[attachmentDeleteErrors]="workspace.attachmentDeleteErrors()"
[downloadingAttachmentIds]="workspace.downloadingAttachmentIds()"
[attachmentDownloadErrors]="workspace.attachmentDownloadErrors()"
(editRequested)="openEditForm($event)"
(deleteRequested)="workspace.deleteTenant($event)"
(attachmentUploadRequested)="
workspace.uploadTenantAttachment($event.tenantId, $event.file)
"
(attachmentDownloadRequested)="
workspace.downloadTenantAttachment(
$event.tenantId,
$event.attachment
)
"
(attachmentDeleteRequested)="
workspace.deleteTenantAttachment(
$event.tenantId,
$event.attachment
)
"
/>
}
</div>
}
}
</div>
</section>
} @else {
<app-tenant-form
[tenant]="editingTenant()"
[saving]="workspace.tenantSaving(editingTenant()?.id)"
[saveError]="workspace.tenantSaveError(editingTenant()?.id)"
(cancelled)="closeForm()"
(submitted)="saveTenant($event)"
/>
<app-tenant-form
[tenant]="editingTenant()"
[saving]="workspace.tenantSaving(editingTenant()?.id)"
[saveError]="workspace.tenantSaveError(editingTenant()?.id)"
(cancelled)="closeForm()"
(submitted)="saveTenant($event)"
/>
}
@@ -3,36 +3,39 @@ import { TenantsPage } from './tenants-page';
import { WorkspaceService } from '../../services/workspace.service';
describe('TenantsPage', () => {
beforeEach(() =>
TestBed.configureTestingModule({
providers: [
{
provide: WorkspaceService,
useValue: { loadTenants: () => undefined, clearTenantSaveError: () => undefined },
},
],
}),
);
beforeEach(() =>
TestBed.configureTestingModule({
providers: [
{
provide: WorkspaceService,
useValue: {
loadTenants: () => undefined,
clearTenantSaveError: () => undefined,
},
},
],
}),
);
it('opens and closes the focused tenant form module', () => {
const fixture = TestBed.createComponent(TenantsPage);
const component = fixture.componentInstance;
it('opens and closes the focused tenant form module', () => {
const fixture = TestBed.createComponent(TenantsPage);
const component = fixture.componentInstance;
component.openAddForm();
expect(component.showForm()).toBe(true);
expect(component.editingTenant()).toBeNull();
component.openAddForm();
expect(component.showForm()).toBe(true);
expect(component.editingTenant()).toBeNull();
component.openEditForm({
id: 'tenant-1',
firstName: 'Camille',
lastName: 'Martin',
email: 'camille@example.com',
phone: '',
address: '',
});
expect(component.editingTenant()?.id).toBe('tenant-1');
component.openEditForm({
id: 'tenant-1',
firstName: 'Camille',
lastName: 'Martin',
email: 'camille@example.com',
phone: '',
address: '',
});
expect(component.editingTenant()?.id).toBe('tenant-1');
component.closeForm();
expect(component.showForm()).toBe(false);
});
component.closeForm();
expect(component.showForm()).toBe(false);
});
});
@@ -7,36 +7,36 @@ import { TenantCard } from '../tenant-card/tenant-card';
import { TenantForm, TenantSaveRequest } from '../tenant-form/tenant-form';
@Component({
selector: 'app-tenants-page',
imports: [MatButtonModule, MatProgressBarModule, TenantCard, TenantForm],
templateUrl: './tenants-page.html',
styleUrl: './tenants-page.css',
selector: 'app-tenants-page',
imports: [MatButtonModule, MatProgressBarModule, TenantCard, TenantForm],
templateUrl: './tenants-page.html',
styleUrl: './tenants-page.css',
})
export class TenantsPage {
readonly workspace = inject(WorkspaceService);
readonly workspace = inject(WorkspaceService);
readonly showForm = signal(false);
readonly editingTenant = signal<Tenant | null>(null);
readonly showForm = signal(false);
readonly editingTenant = signal<Tenant | null>(null);
constructor() {
this.workspace.loadTenants();
}
constructor() {
this.workspace.loadTenants();
}
openAddForm(): void {
this.workspace.clearTenantSaveError();
this.editingTenant.set(null);
this.showForm.set(true);
}
openEditForm(tenant: Tenant): void {
this.workspace.clearTenantSaveError(tenant.id);
this.editingTenant.set(tenant);
this.showForm.set(true);
}
closeForm(): void {
this.showForm.set(false);
this.editingTenant.set(null);
}
saveTenant(request: TenantSaveRequest): void {
this.workspace.saveTenant(request.tenant, request.tenantId, () => this.closeForm());
}
openAddForm(): void {
this.workspace.clearTenantSaveError();
this.editingTenant.set(null);
this.showForm.set(true);
}
openEditForm(tenant: Tenant): void {
this.workspace.clearTenantSaveError(tenant.id);
this.editingTenant.set(tenant);
this.showForm.set(true);
}
closeForm(): void {
this.showForm.set(false);
this.editingTenant.set(null);
}
saveTenant(request: TenantSaveRequest): void {
this.workspace.saveTenant(request.tenant, request.tenantId, () => this.closeForm());
}
}