feedback updates

This commit is contained in:
Cyril Joseph 2025-11-13 23:38:54 -04:00
parent 76d78c5869
commit 62b78cd01a
32 changed files with 144 additions and 40 deletions

View File

@ -0,0 +1,5 @@
export interface ContactTitle {
name: string;
id: string;
value: string;
}

View File

@ -7,5 +7,6 @@ export interface BasicFee {
spid?: number;
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -8,5 +8,6 @@ export interface CargoRate {
rate: number,
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -6,5 +6,6 @@ export interface CarnetFee {
spid: number;
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -7,5 +7,6 @@ export interface ContinuationSheetFee {
carnetType: string;
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -9,5 +9,6 @@ export interface CounterfoilFee {
rate: number,
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -9,5 +9,6 @@ export interface ExpeditedFee {
effectiveDate: Date;
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -1,7 +1,7 @@
export interface SecurityDeposit {
securityDepositId: number;
holderType: string;
uscibMember: boolean;
uscibMember: string;
specialCommodity: string;
specialCountry: string;
rate: number;
@ -10,5 +10,6 @@ export interface SecurityDeposit {
spid: number;
dateCreated?: Date | null;
createdBy?: string | null;
expiredDate?: Date | null;
expired?: boolean;
}

View File

@ -14,6 +14,7 @@ import { CarnetStatus } from '../../models/carnet-status';
import { Country } from '../../models/country';
import { UserService } from './user.service';
import { HolderType } from '../../models/holder-type';
import { ContactTitle } from '../../models/contact-title';
@Injectable({
providedIn: 'root'
@ -163,6 +164,18 @@ export class CommonService {
);
}
getContactTitles(): Observable<ContactTitle[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=012&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
id: item.PARAMID,
value: item.PARAMVALUE,
}))
)
);
}
formatUSDate(datetime: Date): string {
const date = new Date(datetime);
const month = String(date.getUTCMonth() + 1).padStart(2, '0');

View File

@ -31,6 +31,7 @@ export class BasicFeeService {
effectiveDate: item.EFFDATE,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -33,6 +33,7 @@ export class CargoRateService {
rate: item.RATE,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -32,6 +32,7 @@ export class CarnetFeeService {
spid: item.SPID,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -32,6 +32,7 @@ export class ContinuationSheetFeeService {
rate: item.RATE,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -34,6 +34,7 @@ export class CounterfoilFeeService {
rate: item.RATE,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -35,6 +35,7 @@ export class ExpeditedFeeService {
spid: item.SPID,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}

View File

@ -35,6 +35,7 @@ export class SecurityDepositService {
spid: item.SPID,
createdBy: item.CREATEDBY || null,
dateCreated: item.DATECREATED || null,
expiredDate: item.EXPDATE || null,
expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false
}));
}
@ -45,7 +46,7 @@ export class SecurityDepositService {
P_SPID: spid,
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
P_HOLDERTYPE: data.holderType,
P_USCIBMEMBERFLAG: data.uscibMember ? 'Y' : 'N',
P_USCIBMEMBERFLAG: data.uscibMember,
P_SPCLCOMMODITY: data.specialCommodity,
P_SPCLCOUNTRY: data.specialCountry,
P_RATE: data.rate,

View File

@ -37,6 +37,12 @@
<td mat-cell *matCellDef="let fee">{{ fee.effectiveDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>

View File

@ -28,7 +28,7 @@ export class BasicFeeComponent {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['startCarnetValue', 'endCarnetValue', 'fees', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['startCarnetValue', 'endCarnetValue', 'fees', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<BasicFee>();
feeForm: FormGroup;
isEditing = false;

View File

@ -49,6 +49,12 @@
</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>

View File

@ -28,7 +28,7 @@ export class CargoRateComponent implements OnInit {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['carnetType', 'startSets', 'endSets', 'rate', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['carnetType', 'startSets', 'endSets', 'rate', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<any>();
cargoRateForm: FormGroup;
isEditing = false;

View File

@ -23,7 +23,7 @@
<!-- Commission Rate Column -->
<ng-container matColumnDef="commissionRate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Commission Rate</th>
<td mat-cell *matCellDef="let item">{{ item.commissionRate }}</td>
<td mat-cell *matCellDef="let item">{{ item.commissionRate | number:'1.2-2' }}</td>
</ng-container>
<!-- Effective Date Column -->
@ -32,6 +32,12 @@
<td mat-cell *matCellDef="let item">{{ item.effectiveDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
@ -87,6 +93,9 @@
<mat-error *ngIf="feeCommissionForm.get('commissionRate')?.errors?.['required']">
Commission rate is required
</mat-error>
<mat-error *ngIf="feeCommissionForm.get('commissionRate')?.errors?.['pattern']">
Please enter a valid number with up to 2 decimal places
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">

View File

@ -31,7 +31,7 @@ export class CarnetFeeComponent implements OnInit {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['feeType', 'commissionRate', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['feeType', 'commissionRate', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<CarnetFee>();
feeCommissionForm: FormGroup;
isEditing = false;
@ -62,7 +62,7 @@ export class CarnetFeeComponent implements OnInit {
constructor() {
this.feeCommissionForm = this.fb.group({
feeType: ['', Validators.required],
commissionRate: [0, [Validators.required]],
commissionRate: [0, [Validators.required, Validators.pattern(/^\d+\.?\d{0,2}$/)]],
effectiveDate: ['', Validators.required]
});
}

View File

@ -44,6 +44,12 @@
</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>

View File

@ -27,7 +27,7 @@ export class ContinuationSheetFeeComponent implements OnInit {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['customerType', 'carnetType', 'rate', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['customerType', 'carnetType', 'rate', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<any>();
continuationSheetForm: FormGroup;
isEditing = false;

View File

@ -56,6 +56,12 @@
</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>

View File

@ -28,7 +28,7 @@ export class CounterfoilFeeComponent implements OnInit {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['customerType', 'carnetType', 'startSets', 'endSets', 'rate', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['customerType', 'carnetType', 'startSets', 'endSets', 'rate', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<any>();
counterfoilForm: FormGroup;
isEditing = false;

View File

@ -44,6 +44,12 @@
<td mat-cell *matCellDef="let fee">{{ fee.effectiveDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>

View File

@ -32,7 +32,7 @@ export class ExpeditedFeeComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['customerType', 'deliveryType', 'time', 'fee', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['customerType', 'deliveryType', 'time', 'fee', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<any>();
feeForm: FormGroup;
isEditing = false;

View File

@ -24,22 +24,20 @@
<ng-container matColumnDef="uscibMember">
<th mat-header-cell *matHeaderCellDef mat-sort-header>USCIB Member</th>
<td mat-cell *matCellDef="let deposit">
<mat-icon [color]="deposit.uscibMember ? 'primary' : ''">
{{ deposit.uscibMember ? 'check_circle' : 'cancel' }}
</mat-icon>
{{ deposit.uscibMember }}
</td>
</ng-container>
<!-- Special Commodity Column -->
<ng-container matColumnDef="specialCommodity">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Special Commodity</th>
<td mat-cell *matCellDef="let deposit">{{ deposit.specialCommodity || 'N/A' }}</td>
<td mat-cell *matCellDef="let deposit">{{ deposit.specialCommodity }}</td>
</ng-container>
<!-- Special Country Column -->
<ng-container matColumnDef="specialCountry">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Special Country</th>
<td mat-cell *matCellDef="let deposit">{{ getCountryName(deposit.specialCountry) || 'N/A' }}</td>
<td mat-cell *matCellDef="let deposit">{{ getCountryName(deposit.specialCountry) }}</td>
</ng-container>
<!-- Rate Column -->
@ -60,6 +58,12 @@
<td mat-cell *matCellDef="let deposit">{{ deposit.effectiveDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Expired Date Column -->
<ng-container matColumnDef="expiredDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expired Date</th>
<td mat-cell *matCellDef="let fee">{{ fee.expiredDate | date:'mediumDate':'UTC' }}</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
@ -101,20 +105,20 @@
<div class="form-row">
<mat-label>Holder Type</mat-label>
<mat-radio-group formControlName="holderType" required class="horizontal-group">
<mat-radio-group formControlName="holderType" class="horizontal-group">
<mat-radio-button *ngFor="let type of holderTypes" [value]="type.value" class="radio-button">
{{ type.name }}
</mat-radio-button>
</mat-radio-group>
<mat-error *ngIf="depositForm.get('holderType')?.errors?.['required']">
<!-- <mat-error *ngIf="depositForm.get('holderType')?.errors?.['required']">
Holder type is required
</mat-error>
</mat-error> -->
</div>
<div class="form-row">
<mat-label>USCIB Member</mat-label>
<mat-radio-group formControlName="uscibMember" required class="horizontal-group">
<mat-radio-button *ngFor="let type of yesNoOptions" [value]="type.value" class="radio-button">
<mat-radio-button *ngFor="let type of uscibMemberOptions" [value]="type.value" class="radio-button">
{{ type.label }}
</mat-radio-button>
</mat-radio-group>

View File

@ -31,7 +31,7 @@ export class SecurityDepositComponent implements OnInit {
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['holderType', 'uscibMember', 'specialCommodity', 'specialCountry', 'rate', 'rateType', 'effectiveDate', 'actions'];
displayedColumns: string[] = ['holderType', 'uscibMember', 'specialCommodity', 'specialCountry', 'rate', 'rateType', 'effectiveDate', 'expiredDate', 'actions'];
dataSource = new MatTableDataSource<any>();
depositForm: FormGroup;
isEditing = false;
@ -55,9 +55,10 @@ export class SecurityDepositComponent implements OnInit {
@Input() userPreferences!: UserPreferences;
@Output() hasSecurityDeposits = new EventEmitter<boolean>();
yesNoOptions = [
{ value: 'Y', label: 'Yes' },
{ value: 'N', label: 'No' }
uscibMemberOptions = [
{ value: 'Yes', label: 'Yes' },
{ value: 'No', label: 'No' },
{ value: 'Both', label: 'Both' }
];
private fb = inject(FormBuilder);
@ -68,8 +69,8 @@ export class SecurityDepositComponent implements OnInit {
constructor() {
this.depositForm = this.fb.group({
holderType: ['CORP', Validators.required],
uscibMember: ['Y', Validators.required],
holderType: ['CORP'],
uscibMember: ['Yes', Validators.required],
specialCommodity: [''],
specialCountry: [''],
rate: [0, [Validators.required, Validators.min(0)]],
@ -134,7 +135,7 @@ export class SecurityDepositComponent implements OnInit {
loadHolderTypes(): void {
this.commonService.getHolderTypes().subscribe({
next: (holderTypes) => {
this.holderTypes = holderTypes;
this.holderTypes = [...holderTypes, { value: '', name: 'All', id: "0" }];
},
error: (error) => {
console.error('Error loading holder types:', error);
@ -156,7 +157,7 @@ export class SecurityDepositComponent implements OnInit {
this.currentDepositId = null;
this.depositForm.reset({
holderType: 'CORP',
uscibMember: 'N',
uscibMember: 'No',
specialCountry: '',
});
this.depositForm.patchValue({ rate: 0 });
@ -173,7 +174,7 @@ export class SecurityDepositComponent implements OnInit {
this.currentDepositId = deposit.securityDepositId;
this.depositForm.patchValue({
holderType: deposit.holderType,
uscibMember: deposit.uscibMember ? 'Y' : 'N',
uscibMember: deposit.uscibMember,
specialCommodity: deposit.specialCommodity,
specialCountry: deposit.specialCountry,
rate: deposit.rate,
@ -198,7 +199,6 @@ export class SecurityDepositComponent implements OnInit {
const depositData = {
...this.depositForm.value,
uscibMember: this.depositForm.value.uscibMember === 'Y',
specialCommodity: this.depositForm.value.specialCommodity || null,
specialCountry: this.depositForm.value.specialCountry || null
};
@ -257,7 +257,7 @@ export class SecurityDepositComponent implements OnInit {
this.currentDepositId = null;
this.depositForm.reset({
holderType: 'CORP',
uscibMember: 'N',
uscibMember: 'No',
specialCountry: '',
});
}

View File

@ -40,7 +40,7 @@
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Title</th>
<td mat-cell *matCellDef="let contact">{{ contact.title }}</td>
<td mat-cell *matCellDef="let contact">{{ getContactTitleLabel(contact.title) }}</td>
</ng-container>
<!-- Phone Column -->
@ -159,17 +159,16 @@
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Title</mat-label>
<input matInput formControlName="title" required>
<mat-icon matSuffix>work</mat-icon>
<mat-select formControlName="title" required>
<mat-option *ngFor="let contactTitle of contactTitles" [value]="contactTitle.value">
{{ contactTitle.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="contactForm.get('title')?.errors?.['required']">
Title is required
</mat-error>
<mat-error *ngIf="contactForm.get('title')?.errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>

View File

@ -14,8 +14,10 @@ import { UserPreferences } from '../../core/models/user-preference';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NotificationService } from '../../core/services/common/notification.service';
import { ContactService } from '../../core/services/service-provider/contact.service';
import { finalize } from 'rxjs';
import { finalize, Subject, takeUntil } from 'rxjs';
import { ContactLogin } from '../../core/models/service-provider/contact-login';
import { CommonService } from '../../core/services/common/common.service';
import { ContactTitle } from '../../core/models/contact-title';
@Component({
selector: 'app-contacts',
@ -42,6 +44,7 @@ export class ContactsComponent implements OnInit {
showForm = false;
showInactiveContacts = false;
contacts: Contact[] = [];
contactTitles: ContactTitle[] = [];
contactReadOnlyFields: any = {
lastChangedDate: null,
@ -54,8 +57,11 @@ export class ContactsComponent implements OnInit {
@Input() userPreferences: UserPreferences = {};
@Output() hasContacts = new EventEmitter<boolean>();
private destroy$ = new Subject<void>();
private fb = inject(FormBuilder);
private contactService = inject(ContactService);
private commonService = inject(CommonService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
private dialog = inject(MatDialog);
@ -65,7 +71,7 @@ export class ContactsComponent implements OnInit {
firstName: ['', [Validators.required, Validators.maxLength(50)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
middleInitial: ['', [Validators.maxLength(1)]],
title: ['', [Validators.required, Validators.maxLength(100)]],
title: ['', [Validators.required]],
phone: ['', [Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
mobile: ['', [Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
fax: ['', [Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
@ -75,9 +81,15 @@ export class ContactsComponent implements OnInit {
}
ngOnInit(): void {
this.loadContactTitles();
this.loadContacts();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
@ -101,6 +113,19 @@ export class ContactsComponent implements OnInit {
});
}
loadContactTitles(): void {
this.commonService.getContactTitles()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (contactTitles) => {
this.contactTitles = contactTitles;
},
error: (error) => {
console.error('Failed to load contact titles', error);
}
});
}
toggleShowInactiveContacts(): void {
this.showInactiveContacts = !this.showInactiveContacts;
this.renderContacts();
@ -281,4 +306,9 @@ export class ContactsComponent implements OnInit {
}
});
}
getContactTitleLabel(value: string): string {
const type = this.contactTitles.find(t => t.value === value);
return type ? type.name : value;
}
}