Align apartment document upload validation (#51)
This commit is contained in:
1 parent
82293a322d
commit
cd32d05466
7 files changed
+492
-83
No files matched your search
+28
-5
@@ -93,18 +93,28 @@
|
||||
<fieldset [disabled]="uploading()">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Type de document</mat-label>
|
||||
<mat-select [formField]="uploadForm.type">
|
||||
<mat-select
|
||||
[formField]="uploadForm.type"
|
||||
(selectionChange)="clearUploadFieldError('type')"
|
||||
>
|
||||
<mat-option value="">Choisissez un type</mat-option>
|
||||
@for (type of documentTypes; track type.value) {
|
||||
<mat-option [value]="type.value">{{ type.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@if (fieldError('type'); as error) {
|
||||
<p class="form-error" role="alert">{{ error }}</p>
|
||||
}
|
||||
<label class="file-picker">
|
||||
<span>Fichier PDF</span>
|
||||
<input type="file" accept=".pdf,application/pdf" (change)="selectFile($event)" />
|
||||
<input type="file" accept="application/pdf" (change)="selectFile($event)" />
|
||||
<small>{{ selectedFile()?.name ?? 'Aucun fichier sélectionné' }}</small>
|
||||
<small>Le type et le contenu PDF sont vérifiés avant l’envoi.</small>
|
||||
</label>
|
||||
@if (fieldError('file'); as error) {
|
||||
<p class="form-error" role="alert">{{ error }}</p>
|
||||
}
|
||||
<div class="date-fields">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Date d’établissement</mat-label>
|
||||
@@ -112,16 +122,29 @@
|
||||
matInput
|
||||
id="establishedOn"
|
||||
type="date"
|
||||
(input)="clearUploadFieldError('establishedOn')"
|
||||
[formField]="uploadForm.establishedOn"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@if (fieldError('establishedOn'); as error) {
|
||||
<p class="form-error" role="alert">{{ error }}</p>
|
||||
}
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Date d’expiration (facultative)</mat-label>
|
||||
<input matInput id="expiresOn" type="date" [formField]="uploadForm.expiresOn" />
|
||||
<input
|
||||
matInput
|
||||
id="expiresOn"
|
||||
type="date"
|
||||
(input)="clearUploadFieldError('expiresOn')"
|
||||
[formField]="uploadForm.expiresOn"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@if (fieldError('expiresOn'); as error) {
|
||||
<p class="form-error" role="alert">{{ error }}</p>
|
||||
}
|
||||
</div>
|
||||
@if (validationError() || uploadError()) {
|
||||
<p class="form-error" role="alert">{{ validationError() || uploadError() }}</p>
|
||||
@if (uploadError()) {
|
||||
<p class="form-error" role="alert">{{ uploadError() }}</p>
|
||||
}
|
||||
<button
|
||||
mat-flat-button
|
||||
|
||||
+167
-4
@@ -83,6 +83,25 @@ describe('ApartmentDocumentManager', () => {
|
||||
http.expectNone((request) => request.method === 'POST');
|
||||
});
|
||||
|
||||
it('shows every local upload error and focuses the first invalid control', () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.nativeElement.querySelector('.upload-submit').click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Choisissez un type de document.');
|
||||
expect(fixture.nativeElement.textContent).toContain('Sélectionnez un fichier PDF.');
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Renseignez une date d’établissement valide.',
|
||||
);
|
||||
expect(document.activeElement).toBe(fixture.nativeElement.querySelector('mat-select'));
|
||||
http.expectNone((request) => request.method === 'POST');
|
||||
});
|
||||
|
||||
it('requires a PDF after the document type is selected', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
@@ -120,7 +139,7 @@ describe('ApartmentDocumentManager', () => {
|
||||
);
|
||||
if (!dpeOption) throw new Error('Option DPE introuvable.');
|
||||
(dpeOption as HTMLElement).click();
|
||||
const file = new File(['pdf'], 'dpe.pdf', { type: 'application/pdf' });
|
||||
const file = new File(['%PDF-document'], 'dpe.pdf', { type: 'application/pdf' });
|
||||
const fileInput: HTMLInputElement = fixture.nativeElement.querySelector('input[type=file]');
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
configurable: true,
|
||||
@@ -133,7 +152,7 @@ describe('ApartmentDocumentManager', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Renseignez la date d’établissement du document.',
|
||||
'Renseignez une date d’établissement valide.',
|
||||
);
|
||||
http.expectNone((request) => request.method === 'POST');
|
||||
});
|
||||
@@ -152,7 +171,7 @@ describe('ApartmentDocumentManager', () => {
|
||||
);
|
||||
if (!dpeOption) throw new Error('Option DPE introuvable.');
|
||||
(dpeOption as HTMLElement).click();
|
||||
const file = new File(['pdf'], 'dpe.pdf', { type: 'application/pdf' });
|
||||
const file = new File(['%PDF-document'], 'dpe.pdf', { type: 'application/pdf' });
|
||||
const fileInput: HTMLInputElement = fixture.nativeElement.querySelector('input[type=file]');
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
configurable: true,
|
||||
@@ -192,7 +211,7 @@ describe('ApartmentDocumentManager', () => {
|
||||
);
|
||||
if (!dpeOption) throw new Error('Option DPE introuvable.');
|
||||
(dpeOption as HTMLElement).click();
|
||||
const file = new File(['pdf'], 'dpe-2026.pdf', { type: 'application/pdf' });
|
||||
const file = new File(['%PDF-document'], 'dpe-2026', { type: 'application/pdf' });
|
||||
const fileInput: HTMLInputElement = fixture.nativeElement.querySelector('input[type=file]');
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
configurable: true,
|
||||
@@ -234,6 +253,116 @@ describe('ApartmentDocumentManager', () => {
|
||||
expect(submit.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a PDF MIME type and signature without relying on the file name', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const file = new File(['%PDF-document'], 'dpe', { type: 'application/pdf' });
|
||||
const fileInput: HTMLInputElement = fixture.nativeElement.querySelector('input[type=file]');
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
configurable: true,
|
||||
value: { item: () => file },
|
||||
});
|
||||
fileInput.dispatchEvent(new Event('change'));
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.componentInstance.selectedFile()).toBe(file);
|
||||
});
|
||||
|
||||
it('rejects a filename-only PDF before upload', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const file = new File(['%PDF-document'], 'dpe.pdf', { type: '' });
|
||||
const fileInput: HTMLInputElement = fixture.nativeElement.querySelector('input[type=file]');
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
configurable: true,
|
||||
value: { item: () => file },
|
||||
});
|
||||
fileInput.dispatchEvent(new Event('change'));
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.selectedFile()).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain('Sélectionnez un fichier PDF valide.');
|
||||
});
|
||||
|
||||
it('keeps the replacement form and existing document after a failed upload', () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([
|
||||
{
|
||||
id: 'document-1',
|
||||
type: 'DPE',
|
||||
establishedOn: '2026-07-19',
|
||||
expiresOn: null,
|
||||
fileName: 'dpe-existant.pdf',
|
||||
sha256: 'a'.repeat(64),
|
||||
authorId: 'owner-1',
|
||||
uploadedAt: '2026-07-19T10:00:00Z',
|
||||
},
|
||||
]);
|
||||
const file = new File(['%PDF-document'], 'dpe-2026', { type: 'application/pdf' });
|
||||
fixture.componentInstance.uploadModel.set({
|
||||
type: 'DPE',
|
||||
establishedOn: '2026-07-20',
|
||||
expiresOn: '',
|
||||
});
|
||||
fixture.componentInstance.selectedFile.set(file);
|
||||
fixture.componentInstance.submitUpload();
|
||||
|
||||
const upload = http.expectOne('/api/apartments/apartment-1/documents');
|
||||
upload.flush(
|
||||
{
|
||||
code: 'validation_failed',
|
||||
errors: [{ field: 'establishedOn', code: 'invalid' }],
|
||||
},
|
||||
{ status: 400, statusText: 'Bad Request' },
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('dpe-existant.pdf');
|
||||
expect(fixture.componentInstance.uploadModel()).toEqual({
|
||||
type: 'DPE',
|
||||
establishedOn: '2026-07-20',
|
||||
expiresOn: '',
|
||||
});
|
||||
expect(fixture.componentInstance.selectedFile()).toBe(file);
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Renseignez une date d’établissement valide.',
|
||||
);
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Veuillez corriger les champs signalés.',
|
||||
);
|
||||
expect(document.activeElement).toBe(fixture.nativeElement.querySelector('#establishedOn'));
|
||||
});
|
||||
|
||||
it('clears only the corrected upload field error', () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([]);
|
||||
fixture.componentInstance.uploadFieldErrors.set([
|
||||
{ field: 'type', code: 'invalid' },
|
||||
{ field: 'establishedOn', code: 'invalid' },
|
||||
{ field: 'expiresOn', code: 'before_establishment' },
|
||||
]);
|
||||
|
||||
fixture.componentInstance.clearUploadFieldError('establishedOn');
|
||||
|
||||
expect(fixture.componentInstance.uploadFieldErrors()).toEqual([
|
||||
{ field: 'type', code: 'invalid' },
|
||||
{ field: 'expiresOn', code: 'before_establishment' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows a load failure and retries without presenting an empty result', () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
@@ -334,4 +463,38 @@ describe('ApartmentDocumentManager', () => {
|
||||
expect(fixture.nativeElement.textContent).not.toContain('dpe.pdf');
|
||||
expect(fixture.nativeElement.textContent).toContain('Aucun document enregistré.');
|
||||
});
|
||||
|
||||
it('keeps a document visible when its deletion fails', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentDocumentManager);
|
||||
fixture.componentRef.setInput('apartmentId', 'apartment-1');
|
||||
fixture.detectChanges();
|
||||
http.expectOne('/api/apartments/apartment-1/documents').flush([
|
||||
{
|
||||
id: 'document-1',
|
||||
type: 'DPE',
|
||||
establishedOn: '2026-07-19',
|
||||
expiresOn: null,
|
||||
fileName: 'dpe.pdf',
|
||||
sha256: 'a'.repeat(64),
|
||||
authorId: 'owner-1',
|
||||
uploadedAt: '2026-07-19T10:00:00Z',
|
||||
},
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
vi.useFakeTimers();
|
||||
|
||||
fixture.nativeElement.querySelector('.delete-document').click();
|
||||
fixture.detectChanges();
|
||||
const confirm: HTMLButtonElement | null = document.querySelector('.confirm-action');
|
||||
if (!confirm) throw new Error('Confirmation de suppression introuvable.');
|
||||
confirm.click();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
const request = http.expectOne('/api/apartments/apartment-1/documents/document-1');
|
||||
request.flush({}, { status: 500, statusText: 'Server Error' });
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('dpe.pdf');
|
||||
expect(fixture.nativeElement.textContent).toContain('Impossible de supprimer ce document.');
|
||||
});
|
||||
});
|
||||
+136
-65
@@ -1,4 +1,4 @@
|
||||
import { Component, DestroyRef, effect, inject, input, signal } from '@angular/core';
|
||||
import { Component, DestroyRef, effect, ElementRef, inject, input, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormField, FormRoot, form, required, validate } from '@angular/forms/signals';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ApartmentDocumentType,
|
||||
ApartmentService,
|
||||
} from '../../services/apartment.service';
|
||||
import { httpErrorMessage } from '../../services/http-error-message';
|
||||
import { FieldValidationError, saveFailure } from '../../services/http-error-message';
|
||||
import { ApartmentDocumentActions } from './apartment-document-actions.service';
|
||||
import {
|
||||
apartmentDocumentTypeLabel,
|
||||
@@ -27,6 +27,15 @@ const frenchDate = new Intl.DateTimeFormat('fr-FR', {
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
type UploadField = 'type' | 'file' | 'establishedOn' | 'expiresOn';
|
||||
type UploadFieldError = FieldValidationError & { field: UploadField };
|
||||
|
||||
function isUploadField(field: string): field is UploadField {
|
||||
return (
|
||||
field === 'type' || field === 'file' || field === 'establishedOn' || field === 'expiresOn'
|
||||
);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-apartment-document-manager',
|
||||
imports: [
|
||||
@@ -46,6 +55,8 @@ const frenchDate = new Intl.DateTimeFormat('fr-FR', {
|
||||
export class ApartmentDocumentManager {
|
||||
private readonly apartments = inject(ApartmentService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly host = inject(ElementRef<HTMLElement>);
|
||||
private fileSelectionId = 0;
|
||||
private requestId = 0;
|
||||
|
||||
readonly actions = inject(ApartmentDocumentActions);
|
||||
@@ -74,8 +85,8 @@ export class ApartmentDocumentManager {
|
||||
});
|
||||
readonly selectedFile = signal<File | null>(null);
|
||||
readonly uploading = signal(false);
|
||||
readonly validationError = signal('');
|
||||
readonly uploadError = signal('');
|
||||
readonly uploadFieldErrors = signal<FieldValidationError[]>([]);
|
||||
|
||||
constructor() {
|
||||
effect(() => this.load(this.apartmentId()));
|
||||
@@ -111,73 +122,133 @@ export class ApartmentDocumentManager {
|
||||
this.uploadForm().markAsTouched();
|
||||
const model = this.uploadModel();
|
||||
const file = this.selectedFile();
|
||||
if (!model.type) {
|
||||
this.validationError.set('Choisissez un type de document.');
|
||||
} else if (!file) {
|
||||
this.validationError.set('Sélectionnez un fichier PDF.');
|
||||
} else if (!model.establishedOn) {
|
||||
this.validationError.set('Renseignez la date d’établissement du document.');
|
||||
} else if (model.expiresOn && model.expiresOn < model.establishedOn) {
|
||||
this.validationError.set(
|
||||
'La date d’expiration doit être postérieure ou égale à la date d’établissement.',
|
||||
);
|
||||
} else {
|
||||
this.validationError.set('');
|
||||
const fieldErrors = this.validationErrors(model, file);
|
||||
if (fieldErrors.length) {
|
||||
this.uploadError.set('');
|
||||
this.uploading.set(true);
|
||||
this.apartments
|
||||
.uploadDocument(this.apartmentId(), {
|
||||
file,
|
||||
type: model.type,
|
||||
establishedOn: model.establishedOn,
|
||||
expiresOn: model.expiresOn || null,
|
||||
})
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
finalize(() => this.uploading.set(false)),
|
||||
)
|
||||
.subscribe({
|
||||
next: (uploaded) => {
|
||||
this.documents.update((documents) =>
|
||||
[
|
||||
...documents.filter((document) => document.type !== uploaded.type),
|
||||
uploaded,
|
||||
].sort(
|
||||
(left, right) =>
|
||||
apartmentDocumentTypes.findIndex(
|
||||
(type) => type.value === left.type,
|
||||
) -
|
||||
apartmentDocumentTypes.findIndex(
|
||||
(type) => type.value === right.type,
|
||||
),
|
||||
),
|
||||
);
|
||||
this.uploadModel.set({ type: '', establishedOn: '', expiresOn: '' });
|
||||
this.selectedFile.set(null);
|
||||
},
|
||||
error: (error: unknown) =>
|
||||
this.uploadError.set(
|
||||
httpErrorMessage(error, 'Impossible de téléverser ce document.'),
|
||||
this.uploadFieldErrors.set(fieldErrors);
|
||||
this.focus(fieldErrors[0].field);
|
||||
return;
|
||||
}
|
||||
if (!file || !model.type) return;
|
||||
this.uploadError.set('');
|
||||
this.uploadFieldErrors.set([]);
|
||||
this.uploading.set(true);
|
||||
this.apartments
|
||||
.uploadDocument(this.apartmentId(), {
|
||||
file,
|
||||
type: model.type,
|
||||
establishedOn: model.establishedOn,
|
||||
expiresOn: model.expiresOn || null,
|
||||
})
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
finalize(() => this.uploading.set(false)),
|
||||
)
|
||||
.subscribe({
|
||||
next: (uploaded) => {
|
||||
this.documents.update((documents) =>
|
||||
[
|
||||
...documents.filter((document) => document.type !== uploaded.type),
|
||||
uploaded,
|
||||
].sort(
|
||||
(left, right) =>
|
||||
apartmentDocumentTypes.findIndex(
|
||||
(type) => type.value === left.type,
|
||||
) -
|
||||
apartmentDocumentTypes.findIndex(
|
||||
(type) => type.value === right.type,
|
||||
),
|
||||
),
|
||||
});
|
||||
);
|
||||
this.uploadModel.set({ type: '', establishedOn: '', expiresOn: '' });
|
||||
this.selectedFile.set(null);
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
const failure = saveFailure(
|
||||
error,
|
||||
'Impossible de téléverser ce document. Vérifiez que le PDF est valide et respecte la taille maximale du serveur.',
|
||||
);
|
||||
const inlineErrors = failure.fieldErrors.filter(
|
||||
(error): error is UploadFieldError => isUploadField(error.field),
|
||||
);
|
||||
this.uploadFieldErrors.set(inlineErrors);
|
||||
if (inlineErrors.length) this.focus(inlineErrors[0].field);
|
||||
this.uploadError.set(
|
||||
inlineErrors.length > 0 &&
|
||||
inlineErrors.length === failure.fieldErrors.length
|
||||
? ''
|
||||
: failure.message,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async selectFile(event: Event): Promise<void> {
|
||||
const input = event.target;
|
||||
const file = input instanceof HTMLInputElement ? (input.files?.item(0) ?? null) : null;
|
||||
const selectionId = ++this.fileSelectionId;
|
||||
if (input instanceof HTMLInputElement) input.value = '';
|
||||
this.clearUploadFieldError('file');
|
||||
if (!file) {
|
||||
this.selectedFile.set(null);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
file.type.toLowerCase() === 'application/pdf' &&
|
||||
(await file.slice(0, 5).text()) === '%PDF-'
|
||||
) {
|
||||
if (selectionId !== this.fileSelectionId) return;
|
||||
this.selectedFile.set(file);
|
||||
} else {
|
||||
if (selectionId !== this.fileSelectionId) return;
|
||||
this.selectedFile.set(null);
|
||||
this.uploadFieldErrors.update((errors) => [
|
||||
...errors,
|
||||
{ field: 'file', code: 'invalid' },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
selectFile(event: Event): void {
|
||||
const input = event.target;
|
||||
const file = input instanceof HTMLInputElement ? (input.files?.item(0) ?? null) : null;
|
||||
if (
|
||||
file &&
|
||||
file.name.toLowerCase().endsWith('.pdf') &&
|
||||
(file.type === 'application/pdf' || file.type === '')
|
||||
) {
|
||||
this.selectedFile.set(file);
|
||||
this.validationError.set('');
|
||||
} else {
|
||||
this.selectedFile.set(null);
|
||||
if (file) this.validationError.set('Sélectionnez un fichier PDF.');
|
||||
}
|
||||
if (input instanceof HTMLInputElement) input.value = '';
|
||||
clearUploadFieldError(field: UploadField): void {
|
||||
this.uploadFieldErrors.update((errors) => errors.filter((error) => error.field !== field));
|
||||
}
|
||||
|
||||
fieldError(field: UploadField): string {
|
||||
const code = this.uploadFieldErrors().find((error) => error.field === field)?.code;
|
||||
if (!code) return '';
|
||||
if (field === 'type') return 'Choisissez un type de document.';
|
||||
if (field === 'establishedOn') return 'Renseignez une date d’établissement valide.';
|
||||
if (field === 'expiresOn')
|
||||
return code === 'before_establishment'
|
||||
? 'La date d’expiration doit être postérieure ou égale à la date d’établissement.'
|
||||
: 'Renseignez une date d’expiration valide.';
|
||||
return code === 'invalid'
|
||||
? 'Sélectionnez un fichier PDF valide.'
|
||||
: 'Sélectionnez un fichier PDF.';
|
||||
}
|
||||
|
||||
private validationErrors(
|
||||
model: { type: ApartmentDocumentType | ''; establishedOn: string; expiresOn: string },
|
||||
file: File | null,
|
||||
): UploadFieldError[] {
|
||||
const errors: UploadFieldError[] = [];
|
||||
if (!model.type) errors.push({ field: 'type', code: 'required' });
|
||||
if (!file) errors.push({ field: 'file', code: 'required' });
|
||||
if (!model.establishedOn) errors.push({ field: 'establishedOn', code: 'required' });
|
||||
if (model.expiresOn && model.establishedOn && model.expiresOn < model.establishedOn)
|
||||
errors.push({ field: 'expiresOn', code: 'before_establishment' });
|
||||
return errors;
|
||||
}
|
||||
|
||||
private focus(field: UploadField): void {
|
||||
const selector = {
|
||||
type: 'mat-select',
|
||||
file: 'input[type=file]',
|
||||
establishedOn: '#establishedOn',
|
||||
expiresOn: '#expiresOn',
|
||||
}[field];
|
||||
const control = this.host.nativeElement.querySelector(selector);
|
||||
if (control instanceof HTMLElement) control.focus();
|
||||
}
|
||||
|
||||
private load(apartmentId: string): void {
|
||||
|
||||
Reference in new issue
Block a user