354 lines
11 KiB
TypeScript
354 lines
11 KiB
TypeScript
import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core';
|
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
import { finalize, forkJoin } from 'rxjs';
|
|
import { TravelPlanService } from '../../core/services/carnet/travel-plan.service';
|
|
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
|
import { NotificationService } from '../../core/services/common/notification.service';
|
|
import { Country, TravelEntry, TravelPlan } from '../../core/models/carnet/travel-plan';
|
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
|
import { CommonModule } from '@angular/common';
|
|
import { MatListModule } from '@angular/material/list';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
|
|
|
|
@Component({
|
|
selector: 'app-travel-plan',
|
|
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule, MatListModule],
|
|
templateUrl: './travel-plan.component.html',
|
|
styleUrl: './travel-plan.component.scss'
|
|
})
|
|
export class TravelPlanComponent {
|
|
@Input() headerid: number = 0;
|
|
@Input() isViewMode = false;
|
|
@Input() applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
|
|
|
|
@Output() completed = new EventEmitter<boolean>();
|
|
@Output() needsInsurance = new EventEmitter<boolean>();
|
|
|
|
private fb = inject(FormBuilder);
|
|
private dialog = inject(MatDialog);
|
|
|
|
private travelPlanService = inject(TravelPlanService);
|
|
private notificationService = inject(NotificationService);
|
|
private errorHandler = inject(ApiErrorHandlerService);
|
|
|
|
travelForm: FormGroup;
|
|
isLoading = false;
|
|
changeInProgress = false;
|
|
showAdditionalSetsValidationMessage: boolean = false;
|
|
visitsCount = 1;
|
|
transitsCount = 1;
|
|
|
|
countries: Country[] = [];
|
|
selectedVisitCountries: TravelEntry[] = [];
|
|
selectedTransitCountries: TravelEntry[] = [];
|
|
|
|
// Currently selected country in the listbox
|
|
selectedAvailableVisitCountry: Country | null = null;
|
|
selectedAvailableTransitCountry: Country | null = null;
|
|
selectedVisitCountry: Country | null = null;
|
|
selectedTransitCountry: Country | null = null;
|
|
|
|
constructor() {
|
|
this.travelForm = this.fb.group({
|
|
usaEntries: ['', [Validators.required, Validators.min(1), Validators.max(99)]],
|
|
visitsInput: ['', [Validators.min(1), Validators.max(99)]],
|
|
transitsInput: ['', [Validators.min(1), Validators.max(99)]],
|
|
});
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
if (this.headerid) {
|
|
this.loadTravelPlan();
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
if (changes['headerid'] && this.headerid > 0) {
|
|
this.loadTravelPlan();
|
|
}
|
|
}
|
|
|
|
onAvailableVisitCountrySelection(event: any) {
|
|
this.selectedAvailableVisitCountry = event.value;
|
|
}
|
|
|
|
onVisitCountrySelection(event: any) {
|
|
this.selectedVisitCountry = event.value;
|
|
}
|
|
|
|
onAvailableTransitCountrySelection(event: any) {
|
|
this.selectedAvailableTransitCountry = event.value;
|
|
}
|
|
|
|
onTransitCountrySelection(event: any) {
|
|
this.selectedTransitCountry = event.value;
|
|
}
|
|
|
|
// Add selected country to visits
|
|
addVisitCountry(): void {
|
|
if (!this.selectedAvailableVisitCountry) return;
|
|
|
|
if (this.selectedAvailableVisitCountry.actionType === 'FYI') {
|
|
this.fyiAction(this.selectedAvailableVisitCountry);
|
|
} else if (this.selectedAvailableVisitCountry.actionType === 'WARNING') {
|
|
this.warningAction(this.selectedAvailableVisitCountry);
|
|
} else if (this.selectedAvailableVisitCountry.actionType === 'ACTION') {
|
|
this.takeAction(this.selectedAvailableVisitCountry);
|
|
} else {
|
|
this.addVisitCountryToCollection();
|
|
}
|
|
}
|
|
|
|
addVisitCountryToCollection(): void {
|
|
if (!this.selectedAvailableVisitCountry) return;
|
|
|
|
const existingIndex = this.selectedVisitCountries.findIndex(
|
|
c => c.value === this.selectedAvailableVisitCountry!.value
|
|
);
|
|
|
|
if (existingIndex === -1) {
|
|
this.selectedVisitCountries.push({
|
|
...this.selectedAvailableVisitCountry,
|
|
visits: this.visitsCount
|
|
});
|
|
|
|
if (this.applicationType === 'additional') {
|
|
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
|
|
}
|
|
}
|
|
|
|
this.visitsCount = 1;
|
|
}
|
|
|
|
fyiAction(country: Country): void {
|
|
|
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
|
width: '560px',
|
|
data: {
|
|
title: 'For your information',
|
|
message: country.actionMessage,
|
|
confirmText: 'Ok',
|
|
cancelText: 'Cancel'
|
|
}
|
|
});
|
|
|
|
dialogRef.afterClosed().subscribe(result => {
|
|
if (result) {
|
|
this.addVisitCountryToCollection();
|
|
}
|
|
});
|
|
}
|
|
|
|
warningAction(country: Country): void {
|
|
|
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
|
width: '560px',
|
|
data: {
|
|
title: 'Warning',
|
|
message: country.actionMessage,
|
|
confirmText: 'Ok',
|
|
cancelText: 'Cancel'
|
|
}
|
|
});
|
|
|
|
dialogRef.afterClosed().subscribe(result => {
|
|
if (result) {
|
|
this.addVisitCountryToCollection();
|
|
}
|
|
});
|
|
}
|
|
|
|
takeAction(country: Country): void {
|
|
|
|
if (this.IsCountryExists('transit', country)) {
|
|
this.addVisitCountryToCollection();
|
|
return;
|
|
}
|
|
|
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
|
width: '560px',
|
|
data: {
|
|
title: 'Act',
|
|
message: country.actionMessage,
|
|
confirmText: 'Ok',
|
|
cancelText: 'Cancel'
|
|
}
|
|
});
|
|
|
|
dialogRef.afterClosed().subscribe(result => {
|
|
if (result) {
|
|
this.transitsCount = this.visitsCount;
|
|
this.selectedAvailableTransitCountry = this.selectedAvailableVisitCountry;
|
|
this.addVisitCountryToCollection();
|
|
this.addTransitCountryToCollection();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add selected country to transits
|
|
addTransitCountry(): void {
|
|
if (!this.selectedAvailableTransitCountry) return;
|
|
this.addTransitCountryToCollection();
|
|
}
|
|
|
|
addTransitCountryToCollection(): void {
|
|
if (!this.selectedAvailableTransitCountry) return;
|
|
|
|
const existingIndex = this.selectedTransitCountries.findIndex(
|
|
c => c.value === this.selectedAvailableTransitCountry!.value
|
|
);
|
|
|
|
if (existingIndex === -1) {
|
|
this.selectedTransitCountries.push({
|
|
...this.selectedAvailableTransitCountry,
|
|
visits: this.transitsCount
|
|
});
|
|
}
|
|
this.transitsCount = 1;
|
|
}
|
|
|
|
IsCountryExists(countryType: string, country: Country): boolean {
|
|
|
|
const existingIndex = countryType === 'transit' ? this.selectedTransitCountries.findIndex(
|
|
c => c.value === country!.value
|
|
) : this.selectedVisitCountries.findIndex(
|
|
c => c.value === country!.value
|
|
);
|
|
|
|
return existingIndex > -1;
|
|
}
|
|
|
|
// Remove country from visits
|
|
removeVisitCountry(): void {
|
|
if (!this.selectedVisitCountry) return;
|
|
|
|
this.selectedVisitCountries = this.selectedVisitCountries.filter(
|
|
c => c.value !== this.selectedVisitCountry!.value
|
|
);
|
|
|
|
if (this.applicationType === 'additional') {
|
|
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
|
|
}
|
|
}
|
|
|
|
// Remove country from transits
|
|
removeTransitCountry(): void {
|
|
if (!this.selectedTransitCountry) return;
|
|
|
|
this.selectedTransitCountries = this.selectedTransitCountries.filter(
|
|
c => c.value !== this.selectedTransitCountry!.value
|
|
);
|
|
}
|
|
|
|
// Calculate total visits
|
|
get totalVisits(): number {
|
|
return this.selectedVisitCountries.reduce((sum, country) => sum + country.visits, 0);
|
|
}
|
|
|
|
// Calculate total transits
|
|
get totalTransits(): number {
|
|
return this.selectedTransitCountries.reduce((sum, country) => sum + country.visits, 0);
|
|
}
|
|
|
|
// Check if a country is already selected (visit)
|
|
isVisitCountrySelected(country: Country): boolean {
|
|
return this.selectedVisitCountries.some(c => c.value === country.value)
|
|
}
|
|
|
|
// Check if a country is already selected (transit)
|
|
isTransitCountrySelected(country: Country): boolean {
|
|
return this.selectedTransitCountries.some(c => c.value === country.value);
|
|
}
|
|
|
|
loadTravelPlan(): void {
|
|
this.isLoading = true;
|
|
|
|
forkJoin({
|
|
countries: this.travelPlanService.getCountriesAndMessages(),
|
|
travelPlanData: this.travelPlanService.getTravelPlan(this.headerid)
|
|
}).pipe(finalize(() => {
|
|
this.isLoading = false;
|
|
})).subscribe({
|
|
next: (results) => {
|
|
this.countries = results.countries as Country[];
|
|
this.patchTravelPlanData(results.travelPlanData);
|
|
},
|
|
error: (error) => {
|
|
console.error('Error loading travel plan data', error);
|
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load travel plan data');
|
|
this.notificationService.showError(errorMessage);
|
|
}
|
|
});
|
|
}
|
|
|
|
private patchTravelPlanData(plan: TravelPlan): void {
|
|
|
|
this.selectedVisitCountries = plan?.entries;
|
|
this.selectedTransitCountries = plan?.transits;
|
|
|
|
if (this.applicationType === 'additional') {
|
|
this.needsInsurance.emit(this.selectedVisitCountries.length > 0)
|
|
}
|
|
|
|
// Patch form values
|
|
this.travelForm.patchValue({
|
|
usaEntries: plan?.usSets === 0 ? '' : plan?.usSets,
|
|
visitsInput: 1,
|
|
transitsInput: 1 // Reset to default
|
|
});
|
|
|
|
if (this.isViewMode) {
|
|
this.travelForm.disable();
|
|
}
|
|
|
|
if (this.applicationType === 'additional') {
|
|
const usentriesControl = this.travelForm.get('usaEntries');
|
|
usentriesControl?.setValidators([Validators.min(1), Validators.max(99)]);
|
|
usentriesControl?.updateValueAndValidity();
|
|
}
|
|
|
|
this.completed.emit(this.travelForm.valid || this.travelForm.disabled
|
|
|| (this.applicationType === 'additional' && (this.travelForm.value.usaEntries
|
|
|| this.selectedVisitCountries.length > 0 || this.selectedTransitCountries.length > 0)));
|
|
}
|
|
|
|
onSubmit(): void {
|
|
if (this.travelForm.invalid) {
|
|
this.travelForm.markAllAsTouched();
|
|
return;
|
|
}
|
|
|
|
this.showAdditionalSetsValidationMessage = false;
|
|
if (this.applicationType === 'additional' && (!this.travelForm.value.usaEntries &&
|
|
this.selectedVisitCountries.length === 0 &&
|
|
this.selectedTransitCountries.length === 0
|
|
)) {
|
|
this.showAdditionalSetsValidationMessage = true;
|
|
return;
|
|
}
|
|
|
|
const travelPlan: TravelPlan = {
|
|
usSets: this.travelForm.value.usaEntries,
|
|
entries: this.selectedVisitCountries,
|
|
transits: this.selectedTransitCountries
|
|
};
|
|
|
|
this.changeInProgress = true;
|
|
|
|
this.travelPlanService.saveTravelPlan(this.headerid, travelPlan).pipe(finalize(() => {
|
|
this.changeInProgress = false;
|
|
})).subscribe({
|
|
next: () => {
|
|
this.notificationService.showSuccess('Travel plan saved successfully');
|
|
this.completed.emit(true);
|
|
},
|
|
error: (error) => {
|
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save travel plan');
|
|
this.notificationService.showError(errorMessage);
|
|
console.error('Error saving travel plan data : ', error);
|
|
}
|
|
});
|
|
}
|
|
}
|