large update wip

This commit is contained in:
2025-08-24 17:24:40 +03:00
parent ae2dce2871
commit ef93d51f77
50 changed files with 3459 additions and 621 deletions

View File

@@ -0,0 +1,174 @@
import { AfterViewInit, Component, ElementRef, inject, input, OnInit, QueryList, signal, ViewChildren } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { ImageInputComponent } from "../image-input/image-input.component";
import { CandidateDataService } from '../../services/candidate-data.service';
import "@maptiler/leaflet-maptilersdk";
import { LeafletMapComponent } from "../leaflet-map/leaflet-map.component";
import { CITY_LIST } from '../../shared/cities';
import { cityValidator } from '../../validators/city.validator';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { SocketIOService } from '../../services/socket-io.service';
const CITY_NAMES = CITY_LIST.map(c => c.name).sort();
const israeliPhoneRegex = /^(?:(?:(\+?972|\(\+?972\)|\+?\(972\))(?:\s|\.|-)?([1-9]\d?))|(0[23489]{1})|(0[57]{1}[0-9]))(?:\s|\.|-)?([^0\D]{1}\d{2}(?:\s|\.|-)?\d{4})$/;
@Component({
selector: 'app-registration',
imports: [
CommonModule,
ReactiveFormsModule,
MatInputModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule,
ImageInputComponent,
LeafletMapComponent,
MatSnackBarModule,
RouterLink
],
templateUrl: './registration.component.html',
styleUrls: ['./registration.component.scss'],
})
export class RegistrationComponent implements OnInit {
dataService = inject(CandidateDataService);
fb = inject(FormBuilder);
router = inject(Router);
activatedRoute = inject(ActivatedRoute);
snackBar = inject(MatSnackBar)
socketService = inject(SocketIOService);
previewUrl: string | ArrayBuffer | null = null;
editMode = signal(false);
applicationId = signal<number | null>(null);
form = this.fb.group({
fullName: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
phoneNumber: ['', [Validators.required, Validators.pattern(israeliPhoneRegex)]],
age: [0, [Validators.required, Validators.min(18), Validators.max(70)]],
cityOrRegion: ['', [Validators.required, cityValidator(CITY_NAMES)]],
hobbies: ['', [Validators.maxLength(300)]],
justification: ['', [Validators.required, Validators.maxLength(300)]],
profileImage: this.fb.control<File | null>(null, Validators.required),
});
ngOnInit(): void {
const idParam = this.activatedRoute.snapshot.paramMap.get('id');
const url = this.activatedRoute.snapshot.url.map(s => s.path);
if (url.includes('edit') && idParam) {
this.editMode.set(true);
this.applicationId.set(+idParam);
this.loadCandidate(+idParam);
} else {
this.editMode.set(false);
this.applicationId.set(null);
}
}
loadCandidate(id: number) {
this.dataService.getApplicationDetails(id).subscribe({
next: (candidate: any) => {
this.form.patchValue({
fullName: candidate.fullName,
email: candidate.email,
phoneNumber: candidate.phoneNumber,
age: candidate.age,
cityOrRegion: candidate.cityOrRegion,
hobbies: candidate.hobbies,
justification: candidate.justification,
});
if (candidate.profileImageUrl) {
this.previewUrl = candidate.profileImageUrl;
this.form.get('profileImage')?.clearValidators();
this.form.get('profileImage')?.updateValueAndValidity();
}
},
error: err => console.error('Failed to load candidate', err),
});
}
scrollToFirstInvalidField() {
for (const key of Object.keys(this.form.controls)) {
const control = this.form.get(key);
if (control && control.invalid) {
const element = document.querySelector(
`[formControlName="${key}"]`
) as HTMLElement;
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
element.focus();
}
break;
}
}
}
onSubmit() {
if (!this.form.valid) {
this.scrollToFirstInvalidField();
return;
}
const formData = new FormData();
const value = this.form.value;
if (value.fullName) formData.append('fullName', value.fullName);
if (value.email) formData.append('email', value.email);
if (value.phoneNumber) formData.append('phoneNumber', value.phoneNumber);
if (value.age !== null && value.age !== undefined) formData.append('age', value.age.toString());
if (value.cityOrRegion) formData.append('cityOrRegion', value.cityOrRegion);
if (value.hobbies) formData.append('hobbies', value.hobbies);
if (value.justification) formData.append('justification', value.justification);
const imageFile = this.form.get('profileImage')?.value;
if (imageFile) {
formData.append('profileImage', imageFile);
}
if (this.editMode() && this.applicationId()) {
this.dataService.updateCandidateForm(this.applicationId()!, formData).subscribe({
next: (updatedCandidate) => {
this.snackBar.open('✅ Application updated!', 'Close', {
duration: 5000,
horizontalPosition: 'center',
verticalPosition: 'top',
});
this.socketService.socket.emit('candidateUpdated', updatedCandidate);
this.router.navigate(['/application-list']);
},
error: err => console.error('Error updating application', err),
});
} else {
this.dataService.submitCandidateForm(formData).subscribe({
next: (newCandidate) => {
this.snackBar.open('✅ Application saved!', 'Close', {
duration: 5000,
horizontalPosition: 'center',
verticalPosition: 'top',
});
this.socketService.socket.emit('candidateRegistered', newCandidate);
this.form.reset();
},
error: err => console.error('Error submitting form', err),
});
}
}
goBack(): void {
this.router.navigate(['/landing']);
}
}