189 lines
5.8 KiB
TypeScript
189 lines
5.8 KiB
TypeScript
import { Component, inject, signal } from '@angular/core';
|
||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||
import { finalize as rxFinalize } from 'rxjs';
|
||
import {
|
||
ConditionReportService,
|
||
FurnitureInventory,
|
||
FurnitureInventoryLine,
|
||
FurniturePresence,
|
||
} from '../../services/condition-report.service';
|
||
import { httpErrorMessage } from '../../services/http-error-message';
|
||
|
||
const emptyInventory: FurnitureInventory = {
|
||
id: '',
|
||
templateVersion: '',
|
||
lines: [],
|
||
missingCategories: [],
|
||
absentCategories: [],
|
||
legalWarnings: [],
|
||
readyForLeaseDocument: false,
|
||
frozen: false,
|
||
privateAreas: [],
|
||
version: 0,
|
||
};
|
||
|
||
@Component({
|
||
selector: 'app-inventory-page',
|
||
imports: [RouterLink],
|
||
templateUrl: './inventory-page.html',
|
||
styleUrl: './inventory-page.css',
|
||
})
|
||
export class InventoryPage {
|
||
private readonly route = inject(ActivatedRoute);
|
||
private readonly inventories = inject(ConditionReportService);
|
||
readonly leaseId = this.route.snapshot.paramMap.get('leaseId') ?? '';
|
||
readonly inventory = signal<FurnitureInventory>(emptyInventory);
|
||
readonly loading = signal(true);
|
||
readonly saving = signal(false);
|
||
readonly error = signal('');
|
||
readonly success = signal('');
|
||
|
||
constructor() {
|
||
this.load();
|
||
}
|
||
|
||
load(): void {
|
||
this.loading.set(true);
|
||
this.error.set('');
|
||
this.success.set('');
|
||
this.inventories
|
||
.getInventory(this.leaseId)
|
||
.pipe(rxFinalize(() => this.loading.set(false)))
|
||
.subscribe({
|
||
next: (inventory) => this.inventory.set(inventory),
|
||
error: (error) =>
|
||
this.error.set(
|
||
httpErrorMessage(error, 'Impossible de charger l’inventaire du mobilier.'),
|
||
),
|
||
});
|
||
}
|
||
|
||
updateText(
|
||
id: string,
|
||
field: 'privateAreaId' | 'designation' | 'description',
|
||
event: Event,
|
||
): void {
|
||
const value =
|
||
event.target instanceof HTMLInputElement ||
|
||
event.target instanceof HTMLTextAreaElement ||
|
||
event.target instanceof HTMLSelectElement
|
||
? event.target.value
|
||
: '';
|
||
this.updateLine(id, { [field]: value });
|
||
}
|
||
|
||
updateQuantity(id: string, event: Event): void {
|
||
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
||
const quantity =
|
||
!input || input.value === '' || !Number.isFinite(input.valueAsNumber)
|
||
? -1
|
||
: input.valueAsNumber;
|
||
this.updateLine(id, { quantity });
|
||
}
|
||
|
||
updatePresence(id: string, event: Event): void {
|
||
const line = this.inventory().lines.find((candidate) => candidate.id === id);
|
||
const value = event.target instanceof HTMLSelectElement ? event.target.value : '';
|
||
const presence: FurniturePresence | null =
|
||
value === 'PRESENT' || value === 'ABSENT' ? value : null;
|
||
if (!line?.category) return;
|
||
this.updateLine(id, {
|
||
presence,
|
||
quantity:
|
||
presence === 'ABSENT'
|
||
? 0
|
||
: presence === 'PRESENT' && line.quantity === 0
|
||
? 1
|
||
: line.quantity,
|
||
});
|
||
}
|
||
|
||
addLine(): void {
|
||
if (this.inventory().frozen || this.saving()) return;
|
||
this.inventory.update((inventory) => ({
|
||
...inventory,
|
||
lines: [
|
||
...inventory.lines,
|
||
{
|
||
id: crypto.randomUUID(),
|
||
category: null,
|
||
presence: null,
|
||
privateAreaId: '',
|
||
designation: '',
|
||
quantity: 1,
|
||
description: '',
|
||
},
|
||
],
|
||
}));
|
||
}
|
||
|
||
removeLine(id: string): void {
|
||
if (this.inventory().frozen || this.saving()) return;
|
||
this.inventory.update((inventory) => ({
|
||
...inventory,
|
||
lines: inventory.lines.filter((line) => line.id !== id || line.category !== null),
|
||
}));
|
||
}
|
||
|
||
warningLabel(category: string): string {
|
||
return (
|
||
this.inventory().lines.find((line) => line.category === category)?.designation ?? category
|
||
);
|
||
}
|
||
|
||
save(): void {
|
||
const inventory = this.inventory();
|
||
if (inventory.frozen) {
|
||
this.error.set('Cet inventaire est figé et ne peut plus être modifié.');
|
||
return;
|
||
}
|
||
const validationError = this.validationError(inventory.lines);
|
||
if (validationError) {
|
||
this.error.set(validationError);
|
||
return;
|
||
}
|
||
this.saving.set(true);
|
||
this.error.set('');
|
||
this.success.set('');
|
||
this.inventories
|
||
.saveInventory(this.leaseId, { version: inventory.version, lines: inventory.lines })
|
||
.pipe(rxFinalize(() => this.saving.set(false)))
|
||
.subscribe({
|
||
next: (saved) => {
|
||
this.inventory.set(saved);
|
||
this.success.set('Inventaire enregistré.');
|
||
},
|
||
error: (error) =>
|
||
this.error.set(httpErrorMessage(error, 'Impossible d’enregistrer l’inventaire.')),
|
||
});
|
||
}
|
||
|
||
private updateLine(id: string, changes: Partial<FurnitureInventoryLine>): void {
|
||
if (this.inventory().frozen || this.saving()) return;
|
||
this.inventory.update((inventory) => ({
|
||
...inventory,
|
||
lines: inventory.lines.map((line) => (line.id === id ? { ...line, ...changes } : line)),
|
||
}));
|
||
}
|
||
|
||
private validationError(lines: FurnitureInventoryLine[]): string | null {
|
||
for (const line of lines) {
|
||
if (!line.privateAreaId.trim()) return 'Renseignez la zone de chaque ligne.';
|
||
if (!line.designation.trim()) return 'Renseignez la désignation de chaque ligne.';
|
||
if (!Number.isInteger(line.quantity) || line.quantity < 0) {
|
||
return 'Saisissez une quantité entière positive ou nulle.';
|
||
}
|
||
if (line.category && line.presence === null) {
|
||
return 'Déclarez la présence ou l’absence de chaque élément réglementaire.';
|
||
}
|
||
if (line.category && line.presence === 'PRESENT' && line.quantity === 0) {
|
||
return 'Un élément réglementaire présent doit avoir une quantité positive.';
|
||
}
|
||
if (line.category && line.presence === 'ABSENT' && line.quantity !== 0) {
|
||
return 'Un élément réglementaire absent doit avoir une quantité nulle.';
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|