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,33 +1,33 @@
.action-dialog-content,
.dialog-fieldset {
display: grid;
gap: var(--space-4, 1rem);
display: grid;
gap: var(--space-4, 1rem);
}
.action-dialog-content {
width: min(30rem, calc(100vw - 4rem));
max-width: 100%;
max-height: 75vh;
overscroll-behavior-y: contain;
width: min(30rem, calc(100vw - 4rem));
max-width: 100%;
max-height: 75vh;
overscroll-behavior-y: contain;
}
.dialog-fieldset {
min-width: 0;
margin: 0;
padding: 0;
border: 0;
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
.dialog-error {
margin: 0;
color: var(--danger, #b42318);
margin: 0;
color: var(--danger, #b42318);
}
mat-dialog-actions button {
min-height: 2.75rem;
min-height: 2.75rem;
}
mat-spinner {
display: inline-block;
margin-inline-end: var(--space-2, 0.5rem);
display: inline-block;
margin-inline-end: var(--space-2, 0.5rem);
}
@@ -1,39 +1,47 @@
<h2 mat-dialog-title>Rembourser le surplus</h2>
<form [formGroup]="form" (ngSubmit)="submit()">
<mat-dialog-content class="action-dialog-content">
<p>
Solde non affecté disponible : <strong>{{ formatEuro(payment.unallocatedBalance) }}</strong>
</p>
<fieldset class="dialog-fieldset" [disabled]="saving()">
<mat-form-field appearance="outline">
<mat-label>Date du remboursement</mat-label>
<input matInput type="date" formControlName="refundedAt" [max]="today" required />
@if (form.controls.refundedAt.hasError('futureDate')) {
<mat-error>La date du remboursement ne peut pas être dans le futur.</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Montant remboursé</mat-label>
<input matInput type="number" step="0.01" min="0.01" formControlName="amount" required />
<span matTextSuffix>€</span>
@if (form.controls.amount.hasError('max')) {
<mat-error>Le remboursement dépasse le solde non affecté.</mat-error>
} @else if (form.controls.amount.invalid) {
<mat-error>Le montant doit être strictement positif.</mat-error>
}
</mat-form-field>
</fieldset>
@if (error()) {
<p class="dialog-error" role="alert">{{ error() }}</p>
}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close type="button" [disabled]="saving()">Annuler</button>
<button mat-flat-button type="submit" [disabled]="form.invalid || saving()">
@if (saving()) {
<mat-spinner diameter="18" aria-label="Remboursement en cours" />
}
Confirmer le remboursement
</button>
</mat-dialog-actions>
<mat-dialog-content class="action-dialog-content">
<p>
Solde non affecté disponible :
<strong>{{ formatEuro(payment.unallocatedBalance) }}</strong>
</p>
<fieldset class="dialog-fieldset" [disabled]="saving()">
<mat-form-field appearance="outline">
<mat-label>Date du remboursement</mat-label>
<input matInput type="date" formControlName="refundedAt" [max]="today" required />
@if (form.controls.refundedAt.hasError('futureDate')) {
<mat-error>La date du remboursement ne peut pas être dans le futur.</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Montant remboursé</mat-label>
<input
matInput
type="number"
step="0.01"
min="0.01"
formControlName="amount"
required
/>
<span matTextSuffix>€</span>
@if (form.controls.amount.hasError('max')) {
<mat-error>Le remboursement dépasse le solde non affecté.</mat-error>
} @else if (form.controls.amount.invalid) {
<mat-error>Le montant doit être strictement positif.</mat-error>
}
</mat-form-field>
</fieldset>
@if (error()) {
<p class="dialog-error" role="alert">{{ error() }}</p>
}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close type="button" [disabled]="saving()">Annuler</button>
<button mat-flat-button type="submit" [disabled]="form.invalid || saving()">
@if (saving()) {
<mat-spinner diameter="18" aria-label="Remboursement en cours" />
}
Confirmer le remboursement
</button>
</mat-dialog-actions>
</form>
@@ -1,11 +1,11 @@
import { Component, inject, signal } from '@angular/core';
import {
AbstractControl,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
Validators,
AbstractControl,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
@@ -19,69 +19,71 @@ import { RentPayment, RentPaymentService } from '../../services/rent-payment.ser
import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result';
@Component({
selector: 'app-rent-payment-refund-dialog',
imports: [
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatProgressSpinnerModule,
],
templateUrl: './rent-payment-refund-dialog.html',
styleUrl: './rent-payment-refund-dialog.css',
selector: 'app-rent-payment-refund-dialog',
imports: [
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatProgressSpinnerModule,
],
templateUrl: './rent-payment-refund-dialog.html',
styleUrl: './rent-payment-refund-dialog.css',
})
export class RentPaymentRefundDialog {
readonly payment = inject<RentPayment>(MAT_DIALOG_DATA);
private readonly dialogRef = inject(MatDialogRef<RentPaymentRefundDialog>);
private readonly payments = inject(RentPaymentService);
private readonly operationId = crypto.randomUUID();
readonly today = localIsoDate();
readonly saving = signal(false);
readonly error = signal('');
readonly formatEuro = formatEuro;
readonly payment = inject<RentPayment>(MAT_DIALOG_DATA);
private readonly dialogRef = inject(MatDialogRef<RentPaymentRefundDialog>);
private readonly payments = inject(RentPaymentService);
private readonly operationId = crypto.randomUUID();
readonly today = localIsoDate();
readonly saving = signal(false);
readonly error = signal('');
readonly formatEuro = formatEuro;
readonly form = new FormGroup({
refundedAt: new FormControl(this.today, {
nonNullable: true,
validators: [Validators.required, this.notFuture.bind(this)],
}),
amount: new FormControl(this.payment.unallocatedBalance, {
nonNullable: true,
validators: [
Validators.required,
Validators.min(0.01),
Validators.max(this.payment.unallocatedBalance),
],
}),
});
readonly form = new FormGroup({
refundedAt: new FormControl(this.today, {
nonNullable: true,
validators: [Validators.required, this.notFuture.bind(this)],
}),
amount: new FormControl(this.payment.unallocatedBalance, {
nonNullable: true,
validators: [
Validators.required,
Validators.min(0.01),
Validators.max(this.payment.unallocatedBalance),
],
}),
});
submit(): void {
this.form.markAllAsTouched();
if (this.form.invalid || this.saving()) return;
this.saving.set(true);
this.error.set('');
this.payments
.refund(this.payment.id, {
operationId: this.operationId,
paymentVersion: this.payment.version,
timeZone: browserTimeZone,
...this.form.getRawValue(),
})
.pipe(finalize(() => this.saving.set(false)))
.subscribe({
next: (payment) => this.dialogRef.close(payment),
error: (error) => {
if (isRentPaymentDataStale(error)) {
this.dialogRef.close(RENT_PAYMENT_DATA_STALE);
return;
}
this.error.set(httpErrorMessage(error, 'Impossible d’enregistrer ce remboursement.'));
},
});
}
submit(): void {
this.form.markAllAsTouched();
if (this.form.invalid || this.saving()) return;
this.saving.set(true);
this.error.set('');
this.payments
.refund(this.payment.id, {
operationId: this.operationId,
paymentVersion: this.payment.version,
timeZone: browserTimeZone,
...this.form.getRawValue(),
})
.pipe(finalize(() => this.saving.set(false)))
.subscribe({
next: (payment) => this.dialogRef.close(payment),
error: (error) => {
if (isRentPaymentDataStale(error)) {
this.dialogRef.close(RENT_PAYMENT_DATA_STALE);
return;
}
this.error.set(
httpErrorMessage(error, 'Impossible d’enregistrer ce remboursement.'),
);
},
});
}
private notFuture(control: AbstractControl<string>): ValidationErrors | null {
return control.value > this.today ? { futureDate: true } : null;
}
private notFuture(control: AbstractControl<string>): ValidationErrors | null {
return control.value > this.today ? { futureDate: true } : null;
}
}