import { Component, OnDestroy, inject, input, output, signal } from '@angular/core'; import { finalize as rxFinalize } from 'rxjs'; import { ConditionPhoto, ConditionReportService, ConditionReportType, EntryPhotoDisposition, PropertyConditionReport, } from '../../services/condition-report.service'; import { httpErrorMessage } from '../../services/http-error-message'; interface PendingPhoto { file: File; previewUrl: string; } @Component({ selector: 'app-condition-report-evidence-section', templateUrl: './condition-report-evidence-section.html', styleUrl: './condition-report-page.css', }) export class ConditionReportEvidenceSection implements OnDestroy { private readonly reports = inject(ConditionReportService); readonly leaseId = input.required(); readonly type = input.required(); readonly report = input.required(); readonly entryPhotos = input([]); readonly editingDirty = input(false); readonly busyPhotoZone = signal(''); readonly pendingPhotos = signal>({}); readonly reportChanged = output(); readonly errorOccurred = output(); readonly successOccurred = output(); readonly reloadRequested = output(); ngOnDestroy(): void { Object.values(this.pendingPhotos()).forEach(({ previewUrl }) => URL.revokeObjectURL(previewUrl), ); } setZoneAbsence(zoneId: string, reason: string): void { this.update((report) => ({ ...report, zonePhotoAbsences: [ ...report.zonePhotoAbsences.filter((absence) => absence.zoneId !== zoneId), ...(reason.trim() ? [{ zoneId, absence: { reason, author: '', recordedAt: '' } }] : []), ], })); } setEntryPhotoDisposition( entryPhotoId: string, field: keyof EntryPhotoDisposition, value: string, ): void { this.update((report) => { const current = report.entryPhotoDispositions.find( (item) => item.entryPhotoId === entryPhotoId, ) ?? { entryPhotoId }; const next = { ...current, [field]: value || undefined }; return { ...report, entryPhotoDispositions: [ ...report.entryPhotoDispositions.filter((item) => item.entryPhotoId !== entryPhotoId), next, ], }; }); } selectPhoto(zoneId: string, event: Event): void { const input = event.target instanceof HTMLInputElement ? event.target : null; const file = input?.files?.item(0) ?? null; if (input) input.value = ''; if (!file) return; const previous = this.pendingPhotos()[zoneId]; if (previous) URL.revokeObjectURL(previous.previewUrl); this.pendingPhotos.update((photos) => ({ ...photos, [zoneId]: { file, previewUrl: URL.createObjectURL(file) }, })); } retakePhoto(zoneId: string): void { const photo = this.pendingPhotos()[zoneId]; if (!photo) return; URL.revokeObjectURL(photo.previewUrl); this.pendingPhotos.update((photos) => { const remaining = { ...photos }; delete remaining[zoneId]; return remaining; }); } uploadPhoto(zoneId: string): void { const pending = this.pendingPhotos()[zoneId]; if (!pending || this.busyPhotoZone() || this.editingDirty()) return; this.busyPhotoZone.set(zoneId); this.reports .uploadPhoto(this.leaseId(), this.type(), pending.file, zoneId) .pipe(rxFinalize(() => this.busyPhotoZone.set(''))) .subscribe({ next: () => { this.retakePhoto(zoneId); this.successOccurred.emit('Photo ajoutée.'); this.reloadRequested.emit(); }, error: (error) => this.errorOccurred.emit( httpErrorMessage(error, 'La photo a été refusée. Reprenez le cliché puis réessayez.'), ), }); } removePhoto(photo: ConditionPhoto): void { if (this.busyPhotoZone() || this.editingDirty()) return; this.busyPhotoZone.set(photo.zoneId); this.reports .deletePhoto(this.leaseId(), this.type(), photo.reference, this.report().version) .pipe(rxFinalize(() => this.busyPhotoZone.set(''))) .subscribe({ next: () => this.reloadRequested.emit(), error: (error) => this.errorOccurred.emit(httpErrorMessage(error, 'Impossible de supprimer cette photo.')), }); } absenceReason(zoneId: string): string { return ( this.report().zonePhotoAbsences.find((absence) => absence.zoneId === zoneId)?.absence .reason ?? '' ); } disposition(entryPhotoId: string): EntryPhotoDisposition { return ( this.report().entryPhotoDispositions.find((item) => item.entryPhotoId === entryPhotoId) ?? { entryPhotoId, } ); } photoUrl(type: ConditionReportType, reference: string): string { return this.reports.photoUrl(this.leaseId(), type, reference); } private update(change: (report: PropertyConditionReport) => PropertyConditionReport): void { this.reportChanged.emit(change(this.report())); } }