application name, registration and forgot password updates
This commit is contained in:
parent
f9fe0569d3
commit
716f063ecf
@ -1,20 +1,19 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { AppIdGuard } from './guards/appid.guard';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
||||
import { UserSettingsComponent } from './user-settings/user-settings.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: '404', component: NotFoundComponent },
|
||||
{ path: 'login', loadComponent: () => import('./login/login.component').then(m => m.LoginComponent) },
|
||||
{ path: 'forgot-password', loadComponent: () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent) },
|
||||
{ path: 'register/:token', loadComponent: () => import('./register/register.component').then(m => m.RegisterComponent) },
|
||||
{ path: '404', loadComponent: () => import('./shared/components/not-found/not-found.component').then(m => m.NotFoundComponent) },
|
||||
{
|
||||
path: ':appId',
|
||||
children: [
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'usersettings', component: UserSettingsComponent },
|
||||
{ path: 'home', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) },
|
||||
{ path: 'usersettings', loadComponent: () => import('./user-settings/user-settings.component').then(m => m.UserSettingsComponent) },
|
||||
{ path: 'add-carnet', loadComponent: () => import('./carnet/add/add-carnet.component').then(m => m.AddCarnetComponent) },
|
||||
{ path: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||
],
|
||||
canActivate: [AuthGuard, AppIdGuard]
|
||||
|
||||
@ -1,64 +1,21 @@
|
||||
<div class="client-carnet-container">
|
||||
<!-- Initial Questions Section -->
|
||||
<div *ngIf="!questionsCompleted" class="questions-section">
|
||||
<h2>Add New Carnet</h2>
|
||||
|
||||
<form [formGroup]="questionsForm" (ngSubmit)="onQuestionsSubmit()">
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="newCarnet">
|
||||
<mat-label>Do you need a new carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="additionalSets">
|
||||
<mat-label>Do you need additional sets?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="duplicateCarnet">
|
||||
<mat-label>Did you lose your carnet? Are you looking for duplicates?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="extendCarnet">
|
||||
<mat-label>Do you need to extend the carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="!questionsForm.valid">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Stepper Section (shown after questions are answered) -->
|
||||
<mat-stepper *ngIf="questionsCompleted" orientation="vertical" [linear]="isLinear"
|
||||
(selectionChange)="onStepChange($event)" [selectedIndex]="currentStep">
|
||||
<mat-stepper orientation="vertical" [linear]="isLinear" (selectionChange)="onStepChange($event)"
|
||||
[selectedIndex]="currentStep">
|
||||
|
||||
<!-- Application Name Step -->
|
||||
<mat-step [completed]="stepsCompleted.applicationDetail" [editable]="stepsCompleted.applicationDetail">
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Application Name</ng-template>
|
||||
<app-application [isEditMode]="isEditMode" (applicationIdCreated)="onApplicationDetailCreated($event)">
|
||||
<app-application [isEditMode]="isEditMode" (headerIdCreated)="onApplicationDetailCreated($event)">
|
||||
</app-application>
|
||||
</mat-step>
|
||||
|
||||
<!-- Holder Selection Step -->
|
||||
<mat-step [completed]="stepsCompleted.holderSelection" [editable]="stepsCompleted.applicationDetail">
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Holder Selection</ng-template>
|
||||
<app-holder (completed)="onHolderSelectionSaved($event)">
|
||||
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid">
|
||||
</app-holder>
|
||||
</mat-step>
|
||||
|
||||
@ -66,44 +23,45 @@
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Goods Section</ng-template>
|
||||
<app-goods (completed)="onGoodsSectionSaved($event)">
|
||||
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid">
|
||||
</app-goods>
|
||||
</mat-step>
|
||||
|
||||
<!-- Travel Plan Step -->
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend'"
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'"
|
||||
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Travel Plan</ng-template>
|
||||
<!-- <app-travel-plan (completed)="onTravelPlanSaved($event)">
|
||||
</app-travel-plan> -->
|
||||
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid">
|
||||
</app-travel-plan>
|
||||
</mat-step>
|
||||
|
||||
<!-- Insurance Step -->
|
||||
<mat-step [completed]="stepsCompleted.insurance" [editable]="stepsCompleted.applicationDetail">
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'"
|
||||
[completed]="stepsCompleted.insurance" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Insurance</ng-template>
|
||||
<!-- <app-insurance (completed)="onInsuranceSaved($event)">
|
||||
</app-insurance> -->
|
||||
<app-insurance (completed)="onInsuranceSaved($event)" [headerid]="headerid">
|
||||
</app-insurance>
|
||||
</mat-step>
|
||||
|
||||
<!-- Shipping Step -->
|
||||
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Shipping</ng-template>
|
||||
<!-- <app-shipping (completed)="onShippingSaved($event)">
|
||||
</app-shipping> -->
|
||||
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid">
|
||||
</app-shipping>
|
||||
</mat-step>
|
||||
|
||||
<!-- Delivery Method Step -->
|
||||
<mat-step [completed]="stepsCompleted.deliveryMethod" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Delivery Method</ng-template>
|
||||
<!-- <app-delivery-method (completed)="onDeliveryMethodSaved($event)">
|
||||
</app-delivery-method> -->
|
||||
<app-delivery (completed)="onDeliveryMethodSaved($event)" [headerid]="headerid">
|
||||
</app-delivery>
|
||||
</mat-step>
|
||||
|
||||
<!-- Payment Step -->
|
||||
<mat-step [completed]="stepsCompleted.payment" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Payment</ng-template>
|
||||
<!-- <app-payment (completed)="onPaymentSaved($event)">
|
||||
</app-payment> -->
|
||||
<app-payment (completed)="onPaymentSaved($event)" [headerid]="headerid">
|
||||
</app-payment>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
@ -1,51 +1,11 @@
|
||||
.client-carnet-container {
|
||||
// padding: 24px;
|
||||
// max-width: 1200px;
|
||||
// margin: 0 auto;
|
||||
// .client-carnet-container {
|
||||
// // padding: 24px;
|
||||
// // max-width: 1200px;
|
||||
// // margin: 0 auto;
|
||||
|
||||
.questions-section {
|
||||
// padding: 20px;
|
||||
// border-radius: 8px;
|
||||
// margin-bottom: 24px;
|
||||
|
||||
// h2 {
|
||||
// margin-bottom: 24px;
|
||||
// color: #3f51b5;
|
||||
// }
|
||||
|
||||
.question-row {
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem;
|
||||
background: #ffff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
// mat-label {
|
||||
// margin-bottom: 8px;
|
||||
// font-weight: 500;
|
||||
// }
|
||||
|
||||
mat-radio-button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
// .actions {
|
||||
// margin-top: 24px;
|
||||
// display: flex;
|
||||
// justify-content: flex-start;
|
||||
// }
|
||||
// }
|
||||
@ -6,31 +6,26 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { ApplicationComponent } from "../application/application.component";
|
||||
import { HolderComponent } from '../holder/holder.component';
|
||||
import { GoodsComponent } from '../goods/goods.component';
|
||||
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
|
||||
import { InsuranceComponent } from '../insurance/insurance.component';
|
||||
import { PaymentComponent } from '../payment/payment.component';
|
||||
import { ShippingComponent } from '../shipping/shipping.component';
|
||||
import { DeliveryComponent } from '../delivery/delivery.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-carnet',
|
||||
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
|
||||
ApplicationComponent, HolderComponent, GoodsComponent],
|
||||
ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent, InsuranceComponent,
|
||||
ShippingComponent, DeliveryComponent, PaymentComponent],
|
||||
templateUrl: './add-carnet.component.html',
|
||||
styleUrl: './add-carnet.component.scss'
|
||||
})
|
||||
export class AddCarnetComponent {
|
||||
currentStep = 0;
|
||||
questionsCompleted = false;
|
||||
isLinear = true;
|
||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = null;
|
||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
||||
isEditMode = false;
|
||||
applicationid: number = 0;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
// Form group for initial questions
|
||||
questionsForm: FormGroup = this.fb.group({
|
||||
newCarnet: [null, Validators.required],
|
||||
additionalSets: [false],
|
||||
duplicateCarnet: [false],
|
||||
extendCarnet: [false]
|
||||
});
|
||||
headerid: number = 0;
|
||||
|
||||
// Track completion of each step
|
||||
stepsCompleted = {
|
||||
@ -44,30 +39,12 @@ export class AddCarnetComponent {
|
||||
payment: false
|
||||
};
|
||||
|
||||
onQuestionsSubmit(): void {
|
||||
const answers = this.questionsForm.value;
|
||||
|
||||
if (answers.newCarnet) {
|
||||
this.applicationType = 'new';
|
||||
} else if (answers.additionalSets) {
|
||||
this.applicationType = 'additional';
|
||||
} else if (answers.duplicateCarnet) {
|
||||
this.applicationType = 'duplicate';
|
||||
} else if (answers.extendCarnet) {
|
||||
this.applicationType = 'extend';
|
||||
}
|
||||
|
||||
// Move to the first step after questions
|
||||
this.questionsCompleted = true;
|
||||
this.currentStep = 0;
|
||||
}
|
||||
|
||||
onStepChange(event: StepperSelectionEvent): void {
|
||||
this.currentStep = event.selectedIndex;
|
||||
}
|
||||
|
||||
onApplicationDetailCreated(applicationid: number): void {
|
||||
this.applicationid = applicationid;
|
||||
onApplicationDetailCreated(headerid: number): void {
|
||||
this.headerid = this.headerid;
|
||||
this.stepsCompleted.applicationDetail = true;
|
||||
this.isLinear = false; // Disable linear mode after application detail is created
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { ApplicationDetail } from '../../core/models/carnet/application-detail';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-application',
|
||||
@ -16,9 +17,10 @@ import { CommonModule } from '@angular/common';
|
||||
})
|
||||
export class ApplicationComponent {
|
||||
@Input() isEditMode = false;
|
||||
@Input() applicationid: number = 0;
|
||||
@Input() headerid: number = 0;
|
||||
@Input() applicationName: string = '';
|
||||
|
||||
@Output() applicationIdCreated = new EventEmitter<number>();
|
||||
@Output() headerIdCreated = new EventEmitter<number>();
|
||||
|
||||
applicationDetailsForm: FormGroup;
|
||||
isLoading = false;
|
||||
@ -30,31 +32,33 @@ export class ApplicationComponent {
|
||||
private applicationDetailService = inject(ApplicationDetailService);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
constructor() {
|
||||
this.applicationDetailsForm = this.createForm();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.spidCreated.emit(this.spid?.toString());
|
||||
this.applicationName = this.route.snapshot.queryParamMap.get('applicationName') || '';
|
||||
|
||||
// Patch edit form data
|
||||
if (this.applicationid > 0) {
|
||||
this.isLoading = true;
|
||||
this.applicationDetailService.getApplicationDetailsById(this.applicationid).subscribe({
|
||||
next: (applicationDetail: ApplicationDetail) => {
|
||||
if (applicationDetail?.applicationId > 0) {
|
||||
this.patchFormData(applicationDetail);
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error: any) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load application details');
|
||||
this.notificationService.showError(errorMessage);
|
||||
this.isLoading = false;
|
||||
console.error('Error loading application details:', error);
|
||||
}
|
||||
});
|
||||
if (this.applicationName) {
|
||||
this.patchFormData();
|
||||
|
||||
// this.applicationDetailService.getApplicationDetailsById(this.headerid).subscribe({
|
||||
// next: (applicationDetail: ApplicationDetail) => {
|
||||
// if (applicationDetail?.applicationId > 0) {
|
||||
// this.patchFormData(applicationDetail);
|
||||
// }
|
||||
// this.isLoading = false;
|
||||
// },
|
||||
// error: (error: any) => {
|
||||
// let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load application details');
|
||||
// this.notificationService.showError(errorMessage);
|
||||
// this.isLoading = false;
|
||||
// console.error('Error loading application details:', error);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,9 +73,9 @@ export class ApplicationComponent {
|
||||
});
|
||||
}
|
||||
|
||||
patchFormData(data: ApplicationDetail): void {
|
||||
patchFormData(): void {
|
||||
this.applicationDetailsForm.patchValue({
|
||||
name: data.name,
|
||||
name: this.applicationName,
|
||||
});
|
||||
|
||||
if (this.isEditMode) {
|
||||
@ -93,12 +97,12 @@ export class ApplicationComponent {
|
||||
|
||||
const applicationDetailData: ApplicationDetail = this.applicationDetailsForm.value;
|
||||
|
||||
if (!this.isEditMode && this.applicationid == 0) {
|
||||
if (!this.isEditMode && this.headerid == 0) {
|
||||
this.isLoading = true;
|
||||
this.applicationDetailService.createApplicationDetails(applicationDetailData).subscribe({
|
||||
next: (applicationData: any) => {
|
||||
this.notificationService.showSuccess(`Application details added successfully`);
|
||||
this.applicationIdCreated.emit(applicationData.P_HEADERID);
|
||||
this.headerIdCreated.emit(applicationData.P_HEADERID);
|
||||
this.applicationDetailsForm.get('name')?.disable();
|
||||
this.disableSaveButton = true;
|
||||
this.isLoading = false;
|
||||
|
||||
@ -1 +0,0 @@
|
||||
<p>delivery works!</p>
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery',
|
||||
@ -7,5 +7,7 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './delivery.component.scss'
|
||||
})
|
||||
export class DeliveryComponent {
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
|
||||
}
|
||||
|
||||
@ -1 +1,67 @@
|
||||
<p>edit-carnet works!</p>
|
||||
<div class="client-carnet-container">
|
||||
<!-- Stepper Section (shown after questions are answered) -->
|
||||
<mat-stepper orientation="vertical" [linear]="isLinear" (selectionChange)="onStepChange($event)"
|
||||
[selectedIndex]="currentStep">
|
||||
|
||||
<!-- Application Name Step -->
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Application Name</ng-template>
|
||||
<app-application [isEditMode]="isEditMode">
|
||||
</app-application>
|
||||
</mat-step>
|
||||
|
||||
<!-- Holder Selection Step -->
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Holder Selection</ng-template>
|
||||
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid">
|
||||
</app-holder>
|
||||
</mat-step>
|
||||
|
||||
<!-- Goods Section Step -->
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Goods Section</ng-template>
|
||||
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid">
|
||||
</app-goods>
|
||||
</mat-step>
|
||||
|
||||
<!-- Travel Plan Step -->
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'"
|
||||
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Travel Plan</ng-template>
|
||||
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid">
|
||||
</app-travel-plan>
|
||||
</mat-step>
|
||||
|
||||
<!-- Insurance Step -->
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'"
|
||||
[completed]="stepsCompleted.insurance" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Insurance</ng-template>
|
||||
<app-insurance (completed)="onInsuranceSaved($event)" [headerid]="headerid">
|
||||
</app-insurance>
|
||||
</mat-step>
|
||||
|
||||
<!-- Shipping Step -->
|
||||
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Shipping</ng-template>
|
||||
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid">
|
||||
</app-shipping>
|
||||
</mat-step>
|
||||
|
||||
<!-- Delivery Method Step -->
|
||||
<mat-step [completed]="stepsCompleted.deliveryMethod" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Delivery Method</ng-template>
|
||||
<app-delivery (completed)="onDeliveryMethodSaved($event)" [headerid]="headerid">
|
||||
</app-delivery>
|
||||
</mat-step>
|
||||
|
||||
<!-- Payment Step -->
|
||||
<mat-step [completed]="stepsCompleted.payment" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Payment</ng-template>
|
||||
<app-payment (completed)="onPaymentSaved($event)" [headerid]="headerid">
|
||||
</app-payment>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
@ -1,11 +1,81 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { ApplicationComponent } from '../application/application.component';
|
||||
import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { GoodsComponent } from '../goods/goods.component';
|
||||
import { HolderComponent } from '../holder/holder.component';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DeliveryComponent } from '../delivery/delivery.component';
|
||||
import { InsuranceComponent } from '../insurance/insurance.component';
|
||||
import { PaymentComponent } from '../payment/payment.component';
|
||||
import { ShippingComponent } from '../shipping/shipping.component';
|
||||
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-carnet',
|
||||
imports: [],
|
||||
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
|
||||
ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent, InsuranceComponent,
|
||||
ShippingComponent, DeliveryComponent, PaymentComponent],
|
||||
templateUrl: './edit-carnet.component.html',
|
||||
styleUrl: './edit-carnet.component.scss'
|
||||
})
|
||||
export class EditCarnetComponent {
|
||||
currentStep = 0;
|
||||
isLinear = false;
|
||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
|
||||
isEditMode = true; // Set to true for edit mode
|
||||
headerid: number = 0;
|
||||
|
||||
// Track completion of each step
|
||||
stepsCompleted = {
|
||||
applicationDetail: true,
|
||||
holderSelection: false,
|
||||
goodsSection: false,
|
||||
travelPlan: false,
|
||||
insurance: false,
|
||||
shipping: false,
|
||||
deliveryMethod: false,
|
||||
payment: false
|
||||
};
|
||||
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
ngOnInit(): void {
|
||||
const idParam = this.route.snapshot.paramMap.get('headerid');
|
||||
this.headerid = idParam ? parseInt(idParam, 10) : 0;
|
||||
}
|
||||
|
||||
onStepChange(event: StepperSelectionEvent): void {
|
||||
this.currentStep = event.selectedIndex;
|
||||
}
|
||||
|
||||
onHolderSelectionSaved(completed: boolean): void {
|
||||
this.stepsCompleted.holderSelection = completed;
|
||||
}
|
||||
|
||||
onGoodsSectionSaved(completed: boolean): void {
|
||||
this.stepsCompleted.goodsSection = completed;
|
||||
}
|
||||
|
||||
onTravelPlanSaved(completed: boolean): void {
|
||||
this.stepsCompleted.travelPlan = completed;
|
||||
}
|
||||
|
||||
onInsuranceSaved(completed: boolean): void {
|
||||
this.stepsCompleted.insurance = completed;
|
||||
}
|
||||
|
||||
onShippingSaved(completed: boolean): void {
|
||||
this.stepsCompleted.shipping = completed;
|
||||
}
|
||||
|
||||
onDeliveryMethodSaved(completed: boolean): void {
|
||||
this.stepsCompleted.deliveryMethod = completed;
|
||||
}
|
||||
|
||||
onPaymentSaved(completed: boolean): void {
|
||||
this.stepsCompleted.payment = completed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,6 @@ import { CommonModule } from '@angular/common';
|
||||
styleUrl: './goods.component.scss'
|
||||
})
|
||||
export class GoodsComponent {
|
||||
@Input() applicationid: number = 0;
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -7,6 +7,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
styleUrl: './holder.component.scss'
|
||||
})
|
||||
export class HolderComponent {
|
||||
@Input() applicationid: number = 0;
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-insurance',
|
||||
@ -7,5 +7,6 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './insurance.component.scss'
|
||||
})
|
||||
export class InsuranceComponent {
|
||||
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment',
|
||||
@ -7,5 +7,6 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './payment.component.scss'
|
||||
})
|
||||
export class PaymentComponent {
|
||||
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shipping',
|
||||
@ -7,5 +7,6 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './shipping.component.scss'
|
||||
})
|
||||
export class ShippingComponent {
|
||||
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-travel-plan',
|
||||
@ -7,5 +7,6 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './travel-plan.component.scss'
|
||||
})
|
||||
export class TravelPlanComponent {
|
||||
|
||||
@Input() headerid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { AuthService } from '../services/common/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
private excludedUrls = ['/register', '/forgot-password'];
|
||||
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||
@ -17,7 +18,11 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
// Add withCredentials to all requests
|
||||
// Add withCredentials to all requests except excluded URLs
|
||||
if (this.excludedUrls.some(url => request.url.includes(url))) {
|
||||
return next.handle(request);
|
||||
}
|
||||
|
||||
request = request.clone({
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
@ -13,6 +13,7 @@ export interface Menu {
|
||||
export interface UserDetail {
|
||||
spid: number;
|
||||
clientid: number;
|
||||
locationid: number;
|
||||
urlKey: string;
|
||||
logoName: string;
|
||||
themeName: string;
|
||||
|
||||
@ -15,29 +15,37 @@ export class ApplicationDetailService {
|
||||
private http = inject(HttpClient);
|
||||
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])));
|
||||
}
|
||||
// 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
|
||||
};
|
||||
}
|
||||
// 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> {
|
||||
const userDetails = this.userService.getUserDetails();
|
||||
|
||||
if (!userDetails || !userDetails.userDetails) {
|
||||
throw new Error('User details are not available');
|
||||
}
|
||||
|
||||
const applicationDetails = {
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTID: this.userService.getUserClientid(),
|
||||
P_SPID: userDetails.userDetails.spid,
|
||||
P_CLIENTID: userDetails.userDetails.clientid,
|
||||
P_LOCATIONID: userDetails.userDetails.locationid,
|
||||
P_APPLICATIONNAME: data.name,
|
||||
P_ORDERTYPE: 'ORIGINAL',
|
||||
P_USERID: this.userService.getUser(),
|
||||
}
|
||||
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/SaveCarnetApplication`, applicationDetails);
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateApplication`, applicationDetails);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
@ -48,4 +48,25 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
register(email: string, password: string, token: string | null): Observable<any> {
|
||||
let headers = new HttpHeaders();
|
||||
|
||||
if (token) {
|
||||
headers = headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
return this.http.post(
|
||||
`${this.apiUrl}/register`,
|
||||
{ P_EMAILADDR: email, P_PASSWORD: password },
|
||||
{ headers }
|
||||
);
|
||||
}
|
||||
|
||||
forgotPassword(email: string, password: string): Observable<any> {
|
||||
return this.http.post(
|
||||
`${this.apiUrl}/forgot-password`,
|
||||
{ P_EMAILADDR: email, P_PASSWORD: password }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,6 @@ export class UserService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private spid: number = 0;
|
||||
private clientid: number = 0;
|
||||
|
||||
userDetailsSignal = signal<User>({});
|
||||
|
||||
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
||||
@ -98,23 +95,18 @@ export class UserService {
|
||||
}
|
||||
|
||||
getUserSpid(): number {
|
||||
if (this.spid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.spid) {
|
||||
this.spid = userDetails.userDetails.spid;
|
||||
}
|
||||
}
|
||||
return this.spid;
|
||||
const userDetails = this.getUserDetails();
|
||||
return userDetails?.userDetails?.spid ?? 0;
|
||||
}
|
||||
|
||||
getUserClientid(): number {
|
||||
if (this.clientid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.clientid) {
|
||||
this.clientid = userDetails.userDetails.clientid;
|
||||
}
|
||||
}
|
||||
return this.clientid;
|
||||
const userDetails = this.getUserDetails();
|
||||
return userDetails?.userDetails?.clientid ?? 0;
|
||||
}
|
||||
|
||||
getUserLocationid(): number {
|
||||
const userDetails = this.getUserDetails();
|
||||
return userDetails?.userDetails?.locationid ?? 0;
|
||||
}
|
||||
|
||||
private mapToUser(data: any): User {
|
||||
@ -141,6 +133,7 @@ export class UserService {
|
||||
return {
|
||||
spid: userDetails.SPID,
|
||||
clientid: userDetails.CLIENTID,
|
||||
locationid: userDetails.LOCATIONID,
|
||||
urlKey: userDetails.ENCURLKEY,
|
||||
logoName: userDetails.LOGONAME,
|
||||
themeName: userDetails.THEMENAME
|
||||
|
||||
@ -37,7 +37,8 @@ export class HomeService {
|
||||
issueDate: item.ISSUEDATE || null,
|
||||
expiryDate: item.EXPDATE || null,
|
||||
orderType: item.ORDERTYPE,
|
||||
carnetStatus: item.CARNETSTATUS
|
||||
carnetStatus: item.CARNETSTATUS,
|
||||
headerId: item.HEADERID,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
56
src/app/forgot-password/forgot-password.component.html
Normal file
56
src/app/forgot-password/forgot-password.component.html
Normal file
@ -0,0 +1,56 @@
|
||||
<div class="forgot-password-container">
|
||||
<div class="forgot-password-card">
|
||||
<h3 class="subtitle">Forgot Password</h3>
|
||||
|
||||
<form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()" class="forgot-password-form">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput formControlName="email" type="email" required>
|
||||
<mat-icon matSuffix>email</mat-icon>
|
||||
<mat-error *ngIf="email?.errors?.['required']">
|
||||
Email is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="email?.errors?.['email']">
|
||||
Please enter a valid email
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Password</mat-label>
|
||||
<input matInput formControlName="password" [type]="hidePassword ? 'password' : 'text'" required>
|
||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
<mat-error *ngIf="password?.errors?.['required']">
|
||||
Password is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="login">
|
||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" type="submit"
|
||||
[disabled]="forgotPasswordForm.invalid || isLoading">
|
||||
<span *ngIf="!isLoading">Submit</span>
|
||||
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<h3>ATA Carnet</h3>
|
||||
<p>
|
||||
Also known as the "Merchandise Passport," is an international customs document that simplifies temporary
|
||||
exports to over 79 countries and territories.
|
||||
It allows businesses to explore new markets, showcase products at trade shows, and attend global conferences
|
||||
without paying duties or taxes.
|
||||
</p>
|
||||
<p>
|
||||
It simplifies customs procedures for the temporary movement of goods and allows goods to
|
||||
enter Customs territories of the ATA Carnet system free of customs duties and taxes for up to
|
||||
one year.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
95
src/app/forgot-password/forgot-password.component.scss
Normal file
95
src/app/forgot-password/forgot-password.component.scss
Normal file
@ -0,0 +1,95 @@
|
||||
/* forgot-password.component.scss */
|
||||
.forgot-password-container {
|
||||
display: flex;
|
||||
min-height: 85vh;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.forgot-password-card {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.forgot-password-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login {
|
||||
text-align: right;
|
||||
margin-top: -1rem;
|
||||
|
||||
.login-link {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mdc-icon-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// button {
|
||||
// margin-top: 1rem;
|
||||
// padding: 0.5rem;
|
||||
// font-size: 1rem;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
flex: 1;
|
||||
padding: 4rem;
|
||||
background-color: #597b7c;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.6;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.forgot-password-container {
|
||||
flex-direction: column;
|
||||
|
||||
.forgot-password-card {
|
||||
max-width: 100%;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
padding: 2rem;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/app/forgot-password/forgot-password.component.ts
Normal file
71
src/app/forgot-password/forgot-password.component.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||
import { AuthService } from '../core/services/common/auth.service';
|
||||
import { NotificationService } from '../core/services/common/notification.service';
|
||||
import { ThemeService } from '../core/services/theme.service';
|
||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-forgot-password',
|
||||
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
||||
templateUrl: './forgot-password.component.html',
|
||||
styleUrl: './forgot-password.component.scss'
|
||||
})
|
||||
export class ForgotPasswordComponent {
|
||||
|
||||
forgotPasswordForm: FormGroup;
|
||||
isLoading = false;
|
||||
hidePassword = true;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
private authService = inject(AuthService);
|
||||
private router = inject(Router);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
private themeService = inject(ThemeService);
|
||||
|
||||
constructor() {
|
||||
this.forgotPasswordForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: ['', [Validators.required]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.themeService.setTheme('default');
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.forgotPasswordForm.invalid) {
|
||||
this.forgotPasswordForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
const { email, password } = this.forgotPasswordForm.value;
|
||||
|
||||
this.authService.forgotPassword(email, password).subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess('Request successful');
|
||||
this.router.navigate(['/login']);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isLoading = false;
|
||||
let errorMessage = this.errorHandler.handleApiError(error, `Request failed. Please try again.`);
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Forgot password request failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.forgotPasswordForm.get('email');
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this.forgotPasswordForm.get('password');
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { BaseChartDirective } from 'ng2-charts';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CarnetStatus } from '../../core/models/carnet-status';
|
||||
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||
import { UserService } from '../../core/services/common/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chart',
|
||||
@ -18,6 +19,7 @@ export class ChartComponent {
|
||||
@Output() carnetStatusData = new EventEmitter<any>();
|
||||
|
||||
private navigationService = inject(NavigationService);
|
||||
private userService = inject(UserService);
|
||||
|
||||
navigateToManageProvider(spid: number): void {
|
||||
this.navigationService.navigate(['service-provider', spid]);
|
||||
@ -98,8 +100,8 @@ export class ChartComponent {
|
||||
});
|
||||
|
||||
return {
|
||||
title: provider.Service_Provider_Name,
|
||||
spid: provider.SPID,
|
||||
title: provider.Service_Provider_Name ?? provider.Preparer_Name,
|
||||
spid: provider.SPID ?? this.userService.getUserSpid(),
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
|
||||
@ -1,15 +1,46 @@
|
||||
<div class="dashboard-container">
|
||||
<div class="content">
|
||||
<div class="quick-actions">
|
||||
<button mat-raised-button (click)="navigateTo('add-carnet')" color="accent">
|
||||
<mat-icon>add</mat-icon> Add New Carnet
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-container">
|
||||
<!-- Chart Section -->
|
||||
<div class="chart-section">
|
||||
<app-chart [chartData]="carnetData" [carnetStatuses]="carnetStatuses"
|
||||
(carnetStatusData)="onCarnetStatusClick($event)"></app-chart>
|
||||
</div>
|
||||
|
||||
<!-- Chart Section -->
|
||||
<div class="chart-section">
|
||||
<app-chart [chartData]="carnetData" [carnetStatuses]="carnetStatuses"
|
||||
(carnetStatusData)="onCarnetStatusClick($event)"></app-chart>
|
||||
<!-- Questions Section -->
|
||||
<div class="questions-section">
|
||||
<div class="question-row">
|
||||
<mat-radio-group (change)="newCarnet($event)">
|
||||
<mat-label>Do you need a new carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group (change)="additionalSets($event)">
|
||||
<mat-label>Do you need additional sets?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group (change)="duplicateCarnet($event)">
|
||||
<mat-label>Did you lose your carnet? Are you looking for duplicates?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group (change)="extendCarnet($event)">
|
||||
<mat-label>Do you need to extend the carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carnet Details Section -->
|
||||
@ -79,7 +110,7 @@
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<button mat-icon-button color="primary"
|
||||
<button mat-icon-button color="primary" (click)="editCarnet(item)"
|
||||
*ngIf="getCarnetStatusLabel(item.carnetStatus) === 'Pending'"
|
||||
[hidden]="getCarnetStatusLabel(item.carnetStatus) === 'Pending'" matTooltip="Edit">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
||||
@ -1,12 +1,42 @@
|
||||
.dashboard-container {
|
||||
padding: 24px;
|
||||
// padding: 24px;
|
||||
|
||||
.quick-actions {
|
||||
// .quick-actions {
|
||||
// display: flex;
|
||||
// justify-content: end;
|
||||
|
||||
// button {
|
||||
// margin-bottom: 16px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
button {
|
||||
margin-bottom: 16px;
|
||||
.chart-section {
|
||||
flex: 0 0 30%;
|
||||
}
|
||||
|
||||
.questions-section {
|
||||
flex: 0 0 70%;
|
||||
padding: 24px 0px;
|
||||
margin-bottom: 24px;
|
||||
margin-left: 1rem;
|
||||
|
||||
.question-row {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
mat-radio-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
mat-radio-button {
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { ApiErrorHandlerService } from '../core/services/common/api-error-handle
|
||||
import { CommonService } from '../core/services/common/common.service';
|
||||
import { NotificationService } from '../core/services/common/notification.service';
|
||||
import { NavigationService } from '../core/services/common/navigation.service';
|
||||
import { MatRadioChange } from '@angular/material/radio';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@ -111,6 +112,29 @@ export class HomeComponent {
|
||||
return carnetStatus ? carnetStatus.name : value;
|
||||
}
|
||||
|
||||
newCarnet(event: MatRadioChange): void {
|
||||
if (event.value) {
|
||||
this.navigateTo('add-carnet');
|
||||
}
|
||||
}
|
||||
|
||||
additionalSets(event: MatRadioChange): void {
|
||||
}
|
||||
|
||||
duplicateCarnet(event: MatRadioChange): void {
|
||||
}
|
||||
|
||||
extendCarnet(event: MatRadioChange): void {
|
||||
}
|
||||
|
||||
editCarnet(item: any): void {
|
||||
if (item && item.headerId) {
|
||||
this.navigateTo(`edit-carnet/${item.headerId}?ApplicationName=${item.applicationName}`);
|
||||
} else {
|
||||
this.notificationService.showError('Invalid carnet data');
|
||||
}
|
||||
}
|
||||
|
||||
navigateTo(route: string): void {
|
||||
this.navigationService.navigate([route]);
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
</mat-form-field>
|
||||
|
||||
<div class="forgot-password">
|
||||
<a href="#" class="forgot-link">Forgot your password?</a>
|
||||
<a href="#" routerLink="/forgot-password" class="forgot-link">Forgot your password?</a>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="!loginForm.valid">
|
||||
|
||||
@ -7,10 +7,11 @@ import { NavigationService } from '../core/services/common/navigation.service';
|
||||
import { UserService } from '../core/services/common/user.service';
|
||||
import { User } from '../core/models/user';
|
||||
import { ThemeService } from '../core/services/theme.service';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule],
|
||||
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule, RouterModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
|
||||
64
src/app/register/register.component.html
Normal file
64
src/app/register/register.component.html
Normal file
@ -0,0 +1,64 @@
|
||||
<div class="registration-container">
|
||||
<div class="registration-card">
|
||||
<h3 class="subtitle">User Registration</h3>
|
||||
|
||||
<!-- Show error message if token is invalid -->
|
||||
<div *ngIf="tokenError" class="token-error">
|
||||
<mat-error>
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>Invalid registration link. Please request a new registration link from your
|
||||
administrator.</span>
|
||||
</mat-error>
|
||||
</div>
|
||||
|
||||
<form *ngIf="!tokenError" [formGroup]="registrationForm" (ngSubmit)="onSubmit()" class="registration-form">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput formControlName="email" type="email" required>
|
||||
<mat-icon matSuffix>email</mat-icon>
|
||||
<mat-error *ngIf="email?.errors?.['required']">
|
||||
Email is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="email?.errors?.['email']">
|
||||
Please enter a valid email
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Password</mat-label>
|
||||
<input matInput formControlName="password" [type]="hidePassword ? 'password' : 'text'" required>
|
||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
<mat-error *ngIf="password?.errors?.['required']">
|
||||
Password is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="login">
|
||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="registrationForm.invalid || isLoading">
|
||||
<span *ngIf="!isLoading">Register</span>
|
||||
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<h3>ATA Carnet</h3>
|
||||
<p>
|
||||
Also known as the "Merchandise Passport," is an international customs document that simplifies temporary
|
||||
exports to over 79 countries and territories.
|
||||
It allows businesses to explore new markets, showcase products at trade shows, and attend global conferences
|
||||
without paying duties or taxes.
|
||||
</p>
|
||||
<p>
|
||||
It simplifies customs procedures for the temporary movement of goods and allows goods to
|
||||
enter Customs territories of the ATA Carnet system free of customs duties and taxes for up to
|
||||
one year.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
95
src/app/register/register.component.scss
Normal file
95
src/app/register/register.component.scss
Normal file
@ -0,0 +1,95 @@
|
||||
/* registration.component.scss */
|
||||
.registration-container {
|
||||
display: flex;
|
||||
min-height: 85vh;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.registration-card {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.registration-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login {
|
||||
text-align: right;
|
||||
margin-top: -1rem;
|
||||
|
||||
.login-link {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mdc-icon-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// button {
|
||||
// margin-top: 1rem;
|
||||
// padding: 0.5rem;
|
||||
// font-size: 1rem;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
flex: 1;
|
||||
padding: 4rem;
|
||||
background-color: #597b7c;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.6;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.registration-container {
|
||||
flex-direction: column;
|
||||
|
||||
.registration-card {
|
||||
max-width: 100%;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
padding: 2rem;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/app/register/register.component.ts
Normal file
82
src/app/register/register.component.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AuthService } from '../core/services/common/auth.service';
|
||||
import { NotificationService } from '../core/services/common/notification.service';
|
||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ThemeService } from '../core/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
||||
templateUrl: './register.component.html',
|
||||
styleUrl: './register.component.scss'
|
||||
})
|
||||
export class RegisterComponent {
|
||||
|
||||
registrationForm: FormGroup;
|
||||
isLoading = false;
|
||||
hidePassword = true;
|
||||
tokenError: boolean = false;
|
||||
token: string | null = null;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
private authService = inject(AuthService);
|
||||
private router = inject(Router);
|
||||
private route = inject(ActivatedRoute);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
private themeService = inject(ThemeService);
|
||||
|
||||
constructor() {
|
||||
this.registrationForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: ['', [Validators.required]]
|
||||
});
|
||||
|
||||
// Get token from URL if present
|
||||
this.token = this.route.snapshot.paramMap.get('token');
|
||||
|
||||
// Check if token is null or empty
|
||||
if (!this.token || this.token.trim() === '') {
|
||||
this.tokenError = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.themeService.setTheme('default');
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.registrationForm.invalid) {
|
||||
this.registrationForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
const { email, password } = this.registrationForm.value;
|
||||
|
||||
this.authService.register(email, password, this.token).subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess('Registration successful');
|
||||
this.router.navigate(['/login']);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isLoading = false;
|
||||
let errorMessage = `Registration failed. Please request a new registration link from your administrator.`;
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Registration failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.registrationForm.get('email');
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this.registrationForm.get('password');
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user