manage preparer changes
This commit is contained in:
parent
fadfa87cdb
commit
12be0fb8c7
@ -6,6 +6,9 @@ import { HomeComponent } from './home/home.component';
|
||||
import { AppIdGuard } from './guards/appid.guard';
|
||||
import { EditServiceProviderComponent } from './service-provider/edit/edit-service-provider.component';
|
||||
import { UserSettingsComponent } from './user-settings/user-settings.component';
|
||||
import { ManagePreparerComponent } from './preparer/manage/manage-preparer.component';
|
||||
import { EditPreparerComponent } from './preparer/edit/edit-preparer.component';
|
||||
import { AddPreparerComponent } from './preparer/add/add-preparer.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
@ -16,6 +19,9 @@ export const routes: Routes = [
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'usersettings', component: UserSettingsComponent },
|
||||
{ path: 'service-provider/:id', component: EditServiceProviderComponent },
|
||||
{ path: 'preparer', component: ManagePreparerComponent },
|
||||
{ path: 'preparer/:id', component: EditPreparerComponent },
|
||||
{ path: 'add-preparer', component: AddPreparerComponent },
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||
],
|
||||
canActivate: [AuthGuard, AppIdGuard]
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
export interface Location {
|
||||
id: number;
|
||||
clientId: number;
|
||||
locationName: string;
|
||||
locationid: number;
|
||||
clientid: number;
|
||||
name: string;
|
||||
address1: string;
|
||||
address2?: string | null;
|
||||
city: string;
|
||||
state: string;
|
||||
country: string;
|
||||
zip: string;
|
||||
dateCreated?: Date | null;
|
||||
createdBy?: string | null;
|
||||
lastUpdatedBy?: string | null;
|
||||
lastUpdatedDate?: Date | null;
|
||||
isInactive?: boolean | null; // TODO
|
||||
inactivatedDate?: Date | null; // TODO
|
||||
}
|
||||
|
||||
@ -39,18 +39,18 @@ export class BasicDetailService {
|
||||
|
||||
createBasicDetails(data: BasicDetail): Observable<any> {
|
||||
const basicDetails = {
|
||||
p_spid: this.userService.getUserSpid(),
|
||||
p_clientname: data.name,
|
||||
p_lookupcode: data.lookupCode,
|
||||
p_address1: data.address1,
|
||||
p_address2: data.address2,
|
||||
p_city: data.city,
|
||||
p_state: data.state,
|
||||
p_country: data.country,
|
||||
p_zip: data.zip,
|
||||
p_issuingregion: data.carnetIssuingRegion,
|
||||
p_revenuelocation: data.revenueLocation,
|
||||
p_userid: this.userService.getUser(),
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTNAME: data.name,
|
||||
P_LOOKUPCODE: data.lookupCode,
|
||||
P_ADDRESS1: data.address1,
|
||||
P_ADDRESS2: data.address2,
|
||||
P_CITY: data.city,
|
||||
P_STATE: data.state,
|
||||
P_COUNTRY: data.country,
|
||||
P_ZIP: data.zip,
|
||||
P_ISSUINGREGION: data.carnetIssuingRegion,
|
||||
P_REVENUELOCATION: data.revenueLocation,
|
||||
P_USERID: this.userService.getUser(),
|
||||
}
|
||||
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateNewClients`, basicDetails);
|
||||
@ -58,18 +58,18 @@ export class BasicDetailService {
|
||||
|
||||
updateBasicDetails(id: number, data: BasicDetail): Observable<any> {
|
||||
const basicDetails = {
|
||||
p_spid: this.userService.getUserSpid(),
|
||||
p_clientid: id,
|
||||
p_clientname: data.name,
|
||||
p_lookupcode: data.lookupCode,
|
||||
p_address1: data.address1,
|
||||
p_address2: data.address2,
|
||||
p_city: data.city,
|
||||
p_state: data.state,
|
||||
p_country: data.country,
|
||||
p_zip: data.zip,
|
||||
p_revenuelocation: data.revenueLocation,
|
||||
p_userid: this.userService.getUser(),
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTID: id,
|
||||
P_PREPARERNAME: data.name,
|
||||
// P_LOOKUPCODE: data.lookupCode,
|
||||
P_ADDRESS1: data.address1,
|
||||
P_ADDRESS2: data.address2,
|
||||
P_CITY: data.city,
|
||||
P_STATE: data.state,
|
||||
P_COUNTRY: data.country,
|
||||
P_ZIP: data.zip,
|
||||
P_REVENUELOCATION: data.revenueLocation,
|
||||
P_USERID: this.userService.getUser(),
|
||||
}
|
||||
|
||||
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClient`, basicDetails);
|
||||
|
||||
@ -1,9 +1,46 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { UserService } from '../common/user.service';
|
||||
import { BasicDetail } from '../../models/preparer/basic-detail';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { PreparerFilter } from '../../models/preparer/preparer-filter';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ClientService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
constructor(private http: HttpClient, private userService: UserService) { }
|
||||
|
||||
getPreparers(filter: PreparerFilter): Observable<BasicDetail[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparers?P_SPID=${this.userService.getUserSpid()}&P_STATUS=ACTIVE&P_NAME=${filter.name}&P_LOOKUPCODE=${filter.lookupCode}&P_CITY=${filter.city}&P_STATE=${filter.state}`).pipe(
|
||||
map(response => this.mapToClients(response)));
|
||||
}
|
||||
|
||||
private mapToClients(data: any[]): BasicDetail[] {
|
||||
return data.map(basicDetails => ({
|
||||
clientid: basicDetails.CLIENTID,
|
||||
spid: basicDetails.SPID,
|
||||
name: basicDetails.PREPARERNAME,
|
||||
lookupCode: basicDetails.LOOKUPCODE,
|
||||
address1: basicDetails.ADDRESS1,
|
||||
address2: basicDetails.ADDRESS2,
|
||||
city: basicDetails.CITY,
|
||||
state: basicDetails.STATE,
|
||||
country: basicDetails.COUNTRY,
|
||||
carnetIssuingRegion: basicDetails.ISSUINGREGION,
|
||||
revenueLocation: basicDetails.REVENUELOCATION,
|
||||
zip: basicDetails.ZIP,
|
||||
// createdBy: basicDetails.CREATEDBY || null,
|
||||
// dateCreated: basicDetails.DATECREATED || null,
|
||||
// lastUpdatedBy: basicDetails.LASTUPDATEDBY || null,
|
||||
// lastUpdatedDate: basicDetails.LASTUPDATEDDATE || null,
|
||||
// isInactive: basicDetails.INACTIVEFLAG === 'Y' || false,
|
||||
// inactivatedDate: basicDetails.INACTIVEDATE || null
|
||||
}));
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
@ -44,20 +44,20 @@ export class ContactService {
|
||||
|
||||
createContact(clientid: number, data: Contact): Observable<any> {
|
||||
const contact = {
|
||||
p_spid: this.userService.getUserSpid(),
|
||||
p_clientid: clientid,
|
||||
p_defcontactflag: data.defaultContact ? 'Y' : 'N',
|
||||
p_contactstable: [{
|
||||
FirstName: data.firstName,
|
||||
LastName: data.lastName,
|
||||
MiddleInitial: data.middleInitial,
|
||||
Title: data.title,
|
||||
EmailAddress: data.email,
|
||||
MobileNo: data.mobile,
|
||||
PhoneNo: data.phone,
|
||||
FaxNo: data.fax
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTID: clientid,
|
||||
P_DEFCONTACTFLAG: data.defaultContact ? 'Y' : 'N',
|
||||
P_CONTACTSTABLE: [{
|
||||
P_FIRSTNAME: data.firstName,
|
||||
P_LASTNAME: data.lastName,
|
||||
P_MIDDLEINITIAL: data.middleInitial,
|
||||
P_TITLE: data.title,
|
||||
P_EMAILADDRESS: data.email,
|
||||
P_MOBILENO: data.mobile,
|
||||
P_PHONENO: data.phone,
|
||||
P_FAXNO: data.fax
|
||||
}],
|
||||
p_user_id: this.userService.getUser()
|
||||
P_USERID: this.userService.getUser()
|
||||
}
|
||||
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateClientContacts`, contact);
|
||||
@ -65,17 +65,17 @@ export class ContactService {
|
||||
|
||||
updateContact(spContactId: number, data: Contact): Observable<any> {
|
||||
const contact = {
|
||||
p_spid: this.userService.getUserSpid(),
|
||||
p_clientcontactid: spContactId,
|
||||
p_firstname: data.firstName,
|
||||
p_lastname: data.lastName,
|
||||
P_middleinitial: data.middleInitial,
|
||||
p_title: data.title,
|
||||
p_phone: data.phone,
|
||||
p_mobileno: data.mobile,
|
||||
p_fax: data.fax,
|
||||
p_emailaddress: data.email,
|
||||
p_user_id: this.userService.getUser()
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTCONTACTID: spContactId,
|
||||
P_FIRSTNAME: data.firstName,
|
||||
P_LASTNAME: data.lastName,
|
||||
P_MIDDLEINITIAL: data.middleInitial,
|
||||
P_TITLE: data.title,
|
||||
P_EMAILADDRESS: data.email,
|
||||
P_MOBILENO: data.mobile,
|
||||
P_PHONENO: data.phone,
|
||||
P_FAXNO: data.fax,
|
||||
P_USERID: this.userService.getUser()
|
||||
}
|
||||
|
||||
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClientContacts`, contact);
|
||||
|
||||
@ -1,9 +1,82 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { UserService } from '../common/user.service';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { Location } from '../../models/preparer/location';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LocationService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
constructor() { }
|
||||
constructor(private http: HttpClient, private userService: UserService) { }
|
||||
|
||||
getLocationsById(id: number): Observable<Location[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerLocByClientid?p_spid=${this.userService.getUserSpid()}&p_clientid=${id}`).pipe(
|
||||
map(response => this.mapToLocations(response)));
|
||||
}
|
||||
|
||||
private mapToLocations(data: any[]): Location[] {
|
||||
return data.map(location => ({
|
||||
locationid: location.LOCATIONID,
|
||||
spid: location.SPID,
|
||||
clientid: location.CLIENTID,
|
||||
name: location.NAMEOF,
|
||||
address1: location.ADDRESS1,
|
||||
address2: location.ADDRESS2,
|
||||
city: location.CITY,
|
||||
state: location.STATE,
|
||||
country: location.COUNTRY,
|
||||
zip: location.ZIP,
|
||||
createdBy: location.CREATEDBY || null,
|
||||
dateCreated: location.DATECREATED || null,
|
||||
lastUpdatedBy: location.LASTUPDATEDBY || null,
|
||||
lastUpdatedDate: location.LASTUPDATEDDATE || null,
|
||||
isInactive: location.INACTIVEFLAG === 'Y' || false,
|
||||
inactivatedDate: location.INACTIVEDATE || null
|
||||
}));
|
||||
}
|
||||
|
||||
createLocation(clientid: number, data: Location): Observable<any> {
|
||||
const location = {
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTID: clientid,
|
||||
P_CLIENTLOCADDRESSTABLE: [{
|
||||
P_NAMEOF: data.name,
|
||||
P_ADDRESS1: data.address1,
|
||||
P_ADDRESS2: data.address2,
|
||||
P_CITY: data.city,
|
||||
P_STATE: data.state,
|
||||
P_COUNTRY: data.country,
|
||||
P_ZIP: data.zip,
|
||||
}],
|
||||
P_USERID: this.userService.getUser()
|
||||
}
|
||||
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateClientLocations`, location);
|
||||
}
|
||||
|
||||
updateLocation(locationId: number, data: Location): Observable<any> {
|
||||
const location = {
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTLOCATIONID: locationId,
|
||||
P_LOCATIONNAME: data.name,
|
||||
P_ADDRESS1: data.address1,
|
||||
P_ADDRESS2: data.address2,
|
||||
P_CITY: data.city,
|
||||
P_STATE: data.state,
|
||||
P_COUNTRY: data.country,
|
||||
P_ZIP: data.zip,
|
||||
P_USERID: this.userService.getUser()
|
||||
}
|
||||
|
||||
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClientLocations`, location);
|
||||
}
|
||||
|
||||
// deleteLocation(clientContactId: string): Observable<any> {
|
||||
// return this.http.post(`${this.apiUrl}/${this.apiDb}/InactivateSPContact?p_clientcontactid=${clientContactId}`, null);
|
||||
// }
|
||||
}
|
||||
|
||||
@ -20,13 +20,13 @@
|
||||
</mat-step>
|
||||
|
||||
<!-- Location Step -->
|
||||
<!-- <mat-step [completed]="locationCompleted" [editable]="!!clientid && contactsCompleted">
|
||||
<mat-step [completed]="locationCompleted" [editable]="!!clientid && contactsCompleted">
|
||||
<ng-template matStepLabel>Locations</ng-template>
|
||||
|
||||
<app-location *ngIf="clientid" [clientid]="clientid" (hasLocation)="onLocationSaved($event)"
|
||||
<app-location *ngIf="clientid" [clientid]="clientid" (hasLocations)="onLocationSaved($event)"
|
||||
[userPreferences]="userPreferences">
|
||||
</app-location>
|
||||
</mat-step> -->
|
||||
</mat-step>
|
||||
|
||||
</mat-stepper>
|
||||
</div>
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Subject, takeUntil, zip } from 'rxjs';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Country } from '../../core/models/country';
|
||||
@ -75,10 +75,10 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
|
||||
createForm(): FormGroup {
|
||||
return this.fb.group({
|
||||
name: ['', [Validators.required, Validators.maxLength(100)]],
|
||||
lookupCode: ['', Validators.required, Validators.maxLength(20)],
|
||||
address1: ['', Validators.required, Validators.maxLength(100)],
|
||||
address2: ['', [Validators.maxLength(100)]],
|
||||
city: ['', Validators.required, Validators.maxLength(50)],
|
||||
lookupCode: ['', [Validators.required, Validators.maxLength(20)]],
|
||||
address1: ['', [Validators.required, Validators.maxLength(100)]],
|
||||
address2: ['', Validators.maxLength(100)],
|
||||
city: ['', [Validators.required, Validators.maxLength(50)]],
|
||||
state: ['', Validators.required],
|
||||
country: ['', Validators.required],
|
||||
zip: ['', [Validators.required, ZipCodeValidator('country')]],
|
||||
@ -88,7 +88,7 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
loadLookupData(): void {
|
||||
this.commonService.getCountries(this.clientid)
|
||||
this.commonService.getCountries(0)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (countries) => {
|
||||
@ -126,13 +126,8 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
|
||||
.subscribe({
|
||||
next: (states) => {
|
||||
this.states = states;
|
||||
const stateControl = this.basicDetailsForm.get('state');
|
||||
if (this.countriesHasStates.includes(country)) {
|
||||
stateControl?.enable();
|
||||
} else {
|
||||
stateControl?.disable();
|
||||
stateControl?.setValue('FN');
|
||||
}
|
||||
this.updateStateControl('state', country);
|
||||
this.updateStateControl('revenueLocation', country);
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
@ -142,6 +137,16 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
updateStateControl(controlName: string, country: string): void {
|
||||
const stateControl = this.basicDetailsForm.get(controlName);
|
||||
if (this.countriesHasStates.includes(country)) {
|
||||
stateControl?.enable();
|
||||
} else {
|
||||
stateControl?.disable();
|
||||
stateControl?.setValue('FN');
|
||||
}
|
||||
}
|
||||
|
||||
patchFormData(data: BasicDetail): void {
|
||||
this.basicDetailsForm.patchValue({
|
||||
name: data.name,
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<div class="contacts-container">
|
||||
<div class="actions-bar">
|
||||
<mat-slide-toggle (change)="toggleShowInactiveContacts()">
|
||||
Show Inactive Contacts
|
||||
</mat-slide-toggle>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="addNewContact()">
|
||||
<mat-icon>add</mat-icon> Add New Contact
|
||||
</button>
|
||||
@ -35,6 +39,12 @@
|
||||
<td mat-cell *matCellDef="let contact">{{ contact.phone | phone }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Mobile Column -->
|
||||
<ng-container matColumnDef="mobile">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Mobile</th>
|
||||
<td mat-cell *matCellDef="let contact">{{ contact.mobile | phone }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Email Column -->
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
|
||||
@ -59,13 +69,17 @@
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary" (click)="createLogin()" matTooltip="Login">
|
||||
<mat-icon>passkey</mat-icon>
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" *ngIf="!contact.defaultContact || !contact.isInactive" (click)="
|
||||
deleteContact(contact.clientContactid)" [hidden]="contact.defaultContact || contact.isInactive"
|
||||
matTooltip="Inactivate">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" *ngIf="!contact.isInactive" (click)="
|
||||
deleteContact(contact.clientContactid)" [hidden]="contact.isInactive" matTooltip="Inactivate">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<!-- <button mat-icon-button (click)="setDefaultContact(contact.contactId)"
|
||||
[color]="contact.defaultContact ? 'primary' : ''" matTooltip="Set as default">
|
||||
<mat-icon>star</mat-icon>
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
clear: both;
|
||||
margin-bottom: -16px;
|
||||
|
||||
mat-slide-toggle {
|
||||
transform: scale(0.8);
|
||||
margin-left: -0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@ -26,14 +26,15 @@ export class ContactsComponent {
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
|
||||
displayedColumns: string[] = ['firstName', 'lastName', 'title', 'phone', 'email', 'defaultContact', 'actions'];
|
||||
displayedColumns: string[] = ['firstName', 'lastName', 'title', 'phone', 'mobile', 'email', 'defaultContact', 'actions'];
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
contactForm: FormGroup;
|
||||
isEditing = false;
|
||||
currentContactId: number | null = null;
|
||||
isLoading = false;
|
||||
showForm = false;
|
||||
|
||||
showInactiveContacts = false;
|
||||
contacts: Contact[] = [];
|
||||
contactReadOnlyFields: any = {
|
||||
lastChangedDate: null,
|
||||
lastChangedBy: null,
|
||||
@ -79,7 +80,8 @@ export class ContactsComponent {
|
||||
|
||||
this.contactService.getContactsById(this.clientid).subscribe({
|
||||
next: (contacts: Contact[]) => {
|
||||
this.dataSource.data = contacts;
|
||||
this.contacts = contacts;
|
||||
this.renderContacts();
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error: any) => {
|
||||
@ -91,15 +93,6 @@ export class ContactsComponent {
|
||||
});
|
||||
}
|
||||
|
||||
// applyFilter(event: Event): void {
|
||||
// const filterValue = (event.target as HTMLInputElement).value;
|
||||
// this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||
|
||||
// if (this.dataSource.paginator) {
|
||||
// this.dataSource.paginator.firstPage();
|
||||
// }
|
||||
// }
|
||||
|
||||
addNewContact(): void {
|
||||
this.showForm = true;
|
||||
this.isEditing = false;
|
||||
@ -159,6 +152,19 @@ export class ContactsComponent {
|
||||
});
|
||||
}
|
||||
|
||||
toggleShowInactiveContacts(): void {
|
||||
this.showInactiveContacts = !this.showInactiveContacts;
|
||||
this.renderContacts();
|
||||
}
|
||||
|
||||
renderContacts(): void {
|
||||
if (this.showInactiveContacts) {
|
||||
this.dataSource.data = this.contacts;
|
||||
} else {
|
||||
this.dataSource.data = this.contacts.filter(contact => !contact.isInactive);
|
||||
}
|
||||
}
|
||||
|
||||
deleteContact(contactId: string): void {
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
width: '350px',
|
||||
|
||||
@ -19,12 +19,11 @@
|
||||
</mat-expansion-panel-header>
|
||||
<app-contacts [clientid]="clientid" [userPreferences]="userPreferences"></app-contacts>
|
||||
</mat-expansion-panel>
|
||||
<!--
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Locations </mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<app-location [clientid]="clientid" [isEditMode]="isEditMode"
|
||||
[userPreferences]="userPreferences"></app-location>
|
||||
</mat-expansion-panel> -->
|
||||
<app-location [clientid]="clientid" [userPreferences]="userPreferences"></app-location>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
@ -7,10 +7,11 @@ import { UserPreferences } from '../../core/models/user-preference';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UserPreferencesService } from '../../core/services/user-preference.service';
|
||||
import { LocationComponent } from '../location/location.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-preparer',
|
||||
imports: [AngularMaterialModule, BasicDetailsComponent, ContactsComponent, LocationComponent],
|
||||
imports: [AngularMaterialModule, CommonModule, BasicDetailsComponent, ContactsComponent, LocationComponent],
|
||||
templateUrl: './edit-preparer.component.html',
|
||||
styleUrl: './edit-preparer.component.scss'
|
||||
})
|
||||
|
||||
@ -1 +1,221 @@
|
||||
<p>location works!</p>
|
||||
<div class="locations-container">
|
||||
<div class="actions-bar">
|
||||
<button mat-raised-button color="primary" (click)="addNewLocation()">
|
||||
<mat-icon>add</mat-icon> Add New Location
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container mat-elevation-z8">
|
||||
<div class="loading-shade" *ngIf="isLoading">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Location Name</th>
|
||||
<td mat-cell *matCellDef="let location">{{ location.name }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Address1 Column -->
|
||||
<ng-container matColumnDef="address">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Address</th>
|
||||
<td mat-cell *matCellDef="let location">{{ getAddressLabel(location.address1, location.address2,
|
||||
location.zip) }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- City Column -->
|
||||
<ng-container matColumnDef="city">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>City</th>
|
||||
<td mat-cell *matCellDef="let location">{{ location.city }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- State Column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>State</th>
|
||||
<td mat-cell *matCellDef="let location">{{ location.state }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Country Column -->
|
||||
<ng-container matColumnDef="country">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>
|
||||
<td mat-cell *matCellDef="let location">{{ location.country }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let location">
|
||||
<button mat-icon-button color="primary" (click)="editLocation(location)" matTooltip="Edit">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<!--
|
||||
<button mat-icon-button color="warn" *ngIf="!location.defaultLocation || !location.isInactive"
|
||||
(click)="
|
||||
deleteLocation(location.clientLocationid)"
|
||||
[hidden]="location.defaultLocation || location.isInactive" matTooltip="Inactivate">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button> -->
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
<tr matNoDataRow *matNoDataRow>
|
||||
<td [colSpan]="displayedColumns.length" class="no-data-message">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>No records available</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
|
||||
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
|
||||
<!-- Location Form -->
|
||||
<div class="form-container" *ngIf="showForm">
|
||||
<form [formGroup]="locationForm" (ngSubmit)="saveLocation()">
|
||||
<div class="form-header">
|
||||
<h3>{{ isEditing ? 'Edit Location' : 'Add New Location' }}</h3>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="name">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="locationForm.get('name')?.errors?.['required']">
|
||||
Name is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="locationForm.get('name')?.errors?.['maxlength']">
|
||||
Maximum 100 characters allowed
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Address Information -->
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="address1">
|
||||
<mat-label>Address Line 1</mat-label>
|
||||
<input matInput formControlName="address1" required>
|
||||
<mat-error *ngIf="locationForm.get('address1')?.errors?.['required']">
|
||||
Address is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="locationForm.get('address1')?.errors?.['maxlength']">
|
||||
Maximum 100 characters allowed
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="address2">
|
||||
<mat-label>Address Line 2 (Optional)</mat-label>
|
||||
<input matInput formControlName="address2">
|
||||
<mat-error *ngIf="locationForm.get('address2')?.errors?.['maxlength']">
|
||||
Maximum 100 characters allowed
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Location Information -->
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="city">
|
||||
<mat-label>City</mat-label>
|
||||
<input matInput formControlName="city" required>
|
||||
<mat-error *ngIf="locationForm.get('city')?.errors?.['required']">
|
||||
City is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="locationForm.get('city')?.errors?.['maxlength']">
|
||||
Maximum 50 characters allowed
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="country">
|
||||
<mat-label>Country</mat-label>
|
||||
<mat-select formControlName="country" required (selectionChange)="onCountryChange($event.value)">
|
||||
<mat-option *ngFor="let country of countries" [value]="country.value">
|
||||
{{ country.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="locationForm.get('country')?.errors?.['required']">
|
||||
Country is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="state">
|
||||
<mat-label>State/Province</mat-label>
|
||||
<mat-select formControlName="state" required>
|
||||
<mat-option *ngFor="let state of states" [value]="state.value">
|
||||
{{ state.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="locationForm.get('state')?.errors?.['required']">
|
||||
State is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="zip">
|
||||
<mat-label>ZIP/Postal Code</mat-label>
|
||||
<input matInput formControlName="zip" required>
|
||||
<mat-error *ngIf="locationForm.get('zip')?.errors?.['required']">
|
||||
ZIP/Postal code is required
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="locationForm.get('country')?.value === 'US' && locationForm.get('zip')?.touched && locationForm.get('zip')?.errors?.['invalidUSZip']">
|
||||
Please enter a valid 5-digit US ZIP code
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="locationForm.get('country')?.value === 'CA' && locationForm.get('zip')?.touched && locationForm.get('zip')?.errors?.['invalidCanadaPostal']">
|
||||
Please enter a valid postal code (e.g., A1B2C3)
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isEditing" class="readonly-section">
|
||||
<div class="readonly-fields">
|
||||
<div class="field-column">
|
||||
<!-- Last Changed By -->
|
||||
<div class="readonly-field">
|
||||
<label>Last Changed By</label>
|
||||
<div class="readonly-value">
|
||||
{{locationReadOnlyFields.lastChangedBy || 'N/A'}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Inactive status -->
|
||||
<div class="readonly-field">
|
||||
<label>Inactive Status </label>
|
||||
<div class="readonly-value">
|
||||
{{locationReadOnlyFields.isInactive === true ? 'Yes' : 'No' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-column">
|
||||
|
||||
<!-- Last Changed Date -->
|
||||
<div class="readonly-field">
|
||||
<label>Last Changed Date</label>
|
||||
<div class="readonly-value">
|
||||
{{(locationReadOnlyFields.lastChangedDate | date:'mediumDate':'UTC') || 'N/A'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inactivated Date -->
|
||||
<div class="readonly-field">
|
||||
<label>Inactivated Date</label>
|
||||
<div class="readonly-value">
|
||||
{{(locationReadOnlyFields.inactivatedDate | date:'mediumDate':'UTC') || 'N/A'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="locationForm.invalid">
|
||||
{{ isEditing ? 'Update' : 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,163 @@
|
||||
.locations-container {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
.actions-bar {
|
||||
clear: both;
|
||||
margin-bottom: -16px;
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
|
||||
.loading-shade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
mat-table {
|
||||
width: 100%;
|
||||
|
||||
mat-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-column-actions {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
text-align: center;
|
||||
padding: 0.9rem;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
|
||||
mat-icon {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
mat-paginator {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
|
||||
.form-header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: var(--mat-sys-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
mat-form-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.small-field {
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.readonly-section {
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.readonly-fields {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
|
||||
.field-column {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.readonly-field {
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.readonly-value {
|
||||
padding: 0.25rem;
|
||||
font-size: 0.9375rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive adjustments
|
||||
@media (max-width: 768px) {
|
||||
.location-container {
|
||||
padding: 16px;
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
gap: 16px !important;
|
||||
|
||||
.small-field {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,267 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { UserPreferences } from '../../core/models/user-preference';
|
||||
import { Location } from '../../core/models/preparer/location';
|
||||
import { LocationService } from '../../core/services/preparer/location.service';
|
||||
import { NotificationService } from '../../core/services/common/notification.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||
import { CustomPaginator } from '../../shared/custom-paginator';
|
||||
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
|
||||
import { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
|
||||
import { Country } from '../../core/models/country';
|
||||
import { Region } from '../../core/models/region';
|
||||
import { State } from '../../core/models/state';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { CommonService } from '../../core/services/common/common.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-location',
|
||||
imports: [],
|
||||
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
|
||||
templateUrl: './location.component.html',
|
||||
styleUrl: './location.component.scss'
|
||||
styleUrl: './location.component.scss',
|
||||
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
|
||||
})
|
||||
export class LocationComponent {
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
|
||||
displayedColumns: string[] = ['name', 'address', 'city', 'state', 'country', 'actions'];
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
locationForm: FormGroup;
|
||||
isEditing = false;
|
||||
currentLocationId: number | null = null;
|
||||
isLoading = false;
|
||||
showForm = false;
|
||||
countries: Country[] = [];
|
||||
states: State[] = [];
|
||||
|
||||
locationReadOnlyFields: any = {
|
||||
lastChangedDate: null,
|
||||
lastChangedBy: null,
|
||||
isInactive: null,
|
||||
inactivatedDate: null
|
||||
};
|
||||
|
||||
@Input() clientid: number = 0;
|
||||
@Input() userPreferences: UserPreferences = {};
|
||||
@Output() hasLocations = new EventEmitter<boolean>();
|
||||
|
||||
countriesHasStates = ['US', 'CA', 'MX'];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private locationService: LocationService,
|
||||
private notificationService: NotificationService,
|
||||
private dialog: MatDialog,
|
||||
private errorHandler: ApiErrorHandlerService,
|
||||
private commonService: CommonService
|
||||
) {
|
||||
this.locationForm = this.fb.group({
|
||||
name: ['', [Validators.required, Validators.maxLength(100)]],
|
||||
address1: ['', [Validators.required, Validators.maxLength(100)]],
|
||||
address2: ['', [Validators.maxLength(100)]],
|
||||
city: ['', [Validators.required, Validators.maxLength(50)]],
|
||||
state: ['', Validators.required],
|
||||
country: ['', Validators.required],
|
||||
zip: ['', [Validators.required, ZipCodeValidator('country')]],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCountries();
|
||||
this.loadLocations();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
loadLocations(): void {
|
||||
this.isLoading = true;
|
||||
|
||||
this.locationService.getLocationsById(this.clientid).subscribe({
|
||||
next: (locations: Location[]) => {
|
||||
this.dataSource.data = locations;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error: any) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load locations');
|
||||
this.notificationService.showError(errorMessage);
|
||||
this.isLoading = false;
|
||||
console.error('Error loading locations:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCountryChange(country: string): void {
|
||||
this.locationForm.get('state')?.reset();
|
||||
|
||||
if (country) {
|
||||
this.loadStates(country);
|
||||
}
|
||||
|
||||
this.locationForm.get('zip')?.updateValueAndValidity();
|
||||
}
|
||||
|
||||
addNewLocation(): void {
|
||||
this.showForm = true;
|
||||
this.isEditing = false;
|
||||
this.currentLocationId = null;
|
||||
this.locationForm.reset();
|
||||
}
|
||||
|
||||
editLocation(location: Location): void {
|
||||
this.showForm = true;
|
||||
this.isEditing = true;
|
||||
this.currentLocationId = location.locationid;
|
||||
this.locationForm.patchValue({
|
||||
name: location.name,
|
||||
address1: location.address1,
|
||||
address2: location.address2,
|
||||
city: location.city,
|
||||
country: location.country,
|
||||
state: location.state,
|
||||
zip: location.zip,
|
||||
});
|
||||
|
||||
if (location.country) {
|
||||
this.loadStates(location.country);
|
||||
}
|
||||
|
||||
this.locationReadOnlyFields.lastChangedDate = location.lastUpdatedDate ?? location.dateCreated;
|
||||
this.locationReadOnlyFields.lastChangedBy = location.lastUpdatedBy ?? location.createdBy;
|
||||
this.locationReadOnlyFields.isInactive = location.isInactive;
|
||||
this.locationReadOnlyFields.inactivatedDate = location.inactivatedDate;
|
||||
}
|
||||
|
||||
saveLocation(): void {
|
||||
if (this.locationForm.invalid) {
|
||||
this.locationForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
// default the first location
|
||||
const locationData: Location = this.locationForm.value;
|
||||
|
||||
const saveObservable = this.isEditing && (this.currentLocationId! > 0)
|
||||
? this.locationService.updateLocation(this.currentLocationId!, locationData)
|
||||
: this.locationService.createLocation(this.clientid, locationData);
|
||||
|
||||
saveObservable.subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess(`Location ${this.isEditing ? 'updated' : 'added'} successfully`);
|
||||
this.loadLocations();
|
||||
this.cancelEdit();
|
||||
this.hasLocations.emit(true);
|
||||
},
|
||||
error: (error) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} location`);
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Error saving location:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadCountries(): void {
|
||||
this.commonService.getCountries(this.clientid)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (countries) => {
|
||||
this.countries = countries;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to load countries', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadStates(country: string): void {
|
||||
this.isLoading = true;
|
||||
country = this.countriesHasStates.includes(country) ? country : 'FN';
|
||||
this.commonService.getStates(country, this.clientid)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (states) => {
|
||||
this.states = states;
|
||||
const stateControl = this.locationForm.get('state');
|
||||
if (this.countriesHasStates.includes(country)) {
|
||||
stateControl?.enable();
|
||||
} else {
|
||||
stateControl?.disable();
|
||||
stateControl?.setValue('FN');
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to load states', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// deleteLocation(locationId: string): void {
|
||||
// const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
// width: '350px',
|
||||
// data: {
|
||||
// title: 'Confirm Delete',
|
||||
// message: 'Are you sure you want to delete this location?',
|
||||
// confirmText: 'Delete',
|
||||
// cancelText: 'Cancel'
|
||||
// }
|
||||
// });
|
||||
|
||||
// dialogRef.afterClosed().subscribe(result => {
|
||||
// if (result) {
|
||||
// this.locationService.deleteLocation(locationId).subscribe({
|
||||
// next: () => {
|
||||
// this.notificationService.showSuccess('Location deleted successfully');
|
||||
// this.loadLocations();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete location');
|
||||
// this.notificationService.showError(errorMessage);
|
||||
// console.error('Error deleting location:', error);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
getAddressLabel(address1: string, address2?: string, zip?: string): string {
|
||||
let addressLabel = address1;
|
||||
if (address2) {
|
||||
addressLabel += `, ${address2}`;
|
||||
}
|
||||
if (zip) {
|
||||
addressLabel += `, ${zip}`;
|
||||
}
|
||||
return addressLabel;
|
||||
}
|
||||
|
||||
getCountryLabel(value: string): string {
|
||||
const country = this.countries.find(c => c.value === value);
|
||||
return country ? country.name : value;
|
||||
}
|
||||
|
||||
cancelEdit(): void {
|
||||
this.showForm = false;
|
||||
this.isEditing = false;
|
||||
this.currentLocationId = null;
|
||||
this.locationForm.reset();
|
||||
}
|
||||
}
|
||||
|
||||
133
src/app/preparer/manage/manage-preparer.component.html
Normal file
133
src/app/preparer/manage/manage-preparer.component.html
Normal file
@ -0,0 +1,133 @@
|
||||
<div class="manage-preparers-container">
|
||||
<h2 class="page-header">Manage Preparers</h2>
|
||||
|
||||
<div class="search-section">
|
||||
<form [formGroup]="searchForm" class="search-form" (ngSubmit)="onSearch()">
|
||||
<div class="search-fields">
|
||||
<!-- Search Criteria Row 1 -->
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="name">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput formControlName="name" placeholder="Enter company name">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="address">
|
||||
<mat-label>Address</mat-label>
|
||||
<input matInput formControlName="address" placeholder="Enter address">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Search Criteria Row 2 -->
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="city">
|
||||
<mat-label>City</mat-label>
|
||||
<input matInput formControlName="city" placeholder="Enter city">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="state">
|
||||
<mat-label>State</mat-label>
|
||||
<input matInput formControlName="state" placeholder="Enter state">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lookup-code">
|
||||
<mat-label>Lookup Code</mat-label>
|
||||
<input matInput formControlName="lookupCode" placeholder="Enter lookup code">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-actions">
|
||||
<button mat-raised-button color="primary" type="submit"
|
||||
[disabled]="searchForm.invalid || !isSearchCriteriaProvided()">
|
||||
<mat-icon>search</mat-icon>
|
||||
Search
|
||||
</button>
|
||||
<button mat-raised-button type="button" (click)="onClear()">
|
||||
<mat-icon>clear</mat-icon>
|
||||
Clear Filters
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="navigateTo('add-preparer')">
|
||||
<mat-icon>add</mat-icon>
|
||||
Add New Preparer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="results-section" *ngIf="showResults">
|
||||
|
||||
<div class="loading-shade" *ngIf="isLoading">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<table mat-table matSort [dataSource]="dataSource"
|
||||
*ngIf="!isLoading && dataSource && dataSource.data.length > 0" class="results-table">
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
|
||||
<td mat-cell *matCellDef="let client">{{client.name}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Address Column -->
|
||||
<ng-container matColumnDef="address">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Address</th>
|
||||
<td mat-cell *matCellDef="let client">{{ getAddressLabel(client.address1, client.address2, client.zip)}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- City Column -->
|
||||
<ng-container matColumnDef="city">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>City</th>
|
||||
<td mat-cell *matCellDef="let client">{{client.city}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- State Column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>State</th>
|
||||
<td mat-cell *matCellDef="let client">{{client.state}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Country Column -->
|
||||
<ng-container matColumnDef="country">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>
|
||||
<td mat-cell *matCellDef="let client">{{client.country}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Carnet Issuing region Column -->
|
||||
<ng-container matColumnDef="carnetIssuingRegion">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Carnet Issuing region </th>
|
||||
<td mat-cell *matCellDef="let client">{{getRegionLabel(client.carnetIssuingRegion)}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Revenue Location Column -->
|
||||
<ng-container matColumnDef="revenueLocation">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Revenue Location</th>
|
||||
<td mat-cell *matCellDef="let client">{{client.revenueLocation}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef class="actions-header">Actions</th>
|
||||
<td mat-cell *matCellDef="let client" class="actions-cell">
|
||||
<button mat-icon-button color="primary" (click)="onEdit(client.clientid)" matTooltip="Edit">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
<tr matNoDataRow *matNoDataRow>
|
||||
<td [colSpan]="displayedColumns.length" class="no-data-message">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>No records found matching your criteria</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
|
||||
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
128
src/app/preparer/manage/manage-preparer.component.scss
Normal file
128
src/app/preparer/manage/manage-preparer.component.scss
Normal file
@ -0,0 +1,128 @@
|
||||
.page-header {
|
||||
margin: 0.5rem 0px;
|
||||
color: var(--mat-sys-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.manage-preparers-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.search-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
|
||||
.name,
|
||||
.address {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.city,
|
||||
.state,
|
||||
.lookup-code {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.results-section {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
|
||||
.loading-shade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
mat-table {
|
||||
width: 100%;
|
||||
|
||||
mat-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-column-actions {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mat-column-defaultContact {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
text-align: center;
|
||||
padding: 0.9rem;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
|
||||
mat-icon {
|
||||
font-size: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
mat-paginator {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.manage-preparers-container {
|
||||
.search-form {
|
||||
.search-fields {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
.name,
|
||||
.address,
|
||||
.city,
|
||||
.state,
|
||||
.lookup-code {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/app/preparer/manage/manage-preparer.component.ts
Normal file
183
src/app/preparer/manage/manage-preparer.component.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { BasicDetail } from '../../core/models/preparer/basic-detail';
|
||||
import { NotificationService } from '../../core/services/common/notification.service';
|
||||
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||
import { UserPreferencesService } from '../../core/services/user-preference.service';
|
||||
import { UserPreferences } from '../../core/models/user-preference';
|
||||
import { ClientService } from '../../core/services/preparer/client.service';
|
||||
import { PreparerFilter } from '../../core/models/preparer/preparer-filter';
|
||||
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||
import { CustomPaginator } from '../../shared/custom-paginator';
|
||||
import { CommonService } from '../../core/services/common/common.service';
|
||||
import { Region } from '../../core/models/region';
|
||||
import { Country } from '../../core/models/country';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage',
|
||||
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
||||
templateUrl: './manage-preparer.component.html',
|
||||
styleUrl: './manage-preparer.component.scss',
|
||||
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }]
|
||||
})
|
||||
export class ManagePreparerComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
@ViewChild(MatPaginator, { static: false })
|
||||
set paginator(value: MatPaginator) {
|
||||
this.dataSource.paginator = value;
|
||||
}
|
||||
|
||||
@ViewChild(MatSort, { static: false })
|
||||
set sort(value: MatSort) {
|
||||
this.dataSource.sort = value;
|
||||
}
|
||||
|
||||
searchForm: FormGroup;
|
||||
showResults = false;
|
||||
isLoading = false;
|
||||
userPreferences: UserPreferences;
|
||||
countries: Country[] = [];
|
||||
regions: Region[] = [];
|
||||
|
||||
displayedColumns: string[] = ['name', 'address', 'city', 'state', 'country', 'carnetIssuingRegion', 'revenueLocation', 'actions'];
|
||||
dataSource = new MatTableDataSource<any>([]);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private notificationService: NotificationService,
|
||||
private userPrefenceService: UserPreferencesService,
|
||||
private navigationService: NavigationService,
|
||||
private clientService: ClientService,
|
||||
private errorHandler: ApiErrorHandlerService,
|
||||
private commonService: CommonService
|
||||
) {
|
||||
this.userPreferences = userPrefenceService.getPreferences();
|
||||
this.searchForm = this.createSearchForm();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCountries();
|
||||
this.loadRegions();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
createSearchForm(): FormGroup {
|
||||
return this.fb.group({
|
||||
name: [''],
|
||||
address: [''],
|
||||
city: [''],
|
||||
state: [''],
|
||||
lookupCode: ['']
|
||||
});
|
||||
}
|
||||
|
||||
isSearchCriteriaProvided(): boolean {
|
||||
const values = this.searchForm.value;
|
||||
return !!values.name || !!values.address || !!values.city || !!values.state || !!values.lookupCode;
|
||||
}
|
||||
|
||||
onSearch(): void {
|
||||
if (this.searchForm.invalid || !this.isSearchCriteriaProvided()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
this.showResults = true;
|
||||
const filterData: PreparerFilter = this.searchForm.value;
|
||||
|
||||
this.clientService.getPreparers(filterData).subscribe({
|
||||
next: (clients: BasicDetail[]) => {
|
||||
this.dataSource.data = clients;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error: any) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search preparers');
|
||||
this.notificationService.showError(errorMessage);
|
||||
this.isLoading = false;
|
||||
console.error('Error loading preparers:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClear(): void {
|
||||
this.searchForm.reset();
|
||||
this.showResults = false;
|
||||
this.dataSource.data = [];
|
||||
}
|
||||
|
||||
navigateTo(route: string): void {
|
||||
this.navigationService.navigate([route]);
|
||||
}
|
||||
|
||||
onEdit(clientid: number): void {
|
||||
this.navigationService.navigate(['preparer', clientid]);
|
||||
}
|
||||
|
||||
|
||||
loadRegions(): void {
|
||||
this.commonService.getRegions()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (regions) => {
|
||||
this.regions = regions;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to load regions', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadCountries(): void {
|
||||
this.commonService.getCountries(0)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (countries) => {
|
||||
this.countries = countries;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to load countries', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAddressLabel(address1: string, address2?: string, zip?: string): string {
|
||||
let addressLabel = address1;
|
||||
if (address2) {
|
||||
addressLabel += `, ${address2}`;
|
||||
}
|
||||
if (zip) {
|
||||
addressLabel += `, ${zip}`;
|
||||
}
|
||||
return addressLabel;
|
||||
}
|
||||
|
||||
getRegionLabel(value: string): string {
|
||||
const region = this.regions.find(r => r.region === value);
|
||||
return region ? region.regionname : value;
|
||||
}
|
||||
|
||||
getCountryLabel(value: string): string {
|
||||
const country = this.countries.find(c => c.value === value);
|
||||
return country ? country.name : value;
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatAccordion, MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/material-moment-adapter';
|
||||
|
||||
@NgModule({
|
||||
@ -53,7 +54,8 @@ import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/m
|
||||
MatExpansionModule,
|
||||
MatAccordion,
|
||||
MatStepperModule,
|
||||
MatMomentDateModule
|
||||
MatMomentDateModule,
|
||||
MatSlideToggleModule
|
||||
],
|
||||
exports: [
|
||||
MatButtonModule,
|
||||
@ -79,7 +81,8 @@ import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule } from '@angular/m
|
||||
MatExpansionModule,
|
||||
MatAccordion,
|
||||
MatStepperModule,
|
||||
MatMomentDateModule
|
||||
MatMomentDateModule,
|
||||
MatSlideToggleModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user