travel plan and terms & conditions page updates
This commit is contained in:
parent
377b062ace
commit
e89f968786
@ -9,6 +9,7 @@ export const serverRoutes: ServerRoute[] = [
|
|||||||
{ path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client },
|
{ path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client },
|
||||||
{ path: ':appId/add-holder', renderMode: RenderMode.Client },
|
{ path: ':appId/add-holder', renderMode: RenderMode.Client },
|
||||||
{ path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client },
|
{ path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client },
|
||||||
|
{ path: ':appId/terms', renderMode: RenderMode.Client },
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
renderMode: RenderMode.Prerender
|
renderMode: RenderMode.Prerender
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const routes: Routes = [
|
|||||||
{ path: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
|
{ path: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
|
||||||
{ path: 'add-holder', loadComponent: () => import('./holder/add/add-holder.component').then(m => m.AddHolderComponent) },
|
{ path: 'add-holder', loadComponent: () => import('./holder/add/add-holder.component').then(m => m.AddHolderComponent) },
|
||||||
{ path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit-holder.component').then(m => m.EditHolderComponent) },
|
{ path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit-holder.component').then(m => m.EditHolderComponent) },
|
||||||
|
{ path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) },
|
||||||
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||||
],
|
],
|
||||||
canActivate: [AuthGuard, AppIdGuard]
|
canActivate: [AuthGuard, AppIdGuard]
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
[editable]="stepsCompleted.applicationDetail">
|
[editable]="stepsCompleted.applicationDetail">
|
||||||
<ng-template matStepLabel>Holder Selection</ng-template>
|
<ng-template matStepLabel>Holder Selection</ng-template>
|
||||||
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"
|
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"
|
||||||
(updated)="onHolderSelectionUpdated($event)">
|
[applicationName]="applicationName" (updated)="onHolderSelectionUpdated($event)">
|
||||||
</app-holder>
|
</app-holder>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
@ -41,8 +41,14 @@
|
|||||||
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
||||||
<ng-template matStepLabel>Shipping & Payment</ng-template>
|
<ng-template matStepLabel>Shipping & Payment</ng-template>
|
||||||
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"
|
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"
|
||||||
|
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted"
|
||||||
[refreshShippingData]="refreshShippingData">
|
[refreshShippingData]="refreshShippingData">
|
||||||
</app-shipping>
|
</app-shipping>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
<!-- Icon overrides. -->
|
||||||
|
<ng-template matStepperIcon="edit">
|
||||||
|
<mat-icon>done</mat-icon>
|
||||||
|
</ng-template>
|
||||||
</mat-stepper>
|
</mat-stepper>
|
||||||
</div>
|
</div>
|
||||||
@ -25,8 +25,10 @@ export class AddCarnetComponent {
|
|||||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
||||||
isEditMode = false;
|
isEditMode = false;
|
||||||
headerid: number = 0;
|
headerid: number = 0;
|
||||||
|
applicationName: string = '';
|
||||||
userPreferences: UserPreferences;
|
userPreferences: UserPreferences;
|
||||||
refreshShippingData: boolean = false;
|
refreshShippingData: boolean = false;
|
||||||
|
allSectionsCompleted: boolean = false;
|
||||||
|
|
||||||
// Track completion of each step
|
// Track completion of each step
|
||||||
stepsCompleted = {
|
stepsCompleted = {
|
||||||
@ -47,12 +49,14 @@ export class AddCarnetComponent {
|
|||||||
|
|
||||||
onApplicationDetailCreated(data: { headerid: number, applicationName: string }): void {
|
onApplicationDetailCreated(data: { headerid: number, applicationName: string }): void {
|
||||||
this.headerid = data.headerid;
|
this.headerid = data.headerid;
|
||||||
|
this.applicationName = data.applicationName;
|
||||||
this.stepsCompleted.applicationDetail = true;
|
this.stepsCompleted.applicationDetail = true;
|
||||||
this.isLinear = false; // Disable linear mode after application detail is created
|
this.isLinear = false; // Disable linear mode after application detail is created
|
||||||
}
|
}
|
||||||
|
|
||||||
onHolderSelectionSaved(completed: boolean): void {
|
onHolderSelectionSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.holderSelection = completed;
|
this.stepsCompleted.holderSelection = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onHolderSelectionUpdated(updated: boolean): void {
|
onHolderSelectionUpdated(updated: boolean): void {
|
||||||
@ -61,13 +65,22 @@ export class AddCarnetComponent {
|
|||||||
|
|
||||||
onGoodsSectionSaved(completed: boolean): void {
|
onGoodsSectionSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.goodsSection = completed;
|
this.stepsCompleted.goodsSection = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTravelPlanSaved(completed: boolean): void {
|
onTravelPlanSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.travelPlan = completed;
|
this.stepsCompleted.travelPlan = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onShippingSaved(completed: boolean): void {
|
onShippingSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.shipping = completed;
|
this.stepsCompleted.shipping = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllSectionsCompleted(): void {
|
||||||
|
this.allSectionsCompleted = this.stepsCompleted.applicationDetail
|
||||||
|
&& this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection
|
||||||
|
&& this.stepsCompleted.shipping && this.stepsCompleted.travelPlan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,8 +41,14 @@
|
|||||||
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
||||||
<ng-template matStepLabel>Shipping & Payment</ng-template>
|
<ng-template matStepLabel>Shipping & Payment</ng-template>
|
||||||
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
|
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
|
||||||
|
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted"
|
||||||
[refreshShippingData]="refreshShippingData">
|
[refreshShippingData]="refreshShippingData">
|
||||||
</app-shipping>
|
</app-shipping>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
<!-- Icon overrides. -->
|
||||||
|
<ng-template matStepperIcon="edit">
|
||||||
|
<mat-icon>done</mat-icon>
|
||||||
|
</ng-template>
|
||||||
</mat-stepper>
|
</mat-stepper>
|
||||||
</div>
|
</div>
|
||||||
@ -28,6 +28,7 @@ export class EditCarnetComponent {
|
|||||||
userPreferences: UserPreferences;
|
userPreferences: UserPreferences;
|
||||||
applicationName: string = '';
|
applicationName: string = '';
|
||||||
refreshShippingData: boolean = false;
|
refreshShippingData: boolean = false;
|
||||||
|
allSectionsCompleted: boolean = false;
|
||||||
|
|
||||||
// Track completion of each step
|
// Track completion of each step
|
||||||
stepsCompleted = {
|
stepsCompleted = {
|
||||||
@ -54,6 +55,8 @@ export class EditCarnetComponent {
|
|||||||
|
|
||||||
if (returnTo === 'holder') {
|
if (returnTo === 'holder') {
|
||||||
this.currentStep = 1;
|
this.currentStep = 1;
|
||||||
|
} else if (returnTo === 'shipping') {
|
||||||
|
this.currentStep = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,21 +66,32 @@ export class EditCarnetComponent {
|
|||||||
|
|
||||||
onHolderSelectionSaved(completed: boolean): void {
|
onHolderSelectionSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.holderSelection = completed;
|
this.stepsCompleted.holderSelection = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onGoodsSectionSaved(completed: boolean): void {
|
onGoodsSectionSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.goodsSection = completed;
|
this.stepsCompleted.goodsSection = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onHolderSelectionUpdated(updated: boolean): void {
|
onHolderSelectionUpdated(updated: boolean): void {
|
||||||
this.refreshShippingData = updated;
|
this.refreshShippingData = updated;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTravelPlanSaved(completed: boolean): void {
|
onTravelPlanSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.travelPlan = completed;
|
this.stepsCompleted.travelPlan = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
onShippingSaved(completed: boolean): void {
|
onShippingSaved(completed: boolean): void {
|
||||||
this.stepsCompleted.shipping = completed;
|
this.stepsCompleted.shipping = completed;
|
||||||
|
this.isAllSectionsCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllSectionsCompleted() {
|
||||||
|
this.allSectionsCompleted = this.stepsCompleted.applicationDetail
|
||||||
|
&& this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection
|
||||||
|
&& this.stepsCompleted.shipping && this.stepsCompleted.travelPlan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,6 +102,7 @@ export class GoodsComponent {
|
|||||||
next: (goodsDetail: Goods) => {
|
next: (goodsDetail: Goods) => {
|
||||||
if (goodsDetail) {
|
if (goodsDetail) {
|
||||||
this.patchFormData(goodsDetail);
|
this.patchFormData(goodsDetail);
|
||||||
|
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
},
|
},
|
||||||
@ -114,7 +115,6 @@ export class GoodsComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.loadGoodsItems();
|
this.loadGoodsItems();
|
||||||
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +125,7 @@ export class GoodsComponent {
|
|||||||
next: (items: GoodsItem[]) => {
|
next: (items: GoodsItem[]) => {
|
||||||
this.dataSource.data = items;
|
this.dataSource.data = items;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
|
||||||
},
|
},
|
||||||
error: (error: any) => {
|
error: (error: any) => {
|
||||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items');
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items');
|
||||||
|
|||||||
@ -327,6 +327,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
|
<button mat-raised-button color="primary" type="button" (click)="submitApplication()"
|
||||||
|
[disabled]="!enableSubmitButton">
|
||||||
|
<span>Submit Application</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit" [disabled]="shippingForm.invalid || isLoading">
|
<button mat-raised-button color="primary" type="submit" [disabled]="shippingForm.invalid || isLoading">
|
||||||
<span *ngIf="!isLoading">Save</span>
|
<span *ngIf="!isLoading">Save</span>
|
||||||
<mat-spinner *ngIf="isLoading" diameter="24"></mat-spinner>
|
<mat-spinner *ngIf="isLoading" diameter="24"></mat-spinner>
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import { ShippingAddress } from '../../core/models/carnet/shipping-address';
|
|||||||
import { ContactDialogComponent } from './contact-dialog.component';
|
import { ContactDialogComponent } from './contact-dialog.component';
|
||||||
import { HolderService } from '../../core/services/carnet/holder.service';
|
import { HolderService } from '../../core/services/carnet/holder.service';
|
||||||
import { Holder } from '../../core/models/carnet/holder';
|
import { Holder } from '../../core/models/carnet/holder';
|
||||||
|
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||||
|
import { StorageService } from '../../core/services/common/storage.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shipping',
|
selector: 'app-shipping',
|
||||||
@ -32,6 +34,9 @@ export class ShippingComponent {
|
|||||||
@Input() headerid: number = 0;
|
@Input() headerid: number = 0;
|
||||||
@Input() isEditMode = false;
|
@Input() isEditMode = false;
|
||||||
@Input() refreshShippingData = false;
|
@Input() refreshShippingData = false;
|
||||||
|
@Input() enableSubmitButton = false;
|
||||||
|
@Input() applicationName: string = '';
|
||||||
|
|
||||||
@Output() completed = new EventEmitter<boolean>();
|
@Output() completed = new EventEmitter<boolean>();
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
@ -42,6 +47,8 @@ export class ShippingComponent {
|
|||||||
private errorHandler = inject(ApiErrorHandlerService);
|
private errorHandler = inject(ApiErrorHandlerService);
|
||||||
private commonService = inject(CommonService);
|
private commonService = inject(CommonService);
|
||||||
private holderService = inject(HolderService);
|
private holderService = inject(HolderService);
|
||||||
|
private navigationService = inject(NavigationService);
|
||||||
|
private storageService = inject(StorageService);
|
||||||
|
|
||||||
shippingForm: FormGroup;
|
shippingForm: FormGroup;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@ -400,6 +407,8 @@ export class ShippingComponent {
|
|||||||
notes: shipping.contact?.notes,
|
notes: shipping.contact?.notes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.completed.emit(this.shippingForm.valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateShippingValidation(shipTo: string): void {
|
updateShippingValidation(shipTo: string): void {
|
||||||
@ -585,4 +594,13 @@ export class ShippingComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submitApplication(): void {
|
||||||
|
if (this.headerid) {
|
||||||
|
let currentApplicationDetails = { headerid: this.headerid, applicationName: this.applicationName }
|
||||||
|
this.storageService.set("currentapplication", currentApplicationDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navigationService.navigate(["terms"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
104
src/app/carnet/terms-conditions/terms-conditions.component.html
Normal file
104
src/app/carnet/terms-conditions/terms-conditions.component.html
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<div class="terms-container">
|
||||||
|
<mat-card class="terms-card">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>ATA Carnet Terms and Conditions</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<mat-card-content class="terms-content">
|
||||||
|
<!-- Comprehensive Carnet Terms Content -->
|
||||||
|
<h3>1. Definitions</h3>
|
||||||
|
<p><strong>ATA Carnet:</strong> An international customs document that permits the tax-free and duty-free
|
||||||
|
temporary export and import of goods for up to one year.</p>
|
||||||
|
<p><strong>Holder:</strong> The individual or entity in whose name the carnet is issued and who is
|
||||||
|
responsible for complying with all terms.</p>
|
||||||
|
<p><strong>Goods:</strong> Merchandise, equipment, or products covered by the carnet.</p>
|
||||||
|
|
||||||
|
<h3>2. Carnet Usage</h3>
|
||||||
|
<p>2.1 The carnet may be used for:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Commercial samples</li>
|
||||||
|
<li>Professional equipment</li>
|
||||||
|
<li>Goods for exhibitions and fairs</li>
|
||||||
|
<li>Goods for scientific, educational, or cultural purposes</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>2.2 The carnet cannot be used for:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Consumable or disposable items</li>
|
||||||
|
<li>Goods for processing or repair</li>
|
||||||
|
<li>Goods intended for sale or permanent export</li>
|
||||||
|
<li>Prohibited or restricted items under any applicable laws</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3. Holder Responsibilities</h3>
|
||||||
|
<p>3.1 The holder must:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Present the carnet to customs when crossing borders</li>
|
||||||
|
<li>Ensure all goods listed in the carnet are returned by the expiration date</li>
|
||||||
|
<li>Pay all applicable duties and taxes if goods are not re-exported</li>
|
||||||
|
<li>Notify the issuing association immediately of any lost or stolen carnets</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>4. Validity Period</h3>
|
||||||
|
<p>4.1 The carnet is valid for one year from the date of issue.</p>
|
||||||
|
<p>4.2 Goods must be re-exported before the carnet expires.</p>
|
||||||
|
<p>4.3 Extensions may be granted in exceptional circumstances with approval from all relevant customs
|
||||||
|
authorities.</p>
|
||||||
|
|
||||||
|
<h3>5. Customs Procedures</h3>
|
||||||
|
<p>5.1 The holder must present the carnet to customs:</p>
|
||||||
|
<ul>
|
||||||
|
<li>When first exporting goods from the home country</li>
|
||||||
|
<li>When entering each foreign country</li>
|
||||||
|
<li>When re-exporting goods from each foreign country</li>
|
||||||
|
<li>When finally re-importing goods to the home country</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>6. Security Requirements</h3>
|
||||||
|
<p>6.1 The issuing association may require security up to 40% of the total value of goods.</p>
|
||||||
|
<p>6.2 Security will be refunded when the carnet is fully discharged.</p>
|
||||||
|
<p>6.3 The security may be forfeited if terms are violated.</p>
|
||||||
|
|
||||||
|
<h3>7. Liability and Insurance</h3>
|
||||||
|
<p>7.1 The holder is solely responsible for:</p>
|
||||||
|
<ul>
|
||||||
|
<li>All customs duties and taxes if goods are not re-exported</li>
|
||||||
|
<li>Any damage to goods while in transit</li>
|
||||||
|
<li>Compliance with all import/export regulations</li>
|
||||||
|
</ul>
|
||||||
|
<p>7.2 We recommend obtaining comprehensive insurance coverage for all goods.</p>
|
||||||
|
|
||||||
|
<h3>8. Fees and Charges</h3>
|
||||||
|
<p>8.1 The following fees apply:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Issuance fee: 1.5% of total goods value (minimum $250)</li>
|
||||||
|
<li>Processing fee: $75 per application</li>
|
||||||
|
<li>Amendment fee: $50 per change after issuance</li>
|
||||||
|
<li>Late discharge fee: $100 if not returned within 30 days of expiration</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>9. Dispute Resolution</h3>
|
||||||
|
<p>9.1 Any disputes shall be resolved through arbitration in the jurisdiction of the issuing association.
|
||||||
|
</p>
|
||||||
|
<p>9.2 The holder agrees to be bound by the arbitration decision.</p>
|
||||||
|
|
||||||
|
<h3>10. Governing Law</h3>
|
||||||
|
<p>These terms shall be governed by the laws of the country where the carnet was issued.</p>
|
||||||
|
|
||||||
|
<mat-checkbox [(ngModel)]="hasReadAllTerms" color="primary" class="read-checkbox">
|
||||||
|
I have read and agree to all terms and conditions above
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<mat-card-actions align="end">
|
||||||
|
<button mat-raised-button color="primary" (click)="onAccept()" [disabled]="!hasReadAllTerms">
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
<button mat-button (click)="onDecline()">Decline</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
.terms-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-card {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-header {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-checkbox {
|
||||||
|
margin: 24px 0;
|
||||||
|
display: block;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom scrollbar
|
||||||
|
.terms-content::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-content::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-content::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive adjustments
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.terms-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-card {
|
||||||
|
max-height: 95vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||||
|
import { CommonService } from '../../core/services/common/common.service';
|
||||||
|
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||||
|
import { NotificationService } from '../../core/services/common/notification.service';
|
||||||
|
import { StorageService } from '../../core/services/common/storage.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-terms-conditions',
|
||||||
|
imports: [MatCardModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatDividerModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule],
|
||||||
|
templateUrl: './terms-conditions.component.html',
|
||||||
|
styleUrl: './terms-conditions.component.scss'
|
||||||
|
})
|
||||||
|
export class TermsConditionsComponent {
|
||||||
|
|
||||||
|
currentDate = new Date();
|
||||||
|
hasReadAllTerms = false;
|
||||||
|
currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
|
||||||
|
|
||||||
|
private notificationService = inject(NotificationService);
|
||||||
|
private commonService = inject(CommonService);
|
||||||
|
private errorHandler = inject(ApiErrorHandlerService);
|
||||||
|
private storageService = inject(StorageService);
|
||||||
|
private navigationService = inject(NavigationService);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string }>('currentapplication')
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccept(): void {
|
||||||
|
console.log('Terms accepted');
|
||||||
|
}
|
||||||
|
|
||||||
|
onDecline(): void {
|
||||||
|
this.storageService.removeItem('currentapplication')
|
||||||
|
this.navigationService.navigate(["edit-carnet", this.currentApplicationDetails?.headerid],
|
||||||
|
{
|
||||||
|
state: { isEditMode: true },
|
||||||
|
queryParams: {
|
||||||
|
applicationname: this.currentApplicationDetails?.applicationName,
|
||||||
|
return: 'shipping'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,149 @@
|
|||||||
<p>travel-plan works!</p>
|
<div class="travel-plan-container">
|
||||||
|
<form [formGroup]="travelForm" (ngSubmit)="onSubmit()">
|
||||||
|
<!-- USA Entries -->
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline" class="usa-entries">
|
||||||
|
<mat-label>No of times entering & leaving USA</mat-label>
|
||||||
|
<input matInput type="number" formControlName="usaEntries" min="1" max="99">
|
||||||
|
<mat-error *ngIf="travelForm.get('usaEntries')?.hasError('required')">
|
||||||
|
Required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error
|
||||||
|
*ngIf="travelForm.get('usaEntries')?.hasError('min') || travelForm.get('usaEntries')?.hasError('max')">
|
||||||
|
Must be between 1-99
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="country-selection-container">
|
||||||
|
<!-- Available Countries Listbox -->
|
||||||
|
<div class="available-countries">
|
||||||
|
<h3>Visit Countries</h3>
|
||||||
|
|
||||||
|
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
|
||||||
|
<mat-list-option *ngFor="let country of countries" [value]="country"
|
||||||
|
[disabled]="isVisitCountrySelected(country)"
|
||||||
|
(click)="onAvailableVisitCountrySelection(visitavailableoption)" #visitavailableoption>
|
||||||
|
{{country.name}}
|
||||||
|
</mat-list-option>
|
||||||
|
</mat-selection-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="controls">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>No of times</mat-label>
|
||||||
|
<input matInput type="number" formControlName="visitsInput" min="1" max="99"
|
||||||
|
[(ngModel)]="visitsCount">
|
||||||
|
<mat-error
|
||||||
|
*ngIf="travelForm.get('visitsInput')?.hasError('min') || travelForm.get('visitsInput')?.hasError('max')">
|
||||||
|
Must be between 1-99
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button mat-raised-button color="primary" type="button" (click)="addVisitCountry()"
|
||||||
|
class="icon-end">
|
||||||
|
<mat-icon>keyboard_double_arrow_right</mat-icon>
|
||||||
|
<span> Add</span>
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button type="button" (click)="removeVisitCountry()">
|
||||||
|
<mat-icon>keyboard_double_arrow_left</mat-icon>
|
||||||
|
<span> Remove</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Selected Countries -->
|
||||||
|
<div class="selected-countries">
|
||||||
|
<div class="visit-countries">
|
||||||
|
<h3>Selected</h3>
|
||||||
|
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
|
||||||
|
<mat-list-option *ngFor="let country of selectedVisitCountries" [value]="country"
|
||||||
|
(click)="onVisitCountrySelection(visitoption)" #visitoption>
|
||||||
|
{{country.name}} ({{country.visits}})
|
||||||
|
</mat-list-option>
|
||||||
|
<mat-list-item *ngIf="selectedVisitCountries.length === 0">
|
||||||
|
<span class="empty-message">No visit countries selected</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-selection-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="country-selection-container">
|
||||||
|
<!-- Available Countries Listbox -->
|
||||||
|
<div class="available-countries">
|
||||||
|
<h3>Transit Countries</h3>
|
||||||
|
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
|
||||||
|
<mat-list-option *ngFor="let country of countries" [value]="country"
|
||||||
|
[disabled]="isTransitCountrySelected(country)"
|
||||||
|
(click)="onAvailableTransitCountrySelection(transitavailableoption)" #transitavailableoption>
|
||||||
|
{{country.name}}
|
||||||
|
</mat-list-option>
|
||||||
|
</mat-selection-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="controls">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>No of times</mat-label>
|
||||||
|
<input matInput type="number" formControlName="transitsInput" min="1" max="99"
|
||||||
|
[(ngModel)]="transitsCount">
|
||||||
|
<mat-error
|
||||||
|
*ngIf="travelForm.get('transitsInput')?.hasError('min') || travelForm.get('transitsInput')?.hasError('max')">
|
||||||
|
Must be between 1-99
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button mat-raised-button color="primary" type="button" (click)="addTransitCountry()"
|
||||||
|
class="icon-end">
|
||||||
|
<mat-icon>keyboard_double_arrow_right</mat-icon>
|
||||||
|
<span> Add</span>
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button type="button" (click)="removeTransitCountry()">
|
||||||
|
<mat-icon>keyboard_double_arrow_left</mat-icon>
|
||||||
|
<span> Remove</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Selected Countries -->
|
||||||
|
<div class="selected-countries">
|
||||||
|
<div class="transit-countries">
|
||||||
|
<h3>Selected</h3>
|
||||||
|
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
|
||||||
|
<mat-list-option *ngFor="let country of selectedTransitCountries" [value]="country"
|
||||||
|
(click)="onTransitCountrySelection(transitoption)" #transitoption>
|
||||||
|
{{country.name}} ({{country.visits}})
|
||||||
|
</mat-list-option>
|
||||||
|
<mat-list-item *ngIf="selectedTransitCountries.length === 0">
|
||||||
|
<span class="empty-message">No transit countries selected</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-selection-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<!-- Totals -->
|
||||||
|
<div class="totals-section">
|
||||||
|
<div class="total-item">
|
||||||
|
<span>Total Visits:</span>
|
||||||
|
<span class="total-value">{{totalVisits}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-item">
|
||||||
|
<span>Total Transits:</span>
|
||||||
|
<span class="total-value">{{totalTransits}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button mat-raised-button color="primary" type="submit" [disabled]="isLoading">
|
||||||
|
<span *ngIf="!isLoading">Save</span>
|
||||||
|
<mat-spinner *ngIf="isLoading" diameter="24"></mat-spinner>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
.travel-plan-container {
|
||||||
|
padding: 8px;
|
||||||
|
// max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
// .form-row {
|
||||||
|
// margin-bottom: 24px;
|
||||||
|
// max-width: 300px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.usa-entries {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-selection-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-countries,
|
||||||
|
.selected-countries {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-selection-list {
|
||||||
|
height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
mat-list-item {
|
||||||
|
.empty-message {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
width: 150px;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
// gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.icon-end {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-countries {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.visit-countries,
|
||||||
|
.transit-countries {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.totals-section {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
.total-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.travel-container {
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,284 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { 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({
|
@Component({
|
||||||
selector: 'app-travel-plan',
|
selector: 'app-travel-plan',
|
||||||
imports: [],
|
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule, MatListModule],
|
||||||
templateUrl: './travel-plan.component.html',
|
templateUrl: './travel-plan.component.html',
|
||||||
styleUrl: './travel-plan.component.scss'
|
styleUrl: './travel-plan.component.scss'
|
||||||
})
|
})
|
||||||
export class TravelPlanComponent {
|
export class TravelPlanComponent {
|
||||||
@Input() headerid: number = 0;
|
@Input() headerid: number = 0;
|
||||||
@Output() completed = new EventEmitter<boolean>();
|
@Output() completed = 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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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.addVisitCountryToCollection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove country from visits
|
||||||
|
removeVisitCountry(): void {
|
||||||
|
if (!this.selectedVisitCountry) return;
|
||||||
|
|
||||||
|
this.selectedVisitCountries = this.selectedVisitCountries.filter(
|
||||||
|
c => c.value !== this.selectedVisitCountry!.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}).subscribe({
|
||||||
|
next: (results) => {
|
||||||
|
this.countries = results.countries as Country[];
|
||||||
|
this.patchTravelPlanData(results.travelPlanData);
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private patchTravelPlanData(plan: TravelPlan): void {
|
||||||
|
|
||||||
|
this.selectedVisitCountries = plan?.entries;
|
||||||
|
this.selectedTransitCountries = plan?.transits;
|
||||||
|
|
||||||
|
// Patch form values
|
||||||
|
this.travelForm.patchValue({
|
||||||
|
usaEntries: plan?.usSets,
|
||||||
|
visitsInput: 1,
|
||||||
|
transitsInput: 1 // Reset to default
|
||||||
|
});
|
||||||
|
|
||||||
|
this.completed.emit(this.travelForm.valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.travelForm.invalid) {
|
||||||
|
this.travelForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const travelPlan: TravelPlan = {
|
||||||
|
usSets: this.travelForm.value.usaEntries,
|
||||||
|
entries: this.selectedVisitCountries,
|
||||||
|
transits: this.selectedTransitCountries
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.travelPlanService.saveTravelPlan(this.headerid, travelPlan).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess('Travel plan saved successfully');
|
||||||
|
this.completed.emit(true);
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,17 @@
|
|||||||
export interface TravelPlan {
|
export interface TravelPlan {
|
||||||
|
usSets?: number;
|
||||||
|
entries: TravelEntry[];
|
||||||
|
transits: TravelEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Country {
|
||||||
|
name?: string;
|
||||||
|
value: string;
|
||||||
|
actionMessage?: string | null;
|
||||||
|
actionType?: string | null;
|
||||||
|
action?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TravelEntry extends Country {
|
||||||
|
visits: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { environment } from '../../../../environments/environment';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { UserService } from '../common/user.service';
|
import { UserService } from '../common/user.service';
|
||||||
import { ApplicationDetail } from '../../models/carnet/application-detail';
|
import { ApplicationDetail } from '../../models/carnet/application-detail';
|
||||||
import { filter, map, Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -15,21 +15,6 @@ export class ApplicationDetailService {
|
|||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private userService = inject(UserService);
|
private userService = inject(UserService);
|
||||||
|
|
||||||
// getApplicationDetailsById(id: number): ApplicationDetail | any {
|
|
||||||
// return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerByClientid/${this.userService.getUserSpid()}/${id}`).pipe(
|
|
||||||
// filter(response => response.length > 0),
|
|
||||||
// map(response => this.mapToApplicationDetail(response?.[0])));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private mapToApplicationDetail(applicationDetails: any): ApplicationDetail {
|
|
||||||
// return {
|
|
||||||
// clientid: applicationDetails.CLIENTID,
|
|
||||||
// spid: applicationDetails.SPID,
|
|
||||||
// name: applicationDetails.P_APPLICATIONNAME,
|
|
||||||
// applicationId: applicationDetails.P_HEADERID
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
createApplicationDetails(data: ApplicationDetail): Observable<any> {
|
createApplicationDetails(data: ApplicationDetail): Observable<any> {
|
||||||
const userDetails = this.userService.getUserDetails();
|
const userDetails = this.userService.getUserDetails();
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { environment } from '../../../../environments/environment';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { UserService } from '../common/user.service';
|
import { UserService } from '../common/user.service';
|
||||||
import { Goods, GoodsItem } from '../../models/carnet/goods';
|
import { Goods, GoodsItem } from '../../models/carnet/goods';
|
||||||
import { filter, map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -17,8 +17,7 @@ export class GoodsService {
|
|||||||
|
|
||||||
getGoodDetailsByHeaderId(headerid: number): Goods | any {
|
getGoodDetailsByHeaderId(headerid: number): Goods | any {
|
||||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetGoodsDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetGoodsDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
||||||
filter(response => response.length > 0),
|
map(response => this.mapToGoods(response)));
|
||||||
map(response => this.mapToGoods(response?.[0])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToGoods(goodDetails: any): Goods {
|
private mapToGoods(goodDetails: any): Goods {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { environment } from '../../../../environments/environment';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { UserService } from '../common/user.service';
|
import { UserService } from '../common/user.service';
|
||||||
import { Holder } from '../../models/carnet/holder';
|
import { Holder } from '../../models/carnet/holder';
|
||||||
import { filter, map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -17,8 +17,7 @@ export class HolderService {
|
|||||||
|
|
||||||
getHolder(id: number): Observable<Holder> {
|
getHolder(id: number): Observable<Holder> {
|
||||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${id}`).pipe(
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${id}`).pipe(
|
||||||
filter(response => response.length > 0),
|
map(response => this.mapToHolder(response)));
|
||||||
map(response => this.mapToHolder(response?.[0])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToHolder(holder: any): Holder {
|
private mapToHolder(holder: any): Holder {
|
||||||
|
|||||||
@ -19,8 +19,7 @@ export class ShippingService {
|
|||||||
|
|
||||||
getShippingData(headerid: number): Shipping | any {
|
getShippingData(headerid: number): Shipping | any {
|
||||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetShipPaymentDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetShipPaymentDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
||||||
filter(response => response.length > 0),
|
map(response => this.mapToShippingData(response)));
|
||||||
map(response => this.mapToShippingData(response?.[0])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToShippingData(shippingDetails: any): Shipping {
|
private mapToShippingData(shippingDetails: any): Shipping {
|
||||||
|
|||||||
@ -1,9 +1,93 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { Observable, map } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { Country, TravelPlan } from '../../models/carnet/travel-plan';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class TravelPlanService {
|
export class TravelPlanService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
constructor() { }
|
private http = inject(HttpClient);
|
||||||
|
private userService = inject(UserService);
|
||||||
|
|
||||||
|
getCountriesAndMessages(): Observable<Country[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCountriesAndMessages`).pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.map((item) => ({
|
||||||
|
name: item.COUNTRYNAME,
|
||||||
|
value: item.COUNTRYCODE,
|
||||||
|
actionMessage: item.COUNTRYMESSAGE,
|
||||||
|
actionType: item.ADDLPARAMVALUE1,
|
||||||
|
action: item.ADDLPARAMVALUE2
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTravelPlan(headerid: number): Observable<TravelPlan> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCountryDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
|
||||||
|
map(response => this.mapToTravelPlanData(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToTravelPlanData(travelPlanDetails: any): TravelPlan {
|
||||||
|
|
||||||
|
if (!travelPlanDetails?.P_COUNTRYTABLE && !travelPlanDetails?.NOOFUSSETS) {
|
||||||
|
return {
|
||||||
|
entries: [],
|
||||||
|
transits: []
|
||||||
|
} as TravelPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countryTable: any[] = travelPlanDetails.P_COUNTRYTABLE;
|
||||||
|
|
||||||
|
let travelPlanData: TravelPlan = {
|
||||||
|
usSets: travelPlanDetails.NOOFUSSETS,
|
||||||
|
entries: countryTable
|
||||||
|
.filter((item) => item.VISISTTRANSITIND === 'V')
|
||||||
|
.map(item => ({
|
||||||
|
name: item.COUNTRYNAME,
|
||||||
|
value: item.COUNTRYCODE,
|
||||||
|
visits: item.NOOFTIMESENTLEAVE
|
||||||
|
})),
|
||||||
|
transits: countryTable
|
||||||
|
.filter(item => item.VISISTTRANSITIND === 'T')
|
||||||
|
.map(item => ({
|
||||||
|
name: item.COUNTRYNAME,
|
||||||
|
value: item.COUNTRYCODE,
|
||||||
|
visits: item.NOOFTIMESENTLEAVE
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
return travelPlanData;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTravelPlan(headerid: number, travelPlan: TravelPlan): Observable<TravelPlan> {
|
||||||
|
|
||||||
|
const countryTable: any[] = [
|
||||||
|
...travelPlan.entries.map(entry => ({
|
||||||
|
P_VISISTTRANSITIND: 'V' as const,
|
||||||
|
COUNTRYCODE: entry.value,
|
||||||
|
NOOFTIMESENTLEAVE: entry.visits
|
||||||
|
})),
|
||||||
|
...travelPlan.transits.map(transit => ({
|
||||||
|
P_VISISTTRANSITIND: 'T' as const,
|
||||||
|
COUNTRYCODE: transit.value,
|
||||||
|
NOOFTIMESENTLEAVE: transit.visits
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
let travelPlanData = {
|
||||||
|
P_HEADERID: headerid,
|
||||||
|
P_USSETS: travelPlan.usSets,
|
||||||
|
P_COUNTRYTABLE: countryTable,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post<TravelPlan>(`${this.apiUrl}/${this.apiDb}/AddCountries`, travelPlanData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user