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:
1 parent
5d2d85aa8c
commit
878da155bd
564 files changed
+61467
-47179
No files matched your search
+20
-20
@@ -1,41 +1,41 @@
|
||||
.action-dialog-content,
|
||||
.dialog-fieldset {
|
||||
display: grid;
|
||||
gap: var(--space-4, 1rem);
|
||||
display: grid;
|
||||
gap: var(--space-4, 1rem);
|
||||
}
|
||||
|
||||
.action-dialog-content {
|
||||
width: min(32rem, calc(100vw - 4rem));
|
||||
max-width: 100%;
|
||||
max-height: 75vh;
|
||||
overscroll-behavior-y: contain;
|
||||
width: min(32rem, 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;
|
||||
}
|
||||
|
||||
.outcome-summary {
|
||||
margin: 0;
|
||||
padding: var(--space-4, 1rem);
|
||||
border: 1px solid var(--border, #dce5df);
|
||||
border-radius: var(--radius-md, 0.8125rem);
|
||||
background: var(--surface-accent, #dcefeb);
|
||||
margin: 0;
|
||||
padding: var(--space-4, 1rem);
|
||||
border: 1px solid var(--border, #dce5df);
|
||||
border-radius: var(--radius-md, 0.8125rem);
|
||||
background: var(--surface-accent, #dcefeb);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
+54
-46
@@ -1,49 +1,57 @@
|
||||
<h2 mat-dialog-title>Affecter le surplus</h2>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<mat-dialog-content class="action-dialog-content">
|
||||
<p>
|
||||
Solde non affecté disponible :
|
||||
<strong>{{ formatEuro(data.payment.unallocatedBalance) }}</strong>
|
||||
</p>
|
||||
<fieldset class="dialog-fieldset" [disabled]="saving()">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Échéance cible</mat-label>
|
||||
<mat-select formControlName="rentDueId" required>
|
||||
@for (item of data.eligibleRentDues; track item.rentDue.id) {
|
||||
<mat-option [value]="item.rentDue.id">
|
||||
{{ item.label }} — {{ formatEuro(item.rentDue.remainingAmount) }} restant
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Montant à affecter</mat-label>
|
||||
<input matInput type="number" step="0.01" min="0.01" formControlName="amount" required />
|
||||
<span matTextSuffix>€</span>
|
||||
@if (form.controls.amount.hasError('unavailableAmount')) {
|
||||
<mat-error>Le montant dépasse le surplus ou le solde de l’échéance.</mat-error>
|
||||
} @else if (form.controls.amount.invalid) {
|
||||
<mat-error>Le montant doit être strictement positif.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
@if (selectedRentDue(); as item) {
|
||||
<p class="outcome-summary" aria-live="polite">
|
||||
Solde de l’échéance après affectation :
|
||||
<strong>{{ formatEuro(remainingAfter()) }}</strong>
|
||||
</p>
|
||||
}
|
||||
@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="Affectation en cours" />
|
||||
}
|
||||
Confirmer l’affectation
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
<mat-dialog-content class="action-dialog-content">
|
||||
<p>
|
||||
Solde non affecté disponible :
|
||||
<strong>{{ formatEuro(data.payment.unallocatedBalance) }}</strong>
|
||||
</p>
|
||||
<fieldset class="dialog-fieldset" [disabled]="saving()">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Échéance cible</mat-label>
|
||||
<mat-select formControlName="rentDueId" required>
|
||||
@for (item of data.eligibleRentDues; track item.rentDue.id) {
|
||||
<mat-option [value]="item.rentDue.id">
|
||||
{{ item.label }} —
|
||||
{{ formatEuro(item.rentDue.remainingAmount) }} restant
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Montant à affecter</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
formControlName="amount"
|
||||
required
|
||||
/>
|
||||
<span matTextSuffix>€</span>
|
||||
@if (form.controls.amount.hasError('unavailableAmount')) {
|
||||
<mat-error>Le montant dépasse le surplus ou le solde de l’échéance.</mat-error>
|
||||
} @else if (form.controls.amount.invalid) {
|
||||
<mat-error>Le montant doit être strictement positif.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
@if (selectedRentDue(); as item) {
|
||||
<p class="outcome-summary" aria-live="polite">
|
||||
Solde de l’échéance après affectation :
|
||||
<strong>{{ formatEuro(remainingAfter()) }}</strong>
|
||||
</p>
|
||||
}
|
||||
@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="Affectation en cours" />
|
||||
}
|
||||
Confirmer l’affectation
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</form>
|
||||
+98
-98
@@ -1,12 +1,12 @@
|
||||
import { Component, computed, DestroyRef, inject, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||
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';
|
||||
@@ -22,110 +22,110 @@ import { RentPayment, RentPaymentService } from '../../services/rent-payment.ser
|
||||
import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result';
|
||||
|
||||
export interface EligibleRentDue {
|
||||
rentDue: RentDue;
|
||||
label: string;
|
||||
rentDue: RentDue;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface RentPaymentAllocationDialogData {
|
||||
payment: RentPayment;
|
||||
eligibleRentDues: EligibleRentDue[];
|
||||
payment: RentPayment;
|
||||
eligibleRentDues: EligibleRentDue[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-rent-payment-allocation-dialog',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
],
|
||||
templateUrl: './rent-payment-allocation-dialog.html',
|
||||
styleUrl: './rent-payment-allocation-dialog.css',
|
||||
selector: 'app-rent-payment-allocation-dialog',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
],
|
||||
templateUrl: './rent-payment-allocation-dialog.html',
|
||||
styleUrl: './rent-payment-allocation-dialog.css',
|
||||
})
|
||||
export class RentPaymentAllocationDialog {
|
||||
readonly data = inject<RentPaymentAllocationDialogData>(MAT_DIALOG_DATA);
|
||||
private readonly dialogRef = inject(MatDialogRef<RentPaymentAllocationDialog>);
|
||||
private readonly payments = inject(RentPaymentService);
|
||||
private readonly operationId = crypto.randomUUID();
|
||||
readonly saving = signal(false);
|
||||
readonly error = signal('');
|
||||
readonly formatEuro = formatEuro;
|
||||
readonly data = inject<RentPaymentAllocationDialogData>(MAT_DIALOG_DATA);
|
||||
private readonly dialogRef = inject(MatDialogRef<RentPaymentAllocationDialog>);
|
||||
private readonly payments = inject(RentPaymentService);
|
||||
private readonly operationId = crypto.randomUUID();
|
||||
readonly saving = signal(false);
|
||||
readonly error = signal('');
|
||||
readonly formatEuro = formatEuro;
|
||||
|
||||
readonly form = new FormGroup({
|
||||
rentDueId: new FormControl(this.data.eligibleRentDues[0]?.rentDue.id ?? '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
amount: new FormControl(0, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.min(0.01)],
|
||||
}),
|
||||
});
|
||||
readonly form = new FormGroup({
|
||||
rentDueId: new FormControl(this.data.eligibleRentDues[0]?.rentDue.id ?? '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
amount: new FormControl(0, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.min(0.01)],
|
||||
}),
|
||||
});
|
||||
|
||||
private readonly rentDueId = toSignal(this.form.controls.rentDueId.valueChanges, {
|
||||
initialValue: this.form.controls.rentDueId.value,
|
||||
});
|
||||
readonly selectedRentDue = computed(() =>
|
||||
this.data.eligibleRentDues.find((item) => item.rentDue.id === this.rentDueId()),
|
||||
);
|
||||
readonly maximumAmount = computed(() =>
|
||||
Math.min(
|
||||
this.data.payment.unallocatedBalance,
|
||||
this.selectedRentDue()?.rentDue.remainingAmount ?? 0,
|
||||
),
|
||||
);
|
||||
private readonly amount = toSignal(this.form.controls.amount.valueChanges, {
|
||||
initialValue: this.form.controls.amount.value,
|
||||
});
|
||||
readonly remainingAfter = computed(() =>
|
||||
Math.max((this.selectedRentDue()?.rentDue.remainingAmount ?? 0) - (this.amount() ?? 0), 0),
|
||||
);
|
||||
private readonly rentDueId = toSignal(this.form.controls.rentDueId.valueChanges, {
|
||||
initialValue: this.form.controls.rentDueId.value,
|
||||
});
|
||||
readonly selectedRentDue = computed(() =>
|
||||
this.data.eligibleRentDues.find((item) => item.rentDue.id === this.rentDueId()),
|
||||
);
|
||||
readonly maximumAmount = computed(() =>
|
||||
Math.min(
|
||||
this.data.payment.unallocatedBalance,
|
||||
this.selectedRentDue()?.rentDue.remainingAmount ?? 0,
|
||||
),
|
||||
);
|
||||
private readonly amount = toSignal(this.form.controls.amount.valueChanges, {
|
||||
initialValue: this.form.controls.amount.value,
|
||||
});
|
||||
readonly remainingAfter = computed(() =>
|
||||
Math.max((this.selectedRentDue()?.rentDue.remainingAmount ?? 0) - (this.amount() ?? 0), 0),
|
||||
);
|
||||
|
||||
constructor() {
|
||||
const destroyRef = inject(DestroyRef);
|
||||
this.form.controls.amount.addValidators(this.withinAvailableAmount.bind(this));
|
||||
this.form.controls.rentDueId.valueChanges
|
||||
.pipe(takeUntilDestroyed(destroyRef))
|
||||
.subscribe(() => this.useMaximum());
|
||||
this.useMaximum();
|
||||
}
|
||||
constructor() {
|
||||
const destroyRef = inject(DestroyRef);
|
||||
this.form.controls.amount.addValidators(this.withinAvailableAmount.bind(this));
|
||||
this.form.controls.rentDueId.valueChanges
|
||||
.pipe(takeUntilDestroyed(destroyRef))
|
||||
.subscribe(() => this.useMaximum());
|
||||
this.useMaximum();
|
||||
}
|
||||
|
||||
useMaximum(): void {
|
||||
this.form.controls.amount.setValue(this.maximumAmount());
|
||||
this.form.controls.amount.updateValueAndValidity();
|
||||
}
|
||||
useMaximum(): void {
|
||||
this.form.controls.amount.setValue(this.maximumAmount());
|
||||
this.form.controls.amount.updateValueAndValidity();
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
this.form.markAllAsTouched();
|
||||
const due = this.selectedRentDue()?.rentDue;
|
||||
if (!due || this.form.invalid || this.saving()) return;
|
||||
this.saving.set(true);
|
||||
this.error.set('');
|
||||
this.payments
|
||||
.allocate(this.data.payment.id, {
|
||||
operationId: this.operationId,
|
||||
paymentVersion: this.data.payment.version,
|
||||
rentDueId: due.id,
|
||||
rentDueVersion: due.version,
|
||||
amount: this.form.controls.amount.value,
|
||||
})
|
||||
.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’affecter ce règlement.'));
|
||||
},
|
||||
});
|
||||
}
|
||||
submit(): void {
|
||||
this.form.markAllAsTouched();
|
||||
const due = this.selectedRentDue()?.rentDue;
|
||||
if (!due || this.form.invalid || this.saving()) return;
|
||||
this.saving.set(true);
|
||||
this.error.set('');
|
||||
this.payments
|
||||
.allocate(this.data.payment.id, {
|
||||
operationId: this.operationId,
|
||||
paymentVersion: this.data.payment.version,
|
||||
rentDueId: due.id,
|
||||
rentDueVersion: due.version,
|
||||
amount: this.form.controls.amount.value,
|
||||
})
|
||||
.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’affecter ce règlement.'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private withinAvailableAmount(control: AbstractControl<number>): ValidationErrors | null {
|
||||
return control.value > this.maximumAmount() ? { unavailableAmount: true } : null;
|
||||
}
|
||||
private withinAvailableAmount(control: AbstractControl<number>): ValidationErrors | null {
|
||||
return control.value > this.maximumAmount() ? { unavailableAmount: true } : null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user