@idsoftsource/initial-process 1.4.9 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,7 +6,7 @@ import * as i8 from '@angular/forms';
|
|
|
6
6
|
import { NG_VALUE_ACCESSOR, COMPOSITION_BUFFER_MODE, Validators, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
7
7
|
import * as i2 from '@angular/router';
|
|
8
8
|
import { RouterModule } from '@angular/router';
|
|
9
|
-
import { map, of, tap as tap$1, Subscription, switchMap, finalize, catchError, EMPTY,
|
|
9
|
+
import { map, of, tap as tap$1, Subscription, switchMap, finalize, catchError, EMPTY, firstValueFrom, Subject, takeUntil } from 'rxjs';
|
|
10
10
|
import * as i1 from '@angular/common/http';
|
|
11
11
|
import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
12
12
|
import * as i7 from 'ngx-bootstrap/modal';
|
|
@@ -5160,7 +5160,6 @@ class ToolsComponent {
|
|
|
5160
5160
|
setTimeout(() => {
|
|
5161
5161
|
this.homeLoader = false;
|
|
5162
5162
|
window.location.href = this.libConfig.dashboardUrl;
|
|
5163
|
-
;
|
|
5164
5163
|
}, 2000); // 2 seconds
|
|
5165
5164
|
},
|
|
5166
5165
|
error: (err) => {
|
|
@@ -29171,7 +29170,8 @@ class FirstComponent {
|
|
|
29171
29170
|
this.isUploading = true;
|
|
29172
29171
|
setTimeout(() => {
|
|
29173
29172
|
this.store.addSuccess(true);
|
|
29174
|
-
this.saveAWSFile();
|
|
29173
|
+
// this.saveAWSFile();
|
|
29174
|
+
this.uploadresume();
|
|
29175
29175
|
}, 1500);
|
|
29176
29176
|
}
|
|
29177
29177
|
// ✅ MUST ADD THIS LINE
|
|
@@ -29188,70 +29188,72 @@ class FirstComponent {
|
|
|
29188
29188
|
this.store.addSuccess(false);
|
|
29189
29189
|
this.store.nextStep();
|
|
29190
29190
|
}
|
|
29191
|
-
async saveAWSFile() {
|
|
29192
|
-
|
|
29193
|
-
|
|
29194
|
-
|
|
29195
|
-
|
|
29196
|
-
|
|
29197
|
-
|
|
29198
|
-
|
|
29199
|
-
|
|
29200
|
-
|
|
29201
|
-
|
|
29202
|
-
|
|
29203
|
-
|
|
29204
|
-
|
|
29205
|
-
|
|
29206
|
-
|
|
29207
|
-
|
|
29208
|
-
|
|
29209
|
-
|
|
29210
|
-
|
|
29211
|
-
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
|
|
29217
|
-
|
|
29218
|
-
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29223
|
-
|
|
29224
|
-
|
|
29225
|
-
|
|
29191
|
+
// private async saveAWSFile() {
|
|
29192
|
+
// const fileType = this.fileData!.type;
|
|
29193
|
+
// const fileExtension = fileType.split('/')[1] || fileType.split('/')[0] || 'file';
|
|
29194
|
+
// const fileName = `${new uuid().newId()}.${fileExtension}`;
|
|
29195
|
+
// const key = `User/${this.userId}/Resume/${fileName}`;
|
|
29196
|
+
// const params = { key, contentType: fileType, Expires: 300 };
|
|
29197
|
+
// const result = await this.fileService.uploadImageAsync(this.fileData!, params);
|
|
29198
|
+
// console.log(result);
|
|
29199
|
+
// if (!result.success) { return; }
|
|
29200
|
+
// const files: AwsFileRequestModel = {
|
|
29201
|
+
// fileName: this.fileData!.name,
|
|
29202
|
+
// fileSize: this.fileData!.size,
|
|
29203
|
+
// fileType: this.fileData!.type,
|
|
29204
|
+
// fileLocation: result.publicUrl,
|
|
29205
|
+
// encrypted: true,
|
|
29206
|
+
// publicUrl: result.publicUrl
|
|
29207
|
+
// };
|
|
29208
|
+
// files.fileLocation = files.fileLocation.replace(/^https?:\/\/[^/]+\//, '');
|
|
29209
|
+
// files.publicUrl = files.publicUrl.replace(/^https?:\/\/[^/]+\//, '');
|
|
29210
|
+
// console.log(files)
|
|
29211
|
+
// this.fileService.awsFileUpload([files]).subscribe((res: AwsFileResponseModel[]) => {
|
|
29212
|
+
// if (res?.[0]?.fileId) {
|
|
29213
|
+
// this.model.fileId = res[0].fileId;
|
|
29214
|
+
// this.model.fileUrl = res[0].publicUrl;
|
|
29215
|
+
// this.model.fileName = this.fileData!.name;
|
|
29216
|
+
// this.awsFileUpdate.push({
|
|
29217
|
+
// fileId: res?.[0]?.fileId,
|
|
29218
|
+
// isActive: true,
|
|
29219
|
+
// });
|
|
29220
|
+
// }
|
|
29221
|
+
// this.uploadresume();
|
|
29222
|
+
// },);
|
|
29223
|
+
// }
|
|
29224
|
+
// uploadresume() {
|
|
29225
|
+
// this.isUploading = true;
|
|
29226
|
+
// const model = {
|
|
29227
|
+
// s3Key: this.model.fileUrl,
|
|
29228
|
+
// userId: this.userId
|
|
29229
|
+
// };
|
|
29230
|
+
// this.resumedetail.uploadResume(model).subscribe({
|
|
29231
|
+
// next: (res: any) => {
|
|
29232
|
+
// if (res?.failed) {
|
|
29233
|
+
// this.isUploading = false;
|
|
29234
|
+
// this.resumeName = null;
|
|
29235
|
+
// this.fileData = null;
|
|
29236
|
+
// this.model = { fileId: null, fileUrl: null, fileName: null };
|
|
29237
|
+
// return;
|
|
29238
|
+
// }
|
|
29239
|
+
// this.handleAutoNavigation();
|
|
29240
|
+
// this.resumeData = res as ResumeProfile;
|
|
29241
|
+
// this.store.setProfile(this.resumeData);
|
|
29242
|
+
// console.log('Resume Loaded Successfully:', this.resumeData);
|
|
29243
|
+
// console.log(`Working with ${this.resumeData.basicDetails.firstName}`);
|
|
29244
|
+
// this.isUploading = false;
|
|
29245
|
+
// },
|
|
29246
|
+
// error: (err) => {
|
|
29247
|
+
// console.error('Upload failed:', err);
|
|
29248
|
+
// this.isUploading = false;
|
|
29249
|
+
// this.resumeName = null;
|
|
29250
|
+
// this.fileData = null;
|
|
29251
|
+
// }
|
|
29252
|
+
// });
|
|
29253
|
+
// }
|
|
29226
29254
|
uploadresume() {
|
|
29227
|
-
this.
|
|
29228
|
-
|
|
29229
|
-
s3Key: this.model.fileUrl,
|
|
29230
|
-
userId: this.userId
|
|
29231
|
-
};
|
|
29232
|
-
this.resumedetail.uploadResume(model).subscribe({
|
|
29233
|
-
next: (res) => {
|
|
29234
|
-
if (res?.failed) {
|
|
29235
|
-
this.isUploading = false;
|
|
29236
|
-
this.resumeName = null; // ✅ RESET
|
|
29237
|
-
this.fileData = null; // ✅ RESET
|
|
29238
|
-
this.model = { fileId: null, fileUrl: null, fileName: null }; // ✅ RESET
|
|
29239
|
-
return;
|
|
29240
|
-
}
|
|
29241
|
-
this.handleAutoNavigation();
|
|
29242
|
-
this.resumeData = res;
|
|
29243
|
-
this.store.setProfile(this.resumeData);
|
|
29244
|
-
console.log('Resume Loaded Successfully:', this.resumeData);
|
|
29245
|
-
console.log(`Working with ${this.resumeData.basicDetails.firstName}`);
|
|
29246
|
-
this.isUploading = false;
|
|
29247
|
-
},
|
|
29248
|
-
error: (err) => {
|
|
29249
|
-
console.error('Upload failed:', err);
|
|
29250
|
-
this.isUploading = false;
|
|
29251
|
-
this.resumeName = null; // ✅ RESET
|
|
29252
|
-
this.fileData = null; // ✅ RESET
|
|
29253
|
-
}
|
|
29254
|
-
});
|
|
29255
|
+
this.store.setProfile(this.sample);
|
|
29256
|
+
this.handleAutoNavigation();
|
|
29255
29257
|
}
|
|
29256
29258
|
onBackClick() {
|
|
29257
29259
|
this.backToParent.emit();
|
|
@@ -29282,7 +29284,6 @@ class PreviewComponent {
|
|
|
29282
29284
|
providerId;
|
|
29283
29285
|
providerName;
|
|
29284
29286
|
roleData;
|
|
29285
|
-
cloudfrontUrl;
|
|
29286
29287
|
backToParent = new EventEmitter();
|
|
29287
29288
|
fileData = null;
|
|
29288
29289
|
model = { fileId: null, fileUrl: null, fileName: null };
|
|
@@ -29293,14 +29294,37 @@ class PreviewComponent {
|
|
|
29293
29294
|
isSavingEducation = false;
|
|
29294
29295
|
isSavingCertification = false;
|
|
29295
29296
|
isSavingLicense = false;
|
|
29297
|
+
isSavingBasic = false;
|
|
29298
|
+
basicDetailsSaved = false;
|
|
29299
|
+
hasUserDetailData = false;
|
|
29300
|
+
isSavingSkill = false;
|
|
29301
|
+
isSavingTool = false;
|
|
29296
29302
|
payloadUserId;
|
|
29297
29303
|
payloadUserName;
|
|
29304
|
+
cloudfrontUrl;
|
|
29305
|
+
workExperienceServerIds = {};
|
|
29306
|
+
educationServerIds = {};
|
|
29307
|
+
certificationServerIds = {};
|
|
29308
|
+
licenseServerIds = {};
|
|
29309
|
+
skillServerIds = {};
|
|
29310
|
+
toolServerIds = {};
|
|
29311
|
+
workExperienceLocallySaved = {};
|
|
29312
|
+
educationLocallySaved = {};
|
|
29313
|
+
certificationLocallySaved = {};
|
|
29314
|
+
licenseLocallySaved = {};
|
|
29315
|
+
skillLocallySaved = {};
|
|
29316
|
+
toolLocallySaved = {};
|
|
29317
|
+
uploadFolderBySection = {
|
|
29318
|
+
work: 'Experience',
|
|
29319
|
+
education: 'Education',
|
|
29320
|
+
certification: 'Certification',
|
|
29321
|
+
license: 'License'
|
|
29322
|
+
};
|
|
29298
29323
|
resumeData;
|
|
29299
29324
|
statusList = [];
|
|
29300
29325
|
showPopup = false;
|
|
29301
29326
|
showBackConfirmPopup = false;
|
|
29302
|
-
|
|
29303
|
-
userName;
|
|
29327
|
+
showDashboardConfirmPopup = false;
|
|
29304
29328
|
constructor(store, fileService, userSkillSetService, userToolService, userDocumentService, userEducation, userDetailService, userExperienceService, tokenService, libConfig) {
|
|
29305
29329
|
this.store = store;
|
|
29306
29330
|
this.fileService = fileService;
|
|
@@ -29315,13 +29339,6 @@ class PreviewComponent {
|
|
|
29315
29339
|
this.resumeData = this.store.profileSignal();
|
|
29316
29340
|
}
|
|
29317
29341
|
email;
|
|
29318
|
-
showLoader = false;
|
|
29319
|
-
uploadFolderBySection = {
|
|
29320
|
-
work: 'Experience',
|
|
29321
|
-
education: 'Education',
|
|
29322
|
-
certification: 'Certification',
|
|
29323
|
-
license: 'License'
|
|
29324
|
-
};
|
|
29325
29342
|
expYears = [];
|
|
29326
29343
|
details = signal(undefined, ...(ngDevMode ? [{ debugName: "details" }] : []));
|
|
29327
29344
|
async ngOnInit() {
|
|
@@ -29331,14 +29348,11 @@ class PreviewComponent {
|
|
|
29331
29348
|
this.details.set(initialData);
|
|
29332
29349
|
}
|
|
29333
29350
|
this.prefillSectionFieldsFromBasicDetails();
|
|
29334
|
-
// Use 1..30 years of experience.
|
|
29335
29351
|
this.expYears = Array.from({ length: 30 }, (_, i) => i + 1);
|
|
29352
|
+
await this.loadSavedSectionsFromApis();
|
|
29336
29353
|
}
|
|
29337
29354
|
experience = computed(() => this.store.profileSignal()?.workExperience || [], ...(ngDevMode ? [{ debugName: "experience" }] : []));
|
|
29338
|
-
educationList = computed(() => {
|
|
29339
|
-
const list = this.store.profileSignal()?.education || [];
|
|
29340
|
-
return [...list].sort((a, b) => b.endDate.localeCompare(a.endDate));
|
|
29341
|
-
}, ...(ngDevMode ? [{ debugName: "educationList" }] : []));
|
|
29355
|
+
educationList = computed(() => this.store.profileSignal()?.education || [], ...(ngDevMode ? [{ debugName: "educationList" }] : []));
|
|
29342
29356
|
certs = computed(() => this.store.profileSignal()?.certifications || [], ...(ngDevMode ? [{ debugName: "certs" }] : []));
|
|
29343
29357
|
licenses = computed(() => this.store.profileSignal()?.licenses || [], ...(ngDevMode ? [{ debugName: "licenses" }] : []));
|
|
29344
29358
|
skills = computed(() => this.store.profileSignal()?.skills || [], ...(ngDevMode ? [{ debugName: "skills" }] : []));
|
|
@@ -29355,6 +29369,10 @@ class PreviewComponent {
|
|
|
29355
29369
|
const digits = (value ?? '').toString().replace(/\D/g, '');
|
|
29356
29370
|
return digits.slice(0, 10);
|
|
29357
29371
|
}
|
|
29372
|
+
sanitizeZipCode(value) {
|
|
29373
|
+
const digits = (value ?? '').toString().replace(/\D/g, '');
|
|
29374
|
+
return digits.slice(0, 6);
|
|
29375
|
+
}
|
|
29358
29376
|
isMonthRangeInvalid(start, end) {
|
|
29359
29377
|
if (this.isBlankOrNull(start) || this.isBlankOrNull(end))
|
|
29360
29378
|
return false;
|
|
@@ -29460,6 +29478,9 @@ class PreviewComponent {
|
|
|
29460
29478
|
issues.push('State is required');
|
|
29461
29479
|
if (this.isBlank(item.zipCode))
|
|
29462
29480
|
issues.push('Zip code is required');
|
|
29481
|
+
if (!this.isBlank(item.zipCode) && !/^\d{1,6}$/.test((item.zipCode ?? '').trim())) {
|
|
29482
|
+
issues.push('Zip code must be up to 6 digits');
|
|
29483
|
+
}
|
|
29463
29484
|
if (this.isBlank(item.country))
|
|
29464
29485
|
issues.push('Country is required');
|
|
29465
29486
|
if (this.isBlank(item.phone))
|
|
@@ -29605,26 +29626,66 @@ class PreviewComponent {
|
|
|
29605
29626
|
return issues;
|
|
29606
29627
|
}, ...(ngDevMode ? [{ debugName: "toolFormIssues" }] : []));
|
|
29607
29628
|
workSectionHasIssues = computed(() => {
|
|
29608
|
-
|
|
29629
|
+
const hasValidationIssues = this.workIssuesByIndex().some((x) => x.length > 0);
|
|
29630
|
+
const hasUnsavedItems = this.workSectionHasUnsavedItems();
|
|
29631
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29609
29632
|
}, ...(ngDevMode ? [{ debugName: "workSectionHasIssues" }] : []));
|
|
29610
29633
|
educationSectionHasIssues = computed(() => {
|
|
29611
|
-
|
|
29634
|
+
const hasValidationIssues = this.educationIssuesByIndex().some((x) => x.length > 0);
|
|
29635
|
+
const hasUnsavedItems = this.educationSectionHasUnsavedItems();
|
|
29636
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29612
29637
|
}, ...(ngDevMode ? [{ debugName: "educationSectionHasIssues" }] : []));
|
|
29613
29638
|
certificationsSectionHasIssues = computed(() => {
|
|
29614
|
-
|
|
29639
|
+
const hasValidationIssues = this.certIssuesByIndex().some((x) => x.length > 0);
|
|
29640
|
+
const hasUnsavedItems = this.certificationSectionHasUnsavedItems();
|
|
29641
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29615
29642
|
}, ...(ngDevMode ? [{ debugName: "certificationsSectionHasIssues" }] : []));
|
|
29616
|
-
licensesSectionHasIssues = computed(() =>
|
|
29617
|
-
|
|
29618
|
-
|
|
29619
|
-
|
|
29620
|
-
|
|
29621
|
-
|
|
29622
|
-
|
|
29623
|
-
|
|
29624
|
-
|
|
29625
|
-
|
|
29626
|
-
|
|
29627
|
-
|
|
29643
|
+
licensesSectionHasIssues = computed(() => {
|
|
29644
|
+
const hasValidationIssues = this.licenses().some((_, i) => (this.licenseIssuesByIndex()[i] || []).length > 0);
|
|
29645
|
+
const hasUnsavedItems = this.licenseSectionHasUnsavedItems();
|
|
29646
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29647
|
+
}, ...(ngDevMode ? [{ debugName: "licensesSectionHasIssues" }] : []));
|
|
29648
|
+
skillsSectionHasIssues = computed(() => {
|
|
29649
|
+
const hasValidationIssues = this.skillIssuesByIndex().some((x) => x.length > 0);
|
|
29650
|
+
const hasUnsavedItems = this.skillsSectionHasUnsavedItems();
|
|
29651
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29652
|
+
}, ...(ngDevMode ? [{ debugName: "skillsSectionHasIssues" }] : []));
|
|
29653
|
+
toolsSectionHasIssues = computed(() => {
|
|
29654
|
+
const hasValidationIssues = this.toolIssuesByIndex().some((x) => x.length > 0);
|
|
29655
|
+
const hasUnsavedItems = this.toolsSectionHasUnsavedItems();
|
|
29656
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29657
|
+
}, ...(ngDevMode ? [{ debugName: "toolsSectionHasIssues" }] : []));
|
|
29658
|
+
workSectionHasUnsavedItems() {
|
|
29659
|
+
return this.experience().some((_, i) => this.hasUnsavedWorkItem(i));
|
|
29660
|
+
}
|
|
29661
|
+
educationSectionHasUnsavedItems() {
|
|
29662
|
+
return this.educationList().some((_, i) => this.hasUnsavedEducationItem(i));
|
|
29663
|
+
}
|
|
29664
|
+
certificationSectionHasUnsavedItems() {
|
|
29665
|
+
return this.certs().some((_, i) => this.hasUnsavedCertificationItem(i));
|
|
29666
|
+
}
|
|
29667
|
+
licenseSectionHasUnsavedItems() {
|
|
29668
|
+
return this.licenses().some((_, i) => this.hasUnsavedLicenseItem(i));
|
|
29669
|
+
}
|
|
29670
|
+
skillsSectionHasUnsavedItems() {
|
|
29671
|
+
return this.skills().some((_, i) => this.hasUnsavedSkillItem(i));
|
|
29672
|
+
}
|
|
29673
|
+
toolsSectionHasUnsavedItems() {
|
|
29674
|
+
return this.tools().some((_, i) => this.hasUnsavedToolItem(i));
|
|
29675
|
+
}
|
|
29676
|
+
isAnyEditorOpen = computed(() => {
|
|
29677
|
+
return this.isEditMode() ||
|
|
29678
|
+
this.jobEditor().mode !== 'closed' ||
|
|
29679
|
+
this.educationEditor().mode !== 'closed' ||
|
|
29680
|
+
this.certificationEditor().mode !== 'closed' ||
|
|
29681
|
+
this.licenseEditor().mode !== 'closed' ||
|
|
29682
|
+
this.skillEditor().mode !== 'closed' ||
|
|
29683
|
+
this.toolEditor().mode !== 'closed';
|
|
29684
|
+
}, ...(ngDevMode ? [{ debugName: "isAnyEditorOpen" }] : []));
|
|
29685
|
+
canConfirmAndContinue() {
|
|
29686
|
+
// Go to Dashboard enablement depends only on UserDetail/GetByUserId having data.
|
|
29687
|
+
return this.hasUserDetailData;
|
|
29688
|
+
}
|
|
29628
29689
|
expandedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "expandedIndex" }] : []));
|
|
29629
29690
|
toggleJob(index) {
|
|
29630
29691
|
this.expandedIndex.update(current => current === index ? -1 : index);
|
|
@@ -29662,6 +29723,493 @@ class PreviewComponent {
|
|
|
29662
29723
|
}
|
|
29663
29724
|
return value;
|
|
29664
29725
|
}
|
|
29726
|
+
buildSectionQuery(orderBy, includeForUser = false) {
|
|
29727
|
+
return {
|
|
29728
|
+
page: 1,
|
|
29729
|
+
pageSize: 100,
|
|
29730
|
+
filter: includeForUser ? `userId=${this.payloadUserId},forUser=2` : `userId=${this.payloadUserId}`,
|
|
29731
|
+
orderBy,
|
|
29732
|
+
};
|
|
29733
|
+
}
|
|
29734
|
+
buildUserName() {
|
|
29735
|
+
const basic = this.store.profileSignal()?.basicDetails;
|
|
29736
|
+
const fullName = `${basic?.firstName ?? ''} ${basic?.lastName ?? ''}`.trim();
|
|
29737
|
+
this.payloadUserName = fullName;
|
|
29738
|
+
}
|
|
29739
|
+
getCreatedId(res) {
|
|
29740
|
+
const directId = res?.id ?? res?.value?.id ?? res?.data?.id ?? null;
|
|
29741
|
+
if (typeof directId === 'string' && directId.trim())
|
|
29742
|
+
return directId;
|
|
29743
|
+
const fromArray = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : null);
|
|
29744
|
+
const firstWithId = fromArray?.find((item) => typeof item?.id === 'string' && item.id.trim());
|
|
29745
|
+
if (firstWithId?.id)
|
|
29746
|
+
return firstWithId.id;
|
|
29747
|
+
return null;
|
|
29748
|
+
}
|
|
29749
|
+
getEntityId(item) {
|
|
29750
|
+
const candidates = [
|
|
29751
|
+
item?.id,
|
|
29752
|
+
item?.userExperienceId,
|
|
29753
|
+
item?.userEducationId,
|
|
29754
|
+
item?.userDocumentId,
|
|
29755
|
+
item?.userSkillSetId,
|
|
29756
|
+
item?.userToolId,
|
|
29757
|
+
item?.skillSetId,
|
|
29758
|
+
item?.toolId,
|
|
29759
|
+
];
|
|
29760
|
+
const found = candidates.find((x) => typeof x === 'string' && x.trim());
|
|
29761
|
+
return found ?? null;
|
|
29762
|
+
}
|
|
29763
|
+
reindexServerIdsAfterDelete(serverIds, deletedIndex) {
|
|
29764
|
+
const next = {};
|
|
29765
|
+
Object.keys(serverIds).forEach((k) => {
|
|
29766
|
+
const idx = Number(k);
|
|
29767
|
+
if (Number.isNaN(idx) || idx === deletedIndex)
|
|
29768
|
+
return;
|
|
29769
|
+
if (idx < deletedIndex)
|
|
29770
|
+
next[idx] = serverIds[idx];
|
|
29771
|
+
if (idx > deletedIndex)
|
|
29772
|
+
next[idx - 1] = serverIds[idx];
|
|
29773
|
+
});
|
|
29774
|
+
return next;
|
|
29775
|
+
}
|
|
29776
|
+
reindexSavedFlagsAfterDelete(flags, deletedIndex) {
|
|
29777
|
+
const next = {};
|
|
29778
|
+
Object.keys(flags).forEach((k) => {
|
|
29779
|
+
const idx = Number(k);
|
|
29780
|
+
if (Number.isNaN(idx) || idx === deletedIndex)
|
|
29781
|
+
return;
|
|
29782
|
+
if (idx < deletedIndex)
|
|
29783
|
+
next[idx] = !!flags[idx];
|
|
29784
|
+
if (idx > deletedIndex)
|
|
29785
|
+
next[idx - 1] = !!flags[idx];
|
|
29786
|
+
});
|
|
29787
|
+
return next;
|
|
29788
|
+
}
|
|
29789
|
+
markItemSaved(serverIds, localSaved, index, createdId) {
|
|
29790
|
+
if (createdId)
|
|
29791
|
+
serverIds[index] = createdId;
|
|
29792
|
+
localSaved[index] = true;
|
|
29793
|
+
}
|
|
29794
|
+
hasUnsavedWorkItem(index) {
|
|
29795
|
+
return !this.workExperienceServerIds[index] && !this.workExperienceLocallySaved[index];
|
|
29796
|
+
}
|
|
29797
|
+
hasUnsavedEducationItem(index) {
|
|
29798
|
+
return !this.educationServerIds[index] && !this.educationLocallySaved[index];
|
|
29799
|
+
}
|
|
29800
|
+
hasUnsavedCertificationItem(index) {
|
|
29801
|
+
return !this.certificationServerIds[index] && !this.certificationLocallySaved[index];
|
|
29802
|
+
}
|
|
29803
|
+
hasUnsavedLicenseItem(index) {
|
|
29804
|
+
return !this.licenseServerIds[index] && !this.licenseLocallySaved[index];
|
|
29805
|
+
}
|
|
29806
|
+
hasUnsavedSkillItem(index) {
|
|
29807
|
+
return !this.skillServerIds[index] && !this.skillLocallySaved[index];
|
|
29808
|
+
}
|
|
29809
|
+
hasUnsavedToolItem(index) {
|
|
29810
|
+
return !this.toolServerIds[index] && !this.toolLocallySaved[index];
|
|
29811
|
+
}
|
|
29812
|
+
mapSavedWorkToPreview(item) {
|
|
29813
|
+
return {
|
|
29814
|
+
company: item?.companyName ?? '',
|
|
29815
|
+
jobTitle: item?.jobTitle ?? '',
|
|
29816
|
+
country: item?.country ?? '',
|
|
29817
|
+
state: item?.state ?? '',
|
|
29818
|
+
city: item?.city ?? '',
|
|
29819
|
+
startDate: this.toMonthInput(item?.fromDate),
|
|
29820
|
+
endDate: item?.toDate ? this.toMonthInput(item?.toDate) : null,
|
|
29821
|
+
isCurrent: !item?.toDate,
|
|
29822
|
+
responsibilities: (item?.jobDescription ?? '').split(',').map((x) => x.trim()).filter(Boolean),
|
|
29823
|
+
fileId: item?.fileId ?? null,
|
|
29824
|
+
fileUrl: item?.fileUrl ?? null,
|
|
29825
|
+
fileName: item?.fileName ?? null,
|
|
29826
|
+
fileObject: null,
|
|
29827
|
+
};
|
|
29828
|
+
}
|
|
29829
|
+
mapSavedEducationToPreview(item) {
|
|
29830
|
+
return {
|
|
29831
|
+
degree: item?.courseName ?? '',
|
|
29832
|
+
degreeType: item?.courseType ?? '',
|
|
29833
|
+
institution: item?.instituteName ?? '',
|
|
29834
|
+
country: item?.country ?? '',
|
|
29835
|
+
state: item?.state ?? '',
|
|
29836
|
+
city: item?.city ?? '',
|
|
29837
|
+
startDate: this.toMonthInput(item?.startDate),
|
|
29838
|
+
endDate: this.toMonthInput(item?.endDate),
|
|
29839
|
+
achievements: item?.comments ? [item.comments] : [],
|
|
29840
|
+
fileId: item?.fileId ?? null,
|
|
29841
|
+
fileUrl: item?.fileUrl ?? null,
|
|
29842
|
+
fileName: item?.fileName ?? null,
|
|
29843
|
+
fileObject: null,
|
|
29844
|
+
};
|
|
29845
|
+
}
|
|
29846
|
+
mapSavedCertificationToPreview(item) {
|
|
29847
|
+
return {
|
|
29848
|
+
name: item?.documentTypeName ?? '',
|
|
29849
|
+
issuingOrganization: item?.issuedBy ?? null,
|
|
29850
|
+
state: item?.issuedState ?? null,
|
|
29851
|
+
issueDate: item?.issueDate ? this.toMonthInput(item.issueDate) : null,
|
|
29852
|
+
expiryDate: item?.expiryDate ? this.toMonthInput(item.expiryDate) : null,
|
|
29853
|
+
credentialId: item?.number ? String(item.number) : null,
|
|
29854
|
+
fileId: item?.fileId ?? null,
|
|
29855
|
+
fileUrl: item?.fileUrl ?? null,
|
|
29856
|
+
fileName: item?.fileName ?? null,
|
|
29857
|
+
fileObject: null,
|
|
29858
|
+
};
|
|
29859
|
+
}
|
|
29860
|
+
mapSavedLicenseToPreview(item) {
|
|
29861
|
+
return {
|
|
29862
|
+
name: item?.documentTypeName ?? '',
|
|
29863
|
+
issuingAuthority: item?.issuedBy ?? null,
|
|
29864
|
+
licenseNumber: item?.number ? String(item.number) : null,
|
|
29865
|
+
state: item?.issuedState ?? null,
|
|
29866
|
+
issueDate: item?.issueDate ? this.toMonthInput(item.issueDate) : null,
|
|
29867
|
+
expiryDate: item?.expiryDate ? this.toMonthInput(item.expiryDate) : null,
|
|
29868
|
+
fileId: item?.fileId ?? null,
|
|
29869
|
+
fileUrl: item?.fileUrl ?? null,
|
|
29870
|
+
fileName: item?.fileName ?? null,
|
|
29871
|
+
fileObject: null,
|
|
29872
|
+
};
|
|
29873
|
+
}
|
|
29874
|
+
normalizedText(value) {
|
|
29875
|
+
return (value ?? '').toString().trim().toLowerCase();
|
|
29876
|
+
}
|
|
29877
|
+
workSignature(item) {
|
|
29878
|
+
return [
|
|
29879
|
+
this.normalizedText(item.company),
|
|
29880
|
+
this.normalizedText(item.jobTitle),
|
|
29881
|
+
this.normalizedText(item.startDate),
|
|
29882
|
+
this.normalizedText(item.endDate),
|
|
29883
|
+
item.isCurrent ? '1' : '0',
|
|
29884
|
+
].join('|');
|
|
29885
|
+
}
|
|
29886
|
+
educationSignature(item) {
|
|
29887
|
+
return [
|
|
29888
|
+
this.normalizedText(item.degree),
|
|
29889
|
+
this.normalizedText(item.institution),
|
|
29890
|
+
this.normalizedText(item.startDate),
|
|
29891
|
+
this.normalizedText(item.endDate),
|
|
29892
|
+
].join('|');
|
|
29893
|
+
}
|
|
29894
|
+
certificationSignature(item) {
|
|
29895
|
+
return [
|
|
29896
|
+
this.normalizedText(item.name),
|
|
29897
|
+
this.normalizedText(item.issuingOrganization),
|
|
29898
|
+
this.normalizedText(item.state),
|
|
29899
|
+
this.normalizedText(item.issueDate),
|
|
29900
|
+
this.normalizedText(item.expiryDate),
|
|
29901
|
+
].join('|');
|
|
29902
|
+
}
|
|
29903
|
+
licenseSignature(item) {
|
|
29904
|
+
return [
|
|
29905
|
+
this.normalizedText(item.name),
|
|
29906
|
+
this.normalizedText(item.issuingAuthority),
|
|
29907
|
+
this.normalizedText(item.state),
|
|
29908
|
+
this.normalizedText(item.issueDate),
|
|
29909
|
+
this.normalizedText(item.expiryDate),
|
|
29910
|
+
].join('|');
|
|
29911
|
+
}
|
|
29912
|
+
mergeSavedAndCurrent(savedItems, currentItems, signature) {
|
|
29913
|
+
const merged = [];
|
|
29914
|
+
const matchedCurrentIndexes = new Set();
|
|
29915
|
+
const appendedCurrentIndexes = [];
|
|
29916
|
+
const signatureMap = new Map();
|
|
29917
|
+
currentItems.forEach((item, idx) => {
|
|
29918
|
+
const key = signature(item);
|
|
29919
|
+
const list = signatureMap.get(key) ?? [];
|
|
29920
|
+
list.push(idx);
|
|
29921
|
+
signatureMap.set(key, list);
|
|
29922
|
+
});
|
|
29923
|
+
savedItems.forEach((savedItem) => {
|
|
29924
|
+
merged.push(savedItem);
|
|
29925
|
+
const key = signature(savedItem);
|
|
29926
|
+
const candidates = signatureMap.get(key);
|
|
29927
|
+
if (candidates && candidates.length > 0) {
|
|
29928
|
+
const matched = candidates.shift();
|
|
29929
|
+
if (matched !== undefined)
|
|
29930
|
+
matchedCurrentIndexes.add(matched);
|
|
29931
|
+
}
|
|
29932
|
+
});
|
|
29933
|
+
currentItems.forEach((item, idx) => {
|
|
29934
|
+
if (!matchedCurrentIndexes.has(idx)) {
|
|
29935
|
+
merged.push(item);
|
|
29936
|
+
appendedCurrentIndexes.push(idx);
|
|
29937
|
+
}
|
|
29938
|
+
});
|
|
29939
|
+
return { merged, matchedCurrentIndexes, appendedCurrentIndexes };
|
|
29940
|
+
}
|
|
29941
|
+
async ensureWorkServerId(index, item) {
|
|
29942
|
+
if (this.workExperienceServerIds[index] || !this.payloadUserId)
|
|
29943
|
+
return;
|
|
29944
|
+
try {
|
|
29945
|
+
const res = await firstValueFrom(this.userExperienceService.getUserExperience(this.buildSectionQuery('createdDateTime asc')));
|
|
29946
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
29947
|
+
const target = this.workSignature(item);
|
|
29948
|
+
const match = saved.find((x) => this.workSignature(this.mapSavedWorkToPreview(x)) === target);
|
|
29949
|
+
const matchId = this.getEntityId(match);
|
|
29950
|
+
if (matchId)
|
|
29951
|
+
this.workExperienceServerIds[index] = matchId;
|
|
29952
|
+
}
|
|
29953
|
+
catch (err) {
|
|
29954
|
+
console.error('Unable to resolve work experience server id', err);
|
|
29955
|
+
}
|
|
29956
|
+
}
|
|
29957
|
+
async ensureEducationServerId(index, item) {
|
|
29958
|
+
if (this.educationServerIds[index] || !this.payloadUserId)
|
|
29959
|
+
return;
|
|
29960
|
+
try {
|
|
29961
|
+
const res = await firstValueFrom(this.userEducation.getUserEducation(this.buildSectionQuery('createdDateTime asc')));
|
|
29962
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
29963
|
+
const target = this.educationSignature(item);
|
|
29964
|
+
const match = saved.find((x) => this.educationSignature(this.mapSavedEducationToPreview(x)) === target);
|
|
29965
|
+
const matchId = this.getEntityId(match);
|
|
29966
|
+
if (matchId)
|
|
29967
|
+
this.educationServerIds[index] = matchId;
|
|
29968
|
+
}
|
|
29969
|
+
catch (err) {
|
|
29970
|
+
console.error('Unable to resolve education server id', err);
|
|
29971
|
+
}
|
|
29972
|
+
}
|
|
29973
|
+
async ensureCertificationServerId(index, item) {
|
|
29974
|
+
if (this.certificationServerIds[index] || !this.payloadUserId)
|
|
29975
|
+
return;
|
|
29976
|
+
try {
|
|
29977
|
+
const query = {
|
|
29978
|
+
page: 1,
|
|
29979
|
+
pageSize: 100,
|
|
29980
|
+
orderBy: 'createdDateTime asc',
|
|
29981
|
+
filter: `mainType=2,userId=${this.payloadUserId}`,
|
|
29982
|
+
};
|
|
29983
|
+
const res = await firstValueFrom(this.userDocumentService.getUserDocument(query));
|
|
29984
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
29985
|
+
const target = this.certificationSignature(item);
|
|
29986
|
+
const match = saved.find((x) => this.certificationSignature(this.mapSavedCertificationToPreview(x)) === target);
|
|
29987
|
+
const matchId = this.getEntityId(match);
|
|
29988
|
+
if (matchId)
|
|
29989
|
+
this.certificationServerIds[index] = matchId;
|
|
29990
|
+
}
|
|
29991
|
+
catch (err) {
|
|
29992
|
+
console.error('Unable to resolve certification server id', err);
|
|
29993
|
+
}
|
|
29994
|
+
}
|
|
29995
|
+
async ensureLicenseServerId(index, item) {
|
|
29996
|
+
if (this.licenseServerIds[index] || !this.payloadUserId)
|
|
29997
|
+
return;
|
|
29998
|
+
try {
|
|
29999
|
+
const query = {
|
|
30000
|
+
page: 1,
|
|
30001
|
+
pageSize: 10,
|
|
30002
|
+
orderBy: 'createdDateTime asc',
|
|
30003
|
+
filter: `mainType=1`,
|
|
30004
|
+
};
|
|
30005
|
+
const res = await firstValueFrom(this.userDocumentService
|
|
30006
|
+
.getUserDocument(query));
|
|
30007
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
30008
|
+
const target = this.licenseSignature(item);
|
|
30009
|
+
const match = saved.find((x) => this.licenseSignature(this.mapSavedLicenseToPreview(x)) === target);
|
|
30010
|
+
const matchId = this.getEntityId(match);
|
|
30011
|
+
if (matchId)
|
|
30012
|
+
this.licenseServerIds[index] = matchId;
|
|
30013
|
+
}
|
|
30014
|
+
catch (err) {
|
|
30015
|
+
console.error('Unable to resolve license server id', err);
|
|
30016
|
+
}
|
|
30017
|
+
}
|
|
30018
|
+
async ensureSkillServerId(index) {
|
|
30019
|
+
if (this.skillServerIds[index] || !this.payloadUserId)
|
|
30020
|
+
return;
|
|
30021
|
+
try {
|
|
30022
|
+
const payload = this.mapSkills(this.resumeData)[index];
|
|
30023
|
+
if (!payload)
|
|
30024
|
+
return;
|
|
30025
|
+
const res = await firstValueFrom(this.userSkillSetService.getUserSkillSet(this.buildSectionQuery('skillSetName asc', true)));
|
|
30026
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
30027
|
+
const targetName = this.normalizedText(payload.skillSetName);
|
|
30028
|
+
const targetYear = payload.year;
|
|
30029
|
+
const targetStars = payload.starRating;
|
|
30030
|
+
const match = saved.find((x) => this.normalizedText(x?.skillSetName) === targetName
|
|
30031
|
+
&& (x?.year ?? null) === (targetYear ?? null)
|
|
30032
|
+
&& (x?.starRating ?? null) === (targetStars ?? null));
|
|
30033
|
+
const matchId = this.getEntityId(match);
|
|
30034
|
+
if (matchId)
|
|
30035
|
+
this.skillServerIds[index] = matchId;
|
|
30036
|
+
}
|
|
30037
|
+
catch (err) {
|
|
30038
|
+
console.error('Unable to resolve skill server id', err);
|
|
30039
|
+
}
|
|
30040
|
+
}
|
|
30041
|
+
async ensureToolServerId(index) {
|
|
30042
|
+
if (this.toolServerIds[index] || !this.payloadUserId)
|
|
30043
|
+
return;
|
|
30044
|
+
try {
|
|
30045
|
+
const payload = this.mapTools(this.resumeData)[index];
|
|
30046
|
+
if (!payload)
|
|
30047
|
+
return;
|
|
30048
|
+
const res = await firstValueFrom(this.userToolService.getUserTool(this.buildSectionQuery('toolName asc', true)));
|
|
30049
|
+
const saved = Array.isArray(res?.data) ? res.data : [];
|
|
30050
|
+
const targetName = this.normalizedText(payload.toolName);
|
|
30051
|
+
const targetYear = payload.year;
|
|
30052
|
+
const targetStars = payload.starRating;
|
|
30053
|
+
const match = saved.find((x) => this.normalizedText(x?.toolName) === targetName
|
|
30054
|
+
&& (x?.year ?? null) === (targetYear ?? null)
|
|
30055
|
+
&& (x?.starRating ?? null) === (targetStars ?? null));
|
|
30056
|
+
const matchId = this.getEntityId(match);
|
|
30057
|
+
if (matchId)
|
|
30058
|
+
this.toolServerIds[index] = matchId;
|
|
30059
|
+
}
|
|
30060
|
+
catch (err) {
|
|
30061
|
+
console.error('Unable to resolve tool server id', err);
|
|
30062
|
+
}
|
|
30063
|
+
}
|
|
30064
|
+
async loadSavedSectionsFromApis() {
|
|
30065
|
+
const current = this.store.profileSignal();
|
|
30066
|
+
if (!current || !this.payloadUserId)
|
|
30067
|
+
return;
|
|
30068
|
+
try {
|
|
30069
|
+
this.workExperienceServerIds = {};
|
|
30070
|
+
this.educationServerIds = {};
|
|
30071
|
+
this.certificationServerIds = {};
|
|
30072
|
+
this.licenseServerIds = {};
|
|
30073
|
+
this.skillServerIds = {};
|
|
30074
|
+
this.toolServerIds = {};
|
|
30075
|
+
this.workExperienceLocallySaved = {};
|
|
30076
|
+
this.educationLocallySaved = {};
|
|
30077
|
+
this.certificationLocallySaved = {};
|
|
30078
|
+
this.licenseLocallySaved = {};
|
|
30079
|
+
this.skillLocallySaved = {};
|
|
30080
|
+
this.toolLocallySaved = {};
|
|
30081
|
+
const [workRes, educationRes, certificationsRes, licensesRes, skillsRes, toolsRes, userDetailRes] = await Promise.all([
|
|
30082
|
+
firstValueFrom(this.userExperienceService.getUserExperience(this.buildSectionQuery('createdDateTime asc'))),
|
|
30083
|
+
firstValueFrom(this.userEducation.getUserEducation(this.buildSectionQuery('createdDateTime asc'))),
|
|
30084
|
+
// ✅ Certifications (mainType = 2)
|
|
30085
|
+
firstValueFrom(this.userDocumentService.getUserDocument({
|
|
30086
|
+
page: 1,
|
|
30087
|
+
pageSize: 100,
|
|
30088
|
+
orderBy: 'createdDateTime asc',
|
|
30089
|
+
filter: `mainType=2,userId=${this.payloadUserId}`,
|
|
30090
|
+
})),
|
|
30091
|
+
// ✅ Licenses (mainType = 1)
|
|
30092
|
+
firstValueFrom(this.userDocumentService.getUserDocument({
|
|
30093
|
+
page: 1,
|
|
30094
|
+
pageSize: 100,
|
|
30095
|
+
orderBy: 'createdDateTime asc',
|
|
30096
|
+
filter: `mainType=1,userId=${this.payloadUserId}`,
|
|
30097
|
+
})),
|
|
30098
|
+
firstValueFrom(this.userSkillSetService.getUserSkillSet(this.buildSectionQuery('skillSetName asc', true))),
|
|
30099
|
+
firstValueFrom(this.userToolService.getUserTool(this.buildSectionQuery('toolName asc', true))),
|
|
30100
|
+
firstValueFrom(this.userDetailService.getByUserId(this.payloadUserId)).catch(() => null),
|
|
30101
|
+
]);
|
|
30102
|
+
// Enable dashboard button when GetByUserId has any payload.
|
|
30103
|
+
this.hasUserDetailData = this.hasUserDetailPayload(userDetailRes);
|
|
30104
|
+
// Mark basic details as already saved if user data exists on the server
|
|
30105
|
+
const savedBasicDetails = this.mapSavedBasicDetailsToPreview(userDetailRes);
|
|
30106
|
+
if (savedBasicDetails) {
|
|
30107
|
+
this.basicDetailsSaved = true;
|
|
30108
|
+
this.details.set(savedBasicDetails);
|
|
30109
|
+
}
|
|
30110
|
+
const savedWork = Array.isArray(workRes?.data) ? workRes.data : [];
|
|
30111
|
+
const savedEducation = Array.isArray(educationRes?.data) ? educationRes.data : [];
|
|
30112
|
+
const savedCertifications = Array.isArray(certificationsRes?.data) ? certificationsRes.data : [];
|
|
30113
|
+
const savedLicenses = Array.isArray(licensesRes?.data) ? licensesRes.data : [];
|
|
30114
|
+
const savedSkills = Array.isArray(skillsRes?.data) ? skillsRes.data : [];
|
|
30115
|
+
const savedTools = Array.isArray(toolsRes?.data) ? toolsRes.data : [];
|
|
30116
|
+
const savedWorkPreview = savedWork.map((item) => this.mapSavedWorkToPreview(item));
|
|
30117
|
+
const mergedWork = this.mergeSavedAndCurrent(savedWorkPreview, current.workExperience ?? [], (item) => this.workSignature(item));
|
|
30118
|
+
const nextWork = mergedWork.merged;
|
|
30119
|
+
savedWork.forEach((item, index) => {
|
|
30120
|
+
const id = this.getEntityId(item);
|
|
30121
|
+
if (id)
|
|
30122
|
+
this.workExperienceServerIds[index] = id;
|
|
30123
|
+
});
|
|
30124
|
+
const savedEducationPreview = savedEducation.map((item) => this.mapSavedEducationToPreview(item));
|
|
30125
|
+
const mergedEducation = this.mergeSavedAndCurrent(savedEducationPreview, current.education ?? [], (item) => this.educationSignature(item));
|
|
30126
|
+
const nextEducation = mergedEducation.merged;
|
|
30127
|
+
savedEducation.forEach((item, index) => {
|
|
30128
|
+
const id = this.getEntityId(item);
|
|
30129
|
+
if (id)
|
|
30130
|
+
this.educationServerIds[index] = id;
|
|
30131
|
+
});
|
|
30132
|
+
const savedCertPreview = savedCertifications.map((item) => this.mapSavedCertificationToPreview(item));
|
|
30133
|
+
const mergedCertifications = this.mergeSavedAndCurrent(savedCertPreview, current.certifications ?? [], (item) => this.certificationSignature(item));
|
|
30134
|
+
const nextCertifications = mergedCertifications.merged;
|
|
30135
|
+
savedCertifications.forEach((item, index) => {
|
|
30136
|
+
const id = this.getEntityId(item);
|
|
30137
|
+
if (id)
|
|
30138
|
+
this.certificationServerIds[index] = id;
|
|
30139
|
+
});
|
|
30140
|
+
const savedLicensePreview = savedLicenses.map((item) => this.mapSavedLicenseToPreview(item));
|
|
30141
|
+
const mergedLicenses = this.mergeSavedAndCurrent(savedLicensePreview, current.licenses ?? [], (item) => this.licenseSignature(item));
|
|
30142
|
+
const nextLicenses = mergedLicenses.merged;
|
|
30143
|
+
savedLicenses.forEach((item, index) => {
|
|
30144
|
+
const id = this.getEntityId(item);
|
|
30145
|
+
if (id)
|
|
30146
|
+
this.licenseServerIds[index] = id;
|
|
30147
|
+
});
|
|
30148
|
+
const currentSkills = current.skills ?? [];
|
|
30149
|
+
const savedSkillNames = savedSkills.map((item) => (item?.skillSetName ?? '').toString());
|
|
30150
|
+
const mergedSkills = this.mergeSavedAndCurrent(savedSkillNames, currentSkills, (name) => this.normalizedText(name));
|
|
30151
|
+
const nextSkills = mergedSkills.merged;
|
|
30152
|
+
const nextSkillMeta = {};
|
|
30153
|
+
savedSkills.forEach((item, index) => {
|
|
30154
|
+
const id = this.getEntityId(item);
|
|
30155
|
+
if (id)
|
|
30156
|
+
this.skillServerIds[index] = id;
|
|
30157
|
+
nextSkillMeta[index] = {
|
|
30158
|
+
providerName: item?.providerName ?? undefined,
|
|
30159
|
+
starRating: item?.starRating ?? null,
|
|
30160
|
+
year: item?.year ?? null,
|
|
30161
|
+
profileVisibility: !!item?.profileVisibility,
|
|
30162
|
+
notes: item?.notes ?? '',
|
|
30163
|
+
};
|
|
30164
|
+
});
|
|
30165
|
+
const oldSkillMeta = this.store.resumeSkillMeta() ?? {};
|
|
30166
|
+
for (let i = savedSkills.length; i < nextSkills.length; i++) {
|
|
30167
|
+
const sourceCurrentIndex = mergedSkills.appendedCurrentIndexes[i - savedSkills.length];
|
|
30168
|
+
if (sourceCurrentIndex !== undefined && oldSkillMeta[sourceCurrentIndex]) {
|
|
30169
|
+
nextSkillMeta[i] = oldSkillMeta[sourceCurrentIndex];
|
|
30170
|
+
}
|
|
30171
|
+
}
|
|
30172
|
+
this.store.resumeSkillMeta.set(nextSkillMeta);
|
|
30173
|
+
const currentTools = current.tools ?? [];
|
|
30174
|
+
const savedToolNames = savedTools.map((item) => (item?.toolName ?? '').toString());
|
|
30175
|
+
const mergedTools = this.mergeSavedAndCurrent(savedToolNames, currentTools, (name) => this.normalizedText(name));
|
|
30176
|
+
const nextTools = mergedTools.merged;
|
|
30177
|
+
const nextToolMeta = {};
|
|
30178
|
+
savedTools.forEach((item, index) => {
|
|
30179
|
+
const id = this.getEntityId(item);
|
|
30180
|
+
if (id)
|
|
30181
|
+
this.toolServerIds[index] = id;
|
|
30182
|
+
nextToolMeta[index] = {
|
|
30183
|
+
providerName: item?.providerName ?? undefined,
|
|
30184
|
+
starRating: item?.starRating ?? null,
|
|
30185
|
+
year: item?.year ?? null,
|
|
30186
|
+
profileVisibility: !!item?.profileVisibility,
|
|
30187
|
+
notes: item?.notes ?? '',
|
|
30188
|
+
};
|
|
30189
|
+
});
|
|
30190
|
+
const oldToolMeta = this.store.resumeToolMeta() ?? {};
|
|
30191
|
+
for (let i = savedTools.length; i < nextTools.length; i++) {
|
|
30192
|
+
const sourceCurrentIndex = mergedTools.appendedCurrentIndexes[i - savedTools.length];
|
|
30193
|
+
if (sourceCurrentIndex !== undefined && oldToolMeta[sourceCurrentIndex]) {
|
|
30194
|
+
nextToolMeta[i] = oldToolMeta[sourceCurrentIndex];
|
|
30195
|
+
}
|
|
30196
|
+
}
|
|
30197
|
+
this.store.resumeToolMeta.set(nextToolMeta);
|
|
30198
|
+
this.commitProfile({
|
|
30199
|
+
...current,
|
|
30200
|
+
basicDetails: savedBasicDetails ?? current.basicDetails,
|
|
30201
|
+
workExperience: nextWork,
|
|
30202
|
+
education: nextEducation,
|
|
30203
|
+
certifications: nextCertifications,
|
|
30204
|
+
licenses: nextLicenses,
|
|
30205
|
+
skills: nextSkills,
|
|
30206
|
+
tools: nextTools,
|
|
30207
|
+
});
|
|
30208
|
+
}
|
|
30209
|
+
catch (err) {
|
|
30210
|
+
console.error('Unable to load saved section data', err);
|
|
30211
|
+
}
|
|
30212
|
+
}
|
|
29665
30213
|
formatMonthYear(value) {
|
|
29666
30214
|
if (!value)
|
|
29667
30215
|
return '';
|
|
@@ -29708,6 +30256,9 @@ class PreviewComponent {
|
|
|
29708
30256
|
const job = current?.workExperience?.[jobIndex];
|
|
29709
30257
|
if (!current || !job)
|
|
29710
30258
|
return;
|
|
30259
|
+
if (!this.workExperienceServerIds[jobIndex]) {
|
|
30260
|
+
void this.ensureWorkServerId(jobIndex, job);
|
|
30261
|
+
}
|
|
29711
30262
|
this.jobEditor.set({
|
|
29712
30263
|
mode: 'edit', index: jobIndex, data: {
|
|
29713
30264
|
...job,
|
|
@@ -29727,9 +30278,9 @@ class PreviewComponent {
|
|
|
29727
30278
|
const nextJob = this.tempJob();
|
|
29728
30279
|
if (!current || !nextJob)
|
|
29729
30280
|
return;
|
|
29730
|
-
|
|
29731
|
-
|
|
29732
|
-
|
|
30281
|
+
this.isSavingWork = true;
|
|
30282
|
+
try {
|
|
30283
|
+
if (nextJob.fileObject) {
|
|
29733
30284
|
this.fileData = nextJob.fileObject;
|
|
29734
30285
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.work);
|
|
29735
30286
|
nextJob.fileId = uploaded.fileId;
|
|
@@ -29737,33 +30288,21 @@ class PreviewComponent {
|
|
|
29737
30288
|
nextJob.fileName = uploaded.fileName;
|
|
29738
30289
|
nextJob.fileObject = null;
|
|
29739
30290
|
}
|
|
29740
|
-
|
|
29741
|
-
|
|
29742
|
-
|
|
30291
|
+
const next = this.isAddingJob()
|
|
30292
|
+
? { ...current, workExperience: [...(current.workExperience ?? []), nextJob] }
|
|
30293
|
+
: {
|
|
30294
|
+
...current,
|
|
30295
|
+
workExperience: current.workExperience.map((j, idx) => (idx === jobIndex ? nextJob : j))
|
|
30296
|
+
};
|
|
30297
|
+
this.commitProfile(next);
|
|
30298
|
+
const savedIndex = this.isAddingJob() ? next.workExperience.length - 1 : (jobIndex ?? -1);
|
|
30299
|
+
await this.persistWorkExperience(nextJob, savedIndex, !this.isAddingJob());
|
|
30300
|
+
}
|
|
30301
|
+
finally {
|
|
30302
|
+
this.isSavingWork = false;
|
|
29743
30303
|
}
|
|
29744
|
-
const next = this.isAddingJob()
|
|
29745
|
-
? { ...current, workExperience: [...(current.workExperience ?? []), nextJob] }
|
|
29746
|
-
: {
|
|
29747
|
-
...current,
|
|
29748
|
-
workExperience: current.workExperience.map((j, idx) => (idx === jobIndex ? nextJob : j))
|
|
29749
|
-
};
|
|
29750
|
-
this.commitProfile(next);
|
|
29751
30304
|
this.cancelEditJob();
|
|
29752
30305
|
}
|
|
29753
|
-
onWorkExperienceFileSelected(event) {
|
|
29754
|
-
this.revokeObjectUrl(this.tempJob()?.filePreviewUrl);
|
|
29755
|
-
this.selectFile(event);
|
|
29756
|
-
const file = this.fileData;
|
|
29757
|
-
if (!file)
|
|
29758
|
-
return;
|
|
29759
|
-
this.patchTempJob({
|
|
29760
|
-
fileId: null,
|
|
29761
|
-
fileUrl: null,
|
|
29762
|
-
fileName: file.name,
|
|
29763
|
-
fileObject: file,
|
|
29764
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
29765
|
-
});
|
|
29766
|
-
}
|
|
29767
30306
|
deleteJob(index) {
|
|
29768
30307
|
const current = this.store.profileSignal();
|
|
29769
30308
|
if (!current?.workExperience?.length)
|
|
@@ -29776,6 +30315,8 @@ class PreviewComponent {
|
|
|
29776
30315
|
...current,
|
|
29777
30316
|
workExperience: current.workExperience.filter((_, i) => i !== index),
|
|
29778
30317
|
});
|
|
30318
|
+
this.workExperienceServerIds = this.reindexServerIdsAfterDelete(this.workExperienceServerIds, index);
|
|
30319
|
+
this.workExperienceLocallySaved = this.reindexSavedFlagsAfterDelete(this.workExperienceLocallySaved, index);
|
|
29779
30320
|
if (this.editingJobIndex() === index)
|
|
29780
30321
|
this.cancelEditJob();
|
|
29781
30322
|
if (this.expandedIndex() === index)
|
|
@@ -29797,6 +30338,20 @@ class PreviewComponent {
|
|
|
29797
30338
|
return;
|
|
29798
30339
|
this.jobEditor.update((e) => ({ ...e, data: { ...job, ...patch } }));
|
|
29799
30340
|
}
|
|
30341
|
+
onWorkExperienceFileSelected(event) {
|
|
30342
|
+
this.revokeObjectUrl(this.tempJob()?.filePreviewUrl);
|
|
30343
|
+
this.selectFile(event);
|
|
30344
|
+
const file = this.fileData;
|
|
30345
|
+
if (!file)
|
|
30346
|
+
return;
|
|
30347
|
+
this.patchTempJob({
|
|
30348
|
+
fileId: null,
|
|
30349
|
+
fileUrl: null,
|
|
30350
|
+
fileName: file.name,
|
|
30351
|
+
fileObject: file,
|
|
30352
|
+
filePreviewUrl: URL.createObjectURL(file)
|
|
30353
|
+
});
|
|
30354
|
+
}
|
|
29800
30355
|
setTempJobIsCurrent(isCurrent) {
|
|
29801
30356
|
const job = this.tempJob();
|
|
29802
30357
|
if (!job)
|
|
@@ -29825,6 +30380,9 @@ class PreviewComponent {
|
|
|
29825
30380
|
const current = this.store.profileSignal();
|
|
29826
30381
|
if (!current || !current.skills?.length || index < 0 || index >= current.skills.length)
|
|
29827
30382
|
return;
|
|
30383
|
+
if (!this.skillServerIds[index]) {
|
|
30384
|
+
void this.ensureSkillServerId(index);
|
|
30385
|
+
}
|
|
29828
30386
|
const meta = this.store.resumeSkillMeta()?.[index] ?? {};
|
|
29829
30387
|
this.skillEditor.set({
|
|
29830
30388
|
mode: 'edit',
|
|
@@ -29852,7 +30410,7 @@ class PreviewComponent {
|
|
|
29852
30410
|
setTempSkillStars(stars) {
|
|
29853
30411
|
this.patchSkillForm({ stars });
|
|
29854
30412
|
}
|
|
29855
|
-
saveSkillEditor() {
|
|
30413
|
+
async saveSkillEditor() {
|
|
29856
30414
|
const current = this.store.profileSignal();
|
|
29857
30415
|
const idx = this.editingSkillIndex();
|
|
29858
30416
|
if (!current)
|
|
@@ -29867,31 +30425,38 @@ class PreviewComponent {
|
|
|
29867
30425
|
return;
|
|
29868
30426
|
if (form.year === null || form.year === undefined)
|
|
29869
30427
|
return;
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
nextSkills.
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
|
|
29881
|
-
|
|
29882
|
-
|
|
29883
|
-
|
|
29884
|
-
|
|
30428
|
+
this.isSavingSkill = true;
|
|
30429
|
+
try {
|
|
30430
|
+
const nextSkills = [...(current.skills ?? [])];
|
|
30431
|
+
if (this.isAddingSkill()) {
|
|
30432
|
+
nextSkills.push(nextName);
|
|
30433
|
+
}
|
|
30434
|
+
else if (idx !== null) {
|
|
30435
|
+
nextSkills[idx] = nextName;
|
|
30436
|
+
}
|
|
30437
|
+
else {
|
|
30438
|
+
return;
|
|
30439
|
+
}
|
|
30440
|
+
if (this.isAddingSkill()) {
|
|
30441
|
+
this.appendToMainModel('skills', nextName);
|
|
30442
|
+
}
|
|
30443
|
+
else if (idx !== null) {
|
|
30444
|
+
this.updateInMainModel('skills', idx, nextName);
|
|
30445
|
+
}
|
|
30446
|
+
const metaIndex = this.isAddingSkill() ? nextSkills.length - 1 : idx;
|
|
30447
|
+
if (metaIndex !== null) {
|
|
30448
|
+
this.store.setResumeSkillMeta(metaIndex, {
|
|
30449
|
+
providerName: (form.providerName || '').trim() || undefined,
|
|
30450
|
+
starRating: (form.stars || 0) * 2,
|
|
30451
|
+
year: form.year,
|
|
30452
|
+
profileVisibility: form.profileVisibility,
|
|
30453
|
+
notes: form.notes,
|
|
30454
|
+
});
|
|
30455
|
+
await this.persistSkill(metaIndex, !this.isAddingSkill());
|
|
30456
|
+
}
|
|
29885
30457
|
}
|
|
29886
|
-
|
|
29887
|
-
|
|
29888
|
-
this.store.setResumeSkillMeta(metaIndex, {
|
|
29889
|
-
providerName: (form.providerName || '').trim() || undefined,
|
|
29890
|
-
starRating: (form.stars || 0) * 2,
|
|
29891
|
-
year: form.year,
|
|
29892
|
-
profileVisibility: form.profileVisibility,
|
|
29893
|
-
notes: form.notes,
|
|
29894
|
-
});
|
|
30458
|
+
finally {
|
|
30459
|
+
this.isSavingSkill = false;
|
|
29895
30460
|
}
|
|
29896
30461
|
this.closeSkillEditor();
|
|
29897
30462
|
}
|
|
@@ -29920,6 +30485,8 @@ class PreviewComponent {
|
|
|
29920
30485
|
const nextSkills = current.skills.filter((_, i) => i !== index);
|
|
29921
30486
|
this.commitProfile({ ...current, skills: nextSkills });
|
|
29922
30487
|
this.reindexSkillMetaAfterDelete(index);
|
|
30488
|
+
this.skillServerIds = this.reindexServerIdsAfterDelete(this.skillServerIds, index);
|
|
30489
|
+
this.skillLocallySaved = this.reindexSavedFlagsAfterDelete(this.skillLocallySaved, index);
|
|
29923
30490
|
if (this.editingSkillIndex() === index)
|
|
29924
30491
|
this.closeSkillEditor();
|
|
29925
30492
|
}
|
|
@@ -29939,6 +30506,9 @@ class PreviewComponent {
|
|
|
29939
30506
|
const current = this.store.profileSignal();
|
|
29940
30507
|
if (!current || !current.tools?.length || index < 0 || index >= current.tools.length)
|
|
29941
30508
|
return;
|
|
30509
|
+
if (!this.toolServerIds[index]) {
|
|
30510
|
+
void this.ensureToolServerId(index);
|
|
30511
|
+
}
|
|
29942
30512
|
const meta = this.store.resumeToolMeta()?.[index] ?? {};
|
|
29943
30513
|
this.toolEditor.set({
|
|
29944
30514
|
mode: 'edit',
|
|
@@ -29966,7 +30536,7 @@ class PreviewComponent {
|
|
|
29966
30536
|
setTempToolStars(stars) {
|
|
29967
30537
|
this.patchToolForm({ stars });
|
|
29968
30538
|
}
|
|
29969
|
-
saveToolEditor() {
|
|
30539
|
+
async saveToolEditor() {
|
|
29970
30540
|
const current = this.store.profileSignal();
|
|
29971
30541
|
const idx = this.editingToolIndex();
|
|
29972
30542
|
if (!current)
|
|
@@ -29981,31 +30551,38 @@ class PreviewComponent {
|
|
|
29981
30551
|
return;
|
|
29982
30552
|
if (form.year === null || form.year === undefined)
|
|
29983
30553
|
return;
|
|
29984
|
-
|
|
29985
|
-
|
|
29986
|
-
nextTools.
|
|
29987
|
-
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
29996
|
-
|
|
29997
|
-
|
|
29998
|
-
|
|
30554
|
+
this.isSavingTool = true;
|
|
30555
|
+
try {
|
|
30556
|
+
const nextTools = [...(current.tools ?? [])];
|
|
30557
|
+
if (this.isAddingTool()) {
|
|
30558
|
+
nextTools.push(nextName);
|
|
30559
|
+
}
|
|
30560
|
+
else if (idx !== null) {
|
|
30561
|
+
nextTools[idx] = nextName;
|
|
30562
|
+
}
|
|
30563
|
+
else {
|
|
30564
|
+
return;
|
|
30565
|
+
}
|
|
30566
|
+
if (this.isAddingTool()) {
|
|
30567
|
+
this.appendToMainModel('tools', nextName);
|
|
30568
|
+
}
|
|
30569
|
+
else if (idx !== null) {
|
|
30570
|
+
this.updateInMainModel('tools', idx, nextName);
|
|
30571
|
+
}
|
|
30572
|
+
const metaIndex = this.isAddingTool() ? nextTools.length - 1 : idx;
|
|
30573
|
+
if (metaIndex !== null) {
|
|
30574
|
+
this.store.setResumeToolMeta(metaIndex, {
|
|
30575
|
+
providerName: (form.providerName || '').trim() || undefined,
|
|
30576
|
+
starRating: (form.stars || 0) * 2,
|
|
30577
|
+
year: form.year,
|
|
30578
|
+
profileVisibility: form.profileVisibility,
|
|
30579
|
+
notes: form.notes,
|
|
30580
|
+
});
|
|
30581
|
+
await this.persistTool(metaIndex, !this.isAddingTool());
|
|
30582
|
+
}
|
|
29999
30583
|
}
|
|
30000
|
-
|
|
30001
|
-
|
|
30002
|
-
this.store.setResumeToolMeta(metaIndex, {
|
|
30003
|
-
providerName: (form.providerName || '').trim() || undefined,
|
|
30004
|
-
starRating: (form.stars || 0) * 2,
|
|
30005
|
-
year: form.year,
|
|
30006
|
-
profileVisibility: form.profileVisibility,
|
|
30007
|
-
notes: form.notes,
|
|
30008
|
-
});
|
|
30584
|
+
finally {
|
|
30585
|
+
this.isSavingTool = false;
|
|
30009
30586
|
}
|
|
30010
30587
|
this.closeToolEditor();
|
|
30011
30588
|
}
|
|
@@ -30034,6 +30611,8 @@ class PreviewComponent {
|
|
|
30034
30611
|
const nextTools = current.tools.filter((_, i) => i !== index);
|
|
30035
30612
|
this.commitProfile({ ...current, tools: nextTools });
|
|
30036
30613
|
this.reindexToolMetaAfterDelete(index);
|
|
30614
|
+
this.toolServerIds = this.reindexServerIdsAfterDelete(this.toolServerIds, index);
|
|
30615
|
+
this.toolLocallySaved = this.reindexSavedFlagsAfterDelete(this.toolLocallySaved, index);
|
|
30037
30616
|
if (this.editingToolIndex() === index)
|
|
30038
30617
|
this.closeToolEditor();
|
|
30039
30618
|
}
|
|
@@ -30068,25 +30647,14 @@ class PreviewComponent {
|
|
|
30068
30647
|
}
|
|
30069
30648
|
});
|
|
30070
30649
|
}
|
|
30071
|
-
onCertificationFileSelected(event) {
|
|
30072
|
-
this.revokeObjectUrl(this.tempCertification()?.filePreviewUrl);
|
|
30073
|
-
this.selectFile(event);
|
|
30074
|
-
const file = this.fileData;
|
|
30075
|
-
if (!file)
|
|
30076
|
-
return;
|
|
30077
|
-
this.patchTempCertification({
|
|
30078
|
-
fileId: null,
|
|
30079
|
-
fileUrl: null,
|
|
30080
|
-
fileName: file.name,
|
|
30081
|
-
fileObject: file,
|
|
30082
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30083
|
-
});
|
|
30084
|
-
}
|
|
30085
30650
|
startEditCertification(index) {
|
|
30086
30651
|
const current = this.store.profileSignal();
|
|
30087
30652
|
const cert = current?.certifications?.[index];
|
|
30088
30653
|
if (!current || !cert)
|
|
30089
30654
|
return;
|
|
30655
|
+
if (!this.certificationServerIds[index]) {
|
|
30656
|
+
void this.ensureCertificationServerId(index, cert);
|
|
30657
|
+
}
|
|
30090
30658
|
this.certificationEditor.set({
|
|
30091
30659
|
mode: 'edit', index, data: {
|
|
30092
30660
|
...cert,
|
|
@@ -30104,15 +30672,29 @@ class PreviewComponent {
|
|
|
30104
30672
|
return;
|
|
30105
30673
|
this.certificationEditor.update((e) => ({ ...e, data: { ...cert, ...patch } }));
|
|
30106
30674
|
}
|
|
30675
|
+
onCertificationFileSelected(event) {
|
|
30676
|
+
this.revokeObjectUrl(this.tempCertification()?.filePreviewUrl);
|
|
30677
|
+
this.selectFile(event);
|
|
30678
|
+
const file = this.fileData;
|
|
30679
|
+
if (!file)
|
|
30680
|
+
return;
|
|
30681
|
+
this.patchTempCertification({
|
|
30682
|
+
fileId: null,
|
|
30683
|
+
fileUrl: null,
|
|
30684
|
+
fileName: file.name,
|
|
30685
|
+
fileObject: file,
|
|
30686
|
+
filePreviewUrl: URL.createObjectURL(file)
|
|
30687
|
+
});
|
|
30688
|
+
}
|
|
30107
30689
|
async saveCertificationEditor() {
|
|
30108
30690
|
const current = this.store.profileSignal();
|
|
30109
30691
|
const idx = this.editingCertificationIndex();
|
|
30110
30692
|
const nextCert = this.tempCertification();
|
|
30111
30693
|
if (!current || !nextCert)
|
|
30112
30694
|
return;
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30695
|
+
this.isSavingCertification = true;
|
|
30696
|
+
try {
|
|
30697
|
+
if (nextCert.fileObject) {
|
|
30116
30698
|
this.fileData = nextCert.fileObject;
|
|
30117
30699
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.certification);
|
|
30118
30700
|
nextCert.fileId = uploaded.fileId;
|
|
@@ -30120,26 +30702,30 @@ class PreviewComponent {
|
|
|
30120
30702
|
nextCert.fileName = uploaded.fileName;
|
|
30121
30703
|
nextCert.fileObject = null;
|
|
30122
30704
|
}
|
|
30123
|
-
|
|
30124
|
-
|
|
30125
|
-
|
|
30705
|
+
const name = this.normalizeNullableString(nextCert.name);
|
|
30706
|
+
if (!name)
|
|
30707
|
+
return;
|
|
30708
|
+
const normalized = {
|
|
30709
|
+
...nextCert,
|
|
30710
|
+
name,
|
|
30711
|
+
issuingOrganization: this.normalizeNullableString(nextCert.issuingOrganization),
|
|
30712
|
+
state: this.normalizeNullableString(nextCert.state),
|
|
30713
|
+
credentialId: this.normalizeNullableString(nextCert.credentialId),
|
|
30714
|
+
issueDate: this.normalizeMonthInput(nextCert.issueDate),
|
|
30715
|
+
expiryDate: this.normalizeMonthInput(nextCert.expiryDate),
|
|
30716
|
+
};
|
|
30717
|
+
if (this.isAddingCertification())
|
|
30718
|
+
this.appendToMainModel('certifications', normalized);
|
|
30719
|
+
else if (idx !== null)
|
|
30720
|
+
this.updateInMainModel('certifications', idx, normalized);
|
|
30721
|
+
const savedIndex = this.isAddingCertification()
|
|
30722
|
+
? (this.store.profileSignal()?.certifications?.length ?? 1) - 1
|
|
30723
|
+
: (idx ?? -1);
|
|
30724
|
+
await this.persistCertification(normalized, savedIndex, !this.isAddingCertification());
|
|
30725
|
+
}
|
|
30726
|
+
finally {
|
|
30727
|
+
this.isSavingCertification = false;
|
|
30126
30728
|
}
|
|
30127
|
-
const name = this.normalizeNullableString(nextCert.name);
|
|
30128
|
-
if (!name)
|
|
30129
|
-
return;
|
|
30130
|
-
const normalized = {
|
|
30131
|
-
...nextCert,
|
|
30132
|
-
name,
|
|
30133
|
-
issuingOrganization: this.normalizeNullableString(nextCert.issuingOrganization),
|
|
30134
|
-
state: this.normalizeNullableString(nextCert.state),
|
|
30135
|
-
credentialId: this.normalizeNullableString(nextCert.credentialId),
|
|
30136
|
-
issueDate: this.normalizeMonthInput(nextCert.issueDate),
|
|
30137
|
-
expiryDate: this.normalizeMonthInput(nextCert.expiryDate),
|
|
30138
|
-
};
|
|
30139
|
-
if (this.isAddingCertification())
|
|
30140
|
-
this.appendToMainModel('certifications', normalized);
|
|
30141
|
-
else if (idx !== null)
|
|
30142
|
-
this.updateInMainModel('certifications', idx, normalized);
|
|
30143
30729
|
this.cancelEditCertification();
|
|
30144
30730
|
}
|
|
30145
30731
|
deleteCertification(index) {
|
|
@@ -30154,6 +30740,8 @@ class PreviewComponent {
|
|
|
30154
30740
|
...current,
|
|
30155
30741
|
certifications: current.certifications.filter((_, i) => i !== index),
|
|
30156
30742
|
});
|
|
30743
|
+
this.certificationServerIds = this.reindexServerIdsAfterDelete(this.certificationServerIds, index);
|
|
30744
|
+
this.certificationLocallySaved = this.reindexSavedFlagsAfterDelete(this.certificationLocallySaved, index);
|
|
30157
30745
|
if (this.editingCertificationIndex() === index)
|
|
30158
30746
|
this.cancelEditCertification();
|
|
30159
30747
|
}
|
|
@@ -30178,25 +30766,14 @@ class PreviewComponent {
|
|
|
30178
30766
|
}
|
|
30179
30767
|
});
|
|
30180
30768
|
}
|
|
30181
|
-
onLicenseFileSelected(event) {
|
|
30182
|
-
this.revokeObjectUrl(this.tempLicense()?.filePreviewUrl);
|
|
30183
|
-
this.selectFile(event);
|
|
30184
|
-
const file = this.fileData;
|
|
30185
|
-
if (!file)
|
|
30186
|
-
return;
|
|
30187
|
-
this.patchTempLicense({
|
|
30188
|
-
fileId: null,
|
|
30189
|
-
fileUrl: null,
|
|
30190
|
-
fileName: file.name,
|
|
30191
|
-
fileObject: file,
|
|
30192
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30193
|
-
});
|
|
30194
|
-
}
|
|
30195
30769
|
startEditLicense(index) {
|
|
30196
30770
|
const current = this.store.profileSignal();
|
|
30197
30771
|
const lic = current?.licenses?.[index];
|
|
30198
30772
|
if (!current || !lic)
|
|
30199
30773
|
return;
|
|
30774
|
+
if (!this.licenseServerIds[index]) {
|
|
30775
|
+
void this.ensureLicenseServerId(index, lic);
|
|
30776
|
+
}
|
|
30200
30777
|
this.licenseEditor.set({
|
|
30201
30778
|
mode: 'edit', index, data: {
|
|
30202
30779
|
...lic,
|
|
@@ -30214,15 +30791,29 @@ class PreviewComponent {
|
|
|
30214
30791
|
return;
|
|
30215
30792
|
this.licenseEditor.update((e) => ({ ...e, data: { ...lic, ...patch } }));
|
|
30216
30793
|
}
|
|
30794
|
+
onLicenseFileSelected(event) {
|
|
30795
|
+
this.revokeObjectUrl(this.tempLicense()?.filePreviewUrl);
|
|
30796
|
+
this.selectFile(event);
|
|
30797
|
+
const file = this.fileData;
|
|
30798
|
+
if (!file)
|
|
30799
|
+
return;
|
|
30800
|
+
this.patchTempLicense({
|
|
30801
|
+
fileId: null,
|
|
30802
|
+
fileUrl: null,
|
|
30803
|
+
fileName: file.name,
|
|
30804
|
+
fileObject: file,
|
|
30805
|
+
filePreviewUrl: URL.createObjectURL(file)
|
|
30806
|
+
});
|
|
30807
|
+
}
|
|
30217
30808
|
async saveLicenseEditor() {
|
|
30218
30809
|
const current = this.store.profileSignal();
|
|
30219
30810
|
const idx = this.editingLicenseIndex();
|
|
30220
30811
|
const nextLic = this.tempLicense();
|
|
30221
30812
|
if (!current || !nextLic)
|
|
30222
30813
|
return;
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30814
|
+
this.isSavingLicense = true;
|
|
30815
|
+
try {
|
|
30816
|
+
if (nextLic.fileObject) {
|
|
30226
30817
|
this.fileData = nextLic.fileObject;
|
|
30227
30818
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.license);
|
|
30228
30819
|
nextLic.fileId = uploaded.fileId;
|
|
@@ -30230,26 +30821,30 @@ class PreviewComponent {
|
|
|
30230
30821
|
nextLic.fileName = uploaded.fileName;
|
|
30231
30822
|
nextLic.fileObject = null;
|
|
30232
30823
|
}
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30824
|
+
const name = this.normalizeNullableString(nextLic.name);
|
|
30825
|
+
if (!name)
|
|
30826
|
+
return;
|
|
30827
|
+
const normalized = {
|
|
30828
|
+
...nextLic,
|
|
30829
|
+
name,
|
|
30830
|
+
issuingAuthority: this.normalizeNullableString(nextLic.issuingAuthority),
|
|
30831
|
+
licenseNumber: this.normalizeNullableString(nextLic.licenseNumber),
|
|
30832
|
+
state: this.normalizeNullableString(nextLic.state),
|
|
30833
|
+
issueDate: this.normalizeMonthInput(nextLic.issueDate),
|
|
30834
|
+
expiryDate: this.normalizeMonthInput(nextLic.expiryDate),
|
|
30835
|
+
};
|
|
30836
|
+
if (this.isAddingLicense())
|
|
30837
|
+
this.appendToMainModel('licenses', normalized);
|
|
30838
|
+
else if (idx !== null)
|
|
30839
|
+
this.updateInMainModel('licenses', idx, normalized);
|
|
30840
|
+
const savedIndex = this.isAddingLicense()
|
|
30841
|
+
? (this.store.profileSignal()?.licenses?.length ?? 1) - 1
|
|
30842
|
+
: (idx ?? -1);
|
|
30843
|
+
await this.persistLicense(normalized, savedIndex, !this.isAddingLicense());
|
|
30844
|
+
}
|
|
30845
|
+
finally {
|
|
30846
|
+
this.isSavingLicense = false;
|
|
30236
30847
|
}
|
|
30237
|
-
const name = this.normalizeNullableString(nextLic.name);
|
|
30238
|
-
if (!name)
|
|
30239
|
-
return;
|
|
30240
|
-
const normalized = {
|
|
30241
|
-
...nextLic,
|
|
30242
|
-
name,
|
|
30243
|
-
issuingAuthority: this.normalizeNullableString(nextLic.issuingAuthority),
|
|
30244
|
-
licenseNumber: this.normalizeNullableString(nextLic.licenseNumber),
|
|
30245
|
-
state: this.normalizeNullableString(nextLic.state),
|
|
30246
|
-
issueDate: this.normalizeMonthInput(nextLic.issueDate),
|
|
30247
|
-
expiryDate: this.normalizeMonthInput(nextLic.expiryDate),
|
|
30248
|
-
};
|
|
30249
|
-
if (this.isAddingLicense())
|
|
30250
|
-
this.appendToMainModel('licenses', normalized);
|
|
30251
|
-
else if (idx !== null)
|
|
30252
|
-
this.updateInMainModel('licenses', idx, normalized);
|
|
30253
30848
|
this.cancelEditLicense();
|
|
30254
30849
|
}
|
|
30255
30850
|
deleteLicense(index) {
|
|
@@ -30264,6 +30859,8 @@ class PreviewComponent {
|
|
|
30264
30859
|
...current,
|
|
30265
30860
|
licenses: current.licenses.filter((_, i) => i !== index),
|
|
30266
30861
|
});
|
|
30862
|
+
this.licenseServerIds = this.reindexServerIdsAfterDelete(this.licenseServerIds, index);
|
|
30863
|
+
this.licenseLocallySaved = this.reindexSavedFlagsAfterDelete(this.licenseLocallySaved, index);
|
|
30267
30864
|
if (this.editingLicenseIndex() === index)
|
|
30268
30865
|
this.cancelEditLicense();
|
|
30269
30866
|
}
|
|
@@ -30291,25 +30888,14 @@ class PreviewComponent {
|
|
|
30291
30888
|
}
|
|
30292
30889
|
});
|
|
30293
30890
|
}
|
|
30294
|
-
onEducationFileSelected(event) {
|
|
30295
|
-
this.revokeObjectUrl(this.tempEducation()?.filePreviewUrl);
|
|
30296
|
-
this.selectFile(event);
|
|
30297
|
-
const file = this.fileData;
|
|
30298
|
-
if (!file)
|
|
30299
|
-
return;
|
|
30300
|
-
this.patchTempEducation({
|
|
30301
|
-
fileId: null,
|
|
30302
|
-
fileUrl: null,
|
|
30303
|
-
fileName: file.name,
|
|
30304
|
-
fileObject: file,
|
|
30305
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30306
|
-
});
|
|
30307
|
-
}
|
|
30308
30891
|
startEditEducation(index) {
|
|
30309
30892
|
const current = this.store.profileSignal();
|
|
30310
30893
|
const edu = current?.education?.[index];
|
|
30311
30894
|
if (!current || !edu)
|
|
30312
30895
|
return;
|
|
30896
|
+
if (!this.educationServerIds[index]) {
|
|
30897
|
+
void this.ensureEducationServerId(index, edu);
|
|
30898
|
+
}
|
|
30313
30899
|
this.educationEditor.set({
|
|
30314
30900
|
mode: 'edit', index, data: {
|
|
30315
30901
|
...edu,
|
|
@@ -30328,15 +30914,29 @@ class PreviewComponent {
|
|
|
30328
30914
|
return;
|
|
30329
30915
|
this.educationEditor.update((e) => ({ ...e, data: { ...edu, ...patch } }));
|
|
30330
30916
|
}
|
|
30917
|
+
onEducationFileSelected(event) {
|
|
30918
|
+
this.revokeObjectUrl(this.tempEducation()?.filePreviewUrl);
|
|
30919
|
+
this.selectFile(event);
|
|
30920
|
+
const file = this.fileData;
|
|
30921
|
+
if (!file)
|
|
30922
|
+
return;
|
|
30923
|
+
this.patchTempEducation({
|
|
30924
|
+
fileId: null,
|
|
30925
|
+
fileUrl: null,
|
|
30926
|
+
fileName: file.name,
|
|
30927
|
+
fileObject: file,
|
|
30928
|
+
filePreviewUrl: URL.createObjectURL(file)
|
|
30929
|
+
});
|
|
30930
|
+
}
|
|
30331
30931
|
async saveEducation() {
|
|
30332
30932
|
const current = this.store.profileSignal();
|
|
30333
30933
|
const idx = this.editingEducationIndex();
|
|
30334
30934
|
const nextEdu = this.tempEducation();
|
|
30335
30935
|
if (!current || !nextEdu)
|
|
30336
30936
|
return;
|
|
30337
|
-
|
|
30338
|
-
|
|
30339
|
-
|
|
30937
|
+
this.isSavingEducation = true;
|
|
30938
|
+
try {
|
|
30939
|
+
if (nextEdu.fileObject) {
|
|
30340
30940
|
this.fileData = nextEdu.fileObject;
|
|
30341
30941
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.education);
|
|
30342
30942
|
nextEdu.fileId = uploaded.fileId;
|
|
@@ -30344,16 +30944,20 @@ class PreviewComponent {
|
|
|
30344
30944
|
nextEdu.fileName = uploaded.fileName;
|
|
30345
30945
|
nextEdu.fileObject = null;
|
|
30346
30946
|
}
|
|
30347
|
-
|
|
30348
|
-
this.
|
|
30349
|
-
|
|
30947
|
+
if (this.isAddingEducation())
|
|
30948
|
+
this.appendToMainModel('education', nextEdu);
|
|
30949
|
+
else if (idx !== null)
|
|
30950
|
+
this.updateInMainModel('education', idx, nextEdu);
|
|
30951
|
+
else
|
|
30952
|
+
return;
|
|
30953
|
+
const savedIndex = this.isAddingEducation()
|
|
30954
|
+
? (this.store.profileSignal()?.education?.length ?? 1) - 1
|
|
30955
|
+
: (idx ?? -1);
|
|
30956
|
+
await this.persistEducation(nextEdu, savedIndex, !this.isAddingEducation());
|
|
30957
|
+
}
|
|
30958
|
+
finally {
|
|
30959
|
+
this.isSavingEducation = false;
|
|
30350
30960
|
}
|
|
30351
|
-
if (this.isAddingEducation())
|
|
30352
|
-
this.appendToMainModel('education', nextEdu);
|
|
30353
|
-
else if (idx !== null)
|
|
30354
|
-
this.updateInMainModel('education', idx, nextEdu);
|
|
30355
|
-
else
|
|
30356
|
-
return;
|
|
30357
30961
|
this.cancelEditEducation();
|
|
30358
30962
|
}
|
|
30359
30963
|
deleteEducation(index) {
|
|
@@ -30368,6 +30972,8 @@ class PreviewComponent {
|
|
|
30368
30972
|
...current,
|
|
30369
30973
|
education: current.education.filter((_, i) => i !== index),
|
|
30370
30974
|
});
|
|
30975
|
+
this.educationServerIds = this.reindexServerIdsAfterDelete(this.educationServerIds, index);
|
|
30976
|
+
this.educationLocallySaved = this.reindexSavedFlagsAfterDelete(this.educationLocallySaved, index);
|
|
30371
30977
|
if (this.editingEducationIndex() === index)
|
|
30372
30978
|
this.cancelEditEducation();
|
|
30373
30979
|
}
|
|
@@ -30377,74 +30983,39 @@ class PreviewComponent {
|
|
|
30377
30983
|
stayOnPreview() {
|
|
30378
30984
|
this.showBackConfirmPopup = false;
|
|
30379
30985
|
}
|
|
30380
|
-
|
|
30381
|
-
this.
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30396
|
-
|
|
30397
|
-
}, 2000); // 2 seconds
|
|
30398
|
-
},
|
|
30399
|
-
error: (err) => {
|
|
30400
|
-
console.error('Error while saving initial setup', err);
|
|
30401
|
-
this.showLoader = false;
|
|
30402
|
-
}
|
|
30403
|
-
});
|
|
30404
|
-
}
|
|
30405
|
-
mapBasicDetailsDashboard(data) {
|
|
30406
|
-
const basic = data.basicDetails;
|
|
30407
|
-
this.email = data.email;
|
|
30408
|
-
const jobTitle = (basic?.jobTitle ?? '').toString().trim() || 'Drone Pilot';
|
|
30409
|
-
console.log(basic);
|
|
30410
|
-
this.payloadUserName = basic?.firstName + ' ' + basic?.lastName;
|
|
30411
|
-
console.log(basic);
|
|
30412
|
-
return {
|
|
30413
|
-
providerName: this.providerName,
|
|
30414
|
-
providerId: this.providerId,
|
|
30415
|
-
UserRoleId: this.roleData.roleInfo.id,
|
|
30416
|
-
userDetail: {
|
|
30417
|
-
userBio: basic.summary || "",
|
|
30418
|
-
firstName: basic.firstName,
|
|
30419
|
-
lastName: basic.lastName,
|
|
30420
|
-
email: basic.email,
|
|
30421
|
-
address1: basic.address,
|
|
30422
|
-
address2: "",
|
|
30423
|
-
city: basic.city,
|
|
30424
|
-
state: basic.state,
|
|
30425
|
-
zipcode: basic.zipCode,
|
|
30426
|
-
county: "", // you don’t have this → keep empty or derive
|
|
30427
|
-
country: basic.country,
|
|
30428
|
-
latitude: null, // optional (if using Google API later)
|
|
30429
|
-
longitude: null,
|
|
30430
|
-
yearsActive: 0,
|
|
30431
|
-
isInitialSetupCompleted: true,
|
|
30432
|
-
id: this.payloadUserId,
|
|
30433
|
-
phoneNumber: basic.phone,
|
|
30434
|
-
yearsOfExperince: basic.yearsOfExperience,
|
|
30435
|
-
userJobTitle: [jobTitle],
|
|
30436
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
30437
|
-
datePattern: new Date().toLocaleDateString(),
|
|
30438
|
-
dateTimePattern: new Date().toLocaleString(),
|
|
30439
|
-
timePattern: new Date().toLocaleTimeString()
|
|
30440
|
-
}
|
|
30441
|
-
};
|
|
30986
|
+
async goToDashboard() {
|
|
30987
|
+
if (!this.canConfirmAndContinue())
|
|
30988
|
+
return;
|
|
30989
|
+
const payload = this.mapBasicDetailsToUserDetail(this.resumeData);
|
|
30990
|
+
payload.userDetail.isInitialSetupCompleted = true;
|
|
30991
|
+
this.isSavingBasic = true;
|
|
30992
|
+
try {
|
|
30993
|
+
await firstValueFrom(this.userDetailService.initialSetUpCreateUserDetail(payload));
|
|
30994
|
+
this.showDashboardConfirmPopup = false;
|
|
30995
|
+
window.location.href = this.libConfig.dashboardUrl;
|
|
30996
|
+
}
|
|
30997
|
+
catch (err) {
|
|
30998
|
+
console.error('Error while saving initial setup', err);
|
|
30999
|
+
}
|
|
31000
|
+
finally {
|
|
31001
|
+
this.isSavingBasic = false;
|
|
31002
|
+
}
|
|
30442
31003
|
}
|
|
30443
31004
|
proceedBack() {
|
|
30444
31005
|
this.showBackConfirmPopup = false;
|
|
30445
31006
|
this.backToParent.emit();
|
|
30446
31007
|
this.store.currentStep.set(1);
|
|
30447
31008
|
}
|
|
31009
|
+
onGoToDashboardClick() {
|
|
31010
|
+
if (!this.canConfirmAndContinue())
|
|
31011
|
+
return;
|
|
31012
|
+
this.showDashboardConfirmPopup = true;
|
|
31013
|
+
}
|
|
31014
|
+
cancelDashboardRedirect() {
|
|
31015
|
+
if (this.isSavingBasic)
|
|
31016
|
+
return;
|
|
31017
|
+
this.showDashboardConfirmPopup = false;
|
|
31018
|
+
}
|
|
30448
31019
|
tempProfile;
|
|
30449
31020
|
isEditMode = signal(false, ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
30450
31021
|
toggleEdit() {
|
|
@@ -30457,141 +31028,165 @@ class PreviewComponent {
|
|
|
30457
31028
|
cancel() {
|
|
30458
31029
|
this.isEditMode.set(false);
|
|
30459
31030
|
}
|
|
30460
|
-
save() {
|
|
31031
|
+
async save() {
|
|
30461
31032
|
this.details.update(() => ({ ...this.tempProfile }));
|
|
30462
|
-
this.
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
30471
|
-
{
|
|
30472
|
-
name: 'Basic Details',
|
|
30473
|
-
type: 'single',
|
|
30474
|
-
call: () => {
|
|
30475
|
-
const payload = this.mapBasicDetailsToUserDetail(this.resumeData);
|
|
30476
|
-
return this.userDetailService.initialSetUpCreateUserDetail(payload);
|
|
30477
|
-
}
|
|
30478
|
-
},
|
|
30479
|
-
{
|
|
30480
|
-
name: 'Work Experience',
|
|
30481
|
-
type: 'multiple',
|
|
30482
|
-
data: () => this.mapWorkExperience(this.resumeData),
|
|
30483
|
-
call: (item) => this.userExperienceService.createUserExperience(item)
|
|
30484
|
-
},
|
|
30485
|
-
{
|
|
30486
|
-
name: 'Education',
|
|
30487
|
-
type: 'multiple',
|
|
30488
|
-
data: () => this.mapEducation(this.resumeData),
|
|
30489
|
-
call: (item) => this.userEducation.createUserEducation(item)
|
|
30490
|
-
},
|
|
30491
|
-
{
|
|
30492
|
-
name: 'Certification',
|
|
30493
|
-
type: 'multiple',
|
|
30494
|
-
data: () => this.mapCertifications(this.resumeData),
|
|
30495
|
-
call: (item) => this.userDocumentService.createUserDocument(item)
|
|
30496
|
-
},
|
|
30497
|
-
{
|
|
30498
|
-
name: 'License',
|
|
30499
|
-
type: 'multiple',
|
|
30500
|
-
data: () => this.mapLicenses(this.resumeData),
|
|
30501
|
-
call: (item) => this.userDocumentService.createUserDocument(item)
|
|
30502
|
-
},
|
|
30503
|
-
{
|
|
30504
|
-
name: 'Skills',
|
|
30505
|
-
type: 'batch',
|
|
30506
|
-
data: () => this.mapSkills(this.resumeData),
|
|
30507
|
-
call: (items) => this.userSkillSetService.createUserSkillSet(items)
|
|
30508
|
-
},
|
|
30509
|
-
{
|
|
30510
|
-
name: 'Tools',
|
|
30511
|
-
type: 'batch',
|
|
30512
|
-
data: () => this.mapTools(this.resumeData),
|
|
30513
|
-
call: (items) => this.userToolService.createUserTool(items)
|
|
30514
|
-
}
|
|
30515
|
-
];
|
|
30516
|
-
// Skip empty sections and continue with available ones.
|
|
30517
|
-
const steps = rawSteps.filter((step) => {
|
|
30518
|
-
if (step.type === 'multiple' || step.type === 'batch') {
|
|
30519
|
-
const data = step.data(); // ✅ call function
|
|
30520
|
-
return Array.isArray(data) && data.length > 0;
|
|
30521
|
-
}
|
|
30522
|
-
return true;
|
|
30523
|
-
});
|
|
30524
|
-
this.statusList = [];
|
|
30525
|
-
from(steps)
|
|
30526
|
-
.pipe(concatMap((step) => {
|
|
30527
|
-
const stepStatus = {
|
|
30528
|
-
name: step.name,
|
|
30529
|
-
status: 'pending',
|
|
30530
|
-
successCount: 0,
|
|
30531
|
-
failCount: 0
|
|
30532
|
-
};
|
|
30533
|
-
this.statusList.push(stepStatus);
|
|
30534
|
-
// ✅ SINGLE API
|
|
30535
|
-
if (step.type === 'single') {
|
|
30536
|
-
return step.call(null).pipe(tap$1((res) => {
|
|
30537
|
-
if (res?.failed) {
|
|
30538
|
-
stepStatus.status = 'error';
|
|
30539
|
-
stepStatus.error = res.message;
|
|
30540
|
-
}
|
|
30541
|
-
else {
|
|
30542
|
-
stepStatus.status = 'success';
|
|
30543
|
-
}
|
|
30544
|
-
}), catchError((err) => {
|
|
30545
|
-
stepStatus.status = 'error';
|
|
30546
|
-
stepStatus.error = err.message;
|
|
30547
|
-
return of(null); // 🔥 continue flow
|
|
30548
|
-
}));
|
|
30549
|
-
}
|
|
30550
|
-
if (step.type === 'batch') {
|
|
30551
|
-
const stepData = step.data();
|
|
30552
|
-
return step.call(stepData).pipe(tap$1((res) => {
|
|
30553
|
-
if (res?.failed) {
|
|
30554
|
-
stepStatus.status = 'error';
|
|
30555
|
-
stepStatus.error = res.message;
|
|
30556
|
-
}
|
|
30557
|
-
else {
|
|
30558
|
-
stepStatus.status = 'success';
|
|
30559
|
-
stepStatus.successCount = step.data?.length ?? 0;
|
|
30560
|
-
}
|
|
30561
|
-
}), catchError((err) => {
|
|
30562
|
-
stepStatus.status = 'error';
|
|
30563
|
-
stepStatus.error = err.message;
|
|
30564
|
-
return of(null);
|
|
30565
|
-
}));
|
|
30566
|
-
}
|
|
30567
|
-
// ✅ MULTIPLE LOOP (like 2 work experiences)
|
|
30568
|
-
const stepData = step.data(); // ✅ executed AFTER username set
|
|
30569
|
-
return from(stepData).pipe(concatMap((item) => step.call(item).pipe(tap$1((res) => {
|
|
30570
|
-
if (res?.failed) {
|
|
30571
|
-
stepStatus.failCount++;
|
|
30572
|
-
}
|
|
30573
|
-
else {
|
|
30574
|
-
stepStatus.successCount++;
|
|
30575
|
-
}
|
|
30576
|
-
}), catchError(() => {
|
|
30577
|
-
stepStatus.failCount++;
|
|
30578
|
-
return of(null); // 🔥 continue next item
|
|
30579
|
-
}))), tap$1(() => {
|
|
30580
|
-
// final step status
|
|
30581
|
-
if (stepStatus.failCount > 0) {
|
|
30582
|
-
stepStatus.status = 'partial'; // 🔥 mixed result
|
|
30583
|
-
}
|
|
30584
|
-
else {
|
|
30585
|
-
stepStatus.status = 'success';
|
|
31033
|
+
this.isSavingBasic = true;
|
|
31034
|
+
try {
|
|
31035
|
+
const current = this.store.profileSignal();
|
|
31036
|
+
if (current) {
|
|
31037
|
+
this.commitProfile({ ...current, basicDetails: { ...this.tempProfile } });
|
|
31038
|
+
const payload = this.mapBasicDetailsToUserDetail(this.resumeData);
|
|
31039
|
+
if (payload) {
|
|
31040
|
+
await firstValueFrom(this.userDetailService.initialSetUpCreateUserDetail(payload));
|
|
31041
|
+
this.hasUserDetailData = true;
|
|
30586
31042
|
}
|
|
30587
|
-
}), catchError(() => of(null)) // 🔥 safety
|
|
30588
|
-
);
|
|
30589
|
-
}))
|
|
30590
|
-
.subscribe({
|
|
30591
|
-
complete: () => {
|
|
30592
|
-
console.log('All steps completed (with possible errors)');
|
|
30593
31043
|
}
|
|
30594
|
-
}
|
|
31044
|
+
}
|
|
31045
|
+
catch (err) {
|
|
31046
|
+
console.error('Unable to save basic details', err);
|
|
31047
|
+
}
|
|
31048
|
+
finally {
|
|
31049
|
+
this.isSavingBasic = false;
|
|
31050
|
+
this.basicDetailsSaved = true;
|
|
31051
|
+
this.isEditMode.set(false);
|
|
31052
|
+
}
|
|
31053
|
+
}
|
|
31054
|
+
async persistWorkExperience(item, index, isEdit) {
|
|
31055
|
+
this.buildUserName();
|
|
31056
|
+
const payload = this.mapWorkExperience({ workExperience: [item] })[0];
|
|
31057
|
+
if (isEdit && !this.workExperienceServerIds[index]) {
|
|
31058
|
+
await this.ensureWorkServerId(index, item);
|
|
31059
|
+
}
|
|
31060
|
+
const hasServerId = !!this.workExperienceServerIds[index];
|
|
31061
|
+
if (hasServerId)
|
|
31062
|
+
payload.id = this.workExperienceServerIds[index];
|
|
31063
|
+
try {
|
|
31064
|
+
const res = (isEdit && hasServerId)
|
|
31065
|
+
? await firstValueFrom(this.userExperienceService
|
|
31066
|
+
.updateUserExperience(payload))
|
|
31067
|
+
: await firstValueFrom(this.userExperienceService.createUserExperience(payload));
|
|
31068
|
+
const createdId = this.getCreatedId(res);
|
|
31069
|
+
this.markItemSaved(this.workExperienceServerIds, this.workExperienceLocallySaved, index, createdId);
|
|
31070
|
+
}
|
|
31071
|
+
catch (err) {
|
|
31072
|
+
console.error('Unable to persist work experience', err);
|
|
31073
|
+
}
|
|
31074
|
+
}
|
|
31075
|
+
async persistEducation(item, index, isEdit) {
|
|
31076
|
+
this.buildUserName();
|
|
31077
|
+
const payload = this.mapEducation({ education: [item] })[0];
|
|
31078
|
+
if (isEdit && !this.educationServerIds[index]) {
|
|
31079
|
+
await this.ensureEducationServerId(index, item);
|
|
31080
|
+
}
|
|
31081
|
+
const hasServerId = !!this.educationServerIds[index];
|
|
31082
|
+
if (hasServerId)
|
|
31083
|
+
payload.id = this.educationServerIds[index];
|
|
31084
|
+
try {
|
|
31085
|
+
const res = (isEdit && hasServerId)
|
|
31086
|
+
? await firstValueFrom(this.userEducation.updateUserEducation(payload))
|
|
31087
|
+
: await firstValueFrom(this.userEducation.createUserEducation(payload));
|
|
31088
|
+
const createdId = this.getCreatedId(res);
|
|
31089
|
+
this.markItemSaved(this.educationServerIds, this.educationLocallySaved, index, createdId);
|
|
31090
|
+
}
|
|
31091
|
+
catch (err) {
|
|
31092
|
+
console.error('Unable to persist education', err);
|
|
31093
|
+
}
|
|
31094
|
+
}
|
|
31095
|
+
async persistCertification(item, index, isEdit) {
|
|
31096
|
+
this.buildUserName();
|
|
31097
|
+
const payload = this.mapCertifications({
|
|
31098
|
+
certifications: [item],
|
|
31099
|
+
basicDetails: this.store.profileSignal()?.basicDetails
|
|
31100
|
+
})[0];
|
|
31101
|
+
if (isEdit && !this.certificationServerIds[index]) {
|
|
31102
|
+
await this.ensureCertificationServerId(index, item);
|
|
31103
|
+
}
|
|
31104
|
+
const hasServerId = !!this.certificationServerIds[index];
|
|
31105
|
+
if (hasServerId)
|
|
31106
|
+
payload.id = this.certificationServerIds[index];
|
|
31107
|
+
try {
|
|
31108
|
+
const res = (isEdit && hasServerId)
|
|
31109
|
+
? await firstValueFrom(this.userDocumentService
|
|
31110
|
+
.updateUserDocument(payload))
|
|
31111
|
+
: await firstValueFrom(this.userDocumentService.createUserDocument(payload));
|
|
31112
|
+
const createdId = this.getCreatedId(res);
|
|
31113
|
+
this.markItemSaved(this.certificationServerIds, this.certificationLocallySaved, index, createdId);
|
|
31114
|
+
}
|
|
31115
|
+
catch (err) {
|
|
31116
|
+
console.error('Unable to persist certification', err);
|
|
31117
|
+
}
|
|
31118
|
+
}
|
|
31119
|
+
async persistLicense(item, index, isEdit) {
|
|
31120
|
+
this.buildUserName();
|
|
31121
|
+
const payload = this.mapLicenses({
|
|
31122
|
+
licenses: [item],
|
|
31123
|
+
basicDetails: this.store.profileSignal()?.basicDetails
|
|
31124
|
+
})[0];
|
|
31125
|
+
if (isEdit && !this.licenseServerIds[index]) {
|
|
31126
|
+
await this.ensureLicenseServerId(index, item);
|
|
31127
|
+
}
|
|
31128
|
+
const hasServerId = !!this.licenseServerIds[index];
|
|
31129
|
+
if (hasServerId)
|
|
31130
|
+
payload.id = this.licenseServerIds[index];
|
|
31131
|
+
try {
|
|
31132
|
+
const res = (isEdit && hasServerId)
|
|
31133
|
+
? await firstValueFrom(this.userDocumentService
|
|
31134
|
+
.updateUserDocument(payload))
|
|
31135
|
+
: await firstValueFrom(this.userDocumentService.createUserDocument(payload));
|
|
31136
|
+
const createdId = this.getCreatedId(res);
|
|
31137
|
+
this.markItemSaved(this.licenseServerIds, this.licenseLocallySaved, index, createdId);
|
|
31138
|
+
}
|
|
31139
|
+
catch (err) {
|
|
31140
|
+
console.error('Unable to persist license', err);
|
|
31141
|
+
}
|
|
31142
|
+
}
|
|
31143
|
+
async persistSkill(index, isEdit) {
|
|
31144
|
+
if (!this.resumeData)
|
|
31145
|
+
return;
|
|
31146
|
+
this.buildUserName();
|
|
31147
|
+
const payload = this.mapSkills(this.resumeData)[index];
|
|
31148
|
+
if (!payload)
|
|
31149
|
+
return;
|
|
31150
|
+
if (isEdit && !this.skillServerIds[index]) {
|
|
31151
|
+
await this.ensureSkillServerId(index);
|
|
31152
|
+
}
|
|
31153
|
+
const hasServerId = !!this.skillServerIds[index];
|
|
31154
|
+
if (hasServerId)
|
|
31155
|
+
payload.id = this.skillServerIds[index];
|
|
31156
|
+
try {
|
|
31157
|
+
const res = (isEdit && hasServerId)
|
|
31158
|
+
? await firstValueFrom(this.userSkillSetService.updateUserSkillSet(payload))
|
|
31159
|
+
: await firstValueFrom(this.userSkillSetService.createUserSkillSet([payload]));
|
|
31160
|
+
const createdId = this.getCreatedId(res);
|
|
31161
|
+
this.markItemSaved(this.skillServerIds, this.skillLocallySaved, index, createdId);
|
|
31162
|
+
}
|
|
31163
|
+
catch (err) {
|
|
31164
|
+
console.error('Unable to persist skill', err);
|
|
31165
|
+
}
|
|
31166
|
+
}
|
|
31167
|
+
async persistTool(index, isEdit) {
|
|
31168
|
+
if (!this.resumeData)
|
|
31169
|
+
return;
|
|
31170
|
+
this.buildUserName();
|
|
31171
|
+
const payload = this.mapTools(this.resumeData)[index];
|
|
31172
|
+
if (!payload)
|
|
31173
|
+
return;
|
|
31174
|
+
if (isEdit && !this.toolServerIds[index]) {
|
|
31175
|
+
await this.ensureToolServerId(index);
|
|
31176
|
+
}
|
|
31177
|
+
const hasServerId = !!this.toolServerIds[index];
|
|
31178
|
+
if (hasServerId)
|
|
31179
|
+
payload.id = this.toolServerIds[index];
|
|
31180
|
+
try {
|
|
31181
|
+
const res = (isEdit && hasServerId)
|
|
31182
|
+
? await firstValueFrom(this.userToolService.updateUserTool(payload))
|
|
31183
|
+
: await firstValueFrom(this.userToolService.createUserTool([payload]));
|
|
31184
|
+
const createdId = this.getCreatedId(res);
|
|
31185
|
+
this.markItemSaved(this.toolServerIds, this.toolLocallySaved, index, createdId);
|
|
31186
|
+
}
|
|
31187
|
+
catch (err) {
|
|
31188
|
+
console.error('Unable to persist tool', err);
|
|
31189
|
+
}
|
|
30595
31190
|
}
|
|
30596
31191
|
async saveAWSFile(sectionFolder) {
|
|
30597
31192
|
if (!this.fileData)
|
|
@@ -30705,6 +31300,81 @@ class PreviewComponent {
|
|
|
30705
31300
|
}
|
|
30706
31301
|
};
|
|
30707
31302
|
}
|
|
31303
|
+
mapBasicDetailsDashboard(data) {
|
|
31304
|
+
const basic = data.basicDetails;
|
|
31305
|
+
this.email = data.email;
|
|
31306
|
+
const jobTitle = (basic?.jobTitle ?? '').toString().trim() || 'Drone Pilot';
|
|
31307
|
+
console.log(basic);
|
|
31308
|
+
this.payloadUserName = basic?.firstName + ' ' + basic?.lastName;
|
|
31309
|
+
console.log(basic);
|
|
31310
|
+
return {
|
|
31311
|
+
providerName: this.providerName,
|
|
31312
|
+
providerId: this.providerId,
|
|
31313
|
+
UserRoleId: this.roleData.roleInfo.id,
|
|
31314
|
+
userDetail: {
|
|
31315
|
+
userBio: basic.summary || "",
|
|
31316
|
+
firstName: basic.firstName,
|
|
31317
|
+
lastName: basic.lastName,
|
|
31318
|
+
email: basic.email,
|
|
31319
|
+
address1: basic.address,
|
|
31320
|
+
address2: "",
|
|
31321
|
+
city: basic.city,
|
|
31322
|
+
state: basic.state,
|
|
31323
|
+
zipcode: basic.zipCode,
|
|
31324
|
+
county: "", // you don’t have this → keep empty or derive
|
|
31325
|
+
country: basic.country,
|
|
31326
|
+
latitude: null, // optional (if using Google API later)
|
|
31327
|
+
longitude: null,
|
|
31328
|
+
yearsActive: 0,
|
|
31329
|
+
isInitialSetupCompleted: true,
|
|
31330
|
+
id: this.payloadUserId,
|
|
31331
|
+
phoneNumber: basic.phone,
|
|
31332
|
+
yearsOfExperince: basic.yearsOfExperience,
|
|
31333
|
+
userJobTitle: [jobTitle],
|
|
31334
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
31335
|
+
datePattern: new Date().toLocaleDateString(),
|
|
31336
|
+
dateTimePattern: new Date().toLocaleString(),
|
|
31337
|
+
timePattern: new Date().toLocaleTimeString()
|
|
31338
|
+
}
|
|
31339
|
+
};
|
|
31340
|
+
}
|
|
31341
|
+
mapSavedBasicDetailsToPreview(userDetailRes) {
|
|
31342
|
+
const server = userDetailRes?.data?.userDetail ?? userDetailRes?.userDetail ?? userDetailRes?.data ?? userDetailRes;
|
|
31343
|
+
if (!server)
|
|
31344
|
+
return null;
|
|
31345
|
+
if (!(server?.firstName || server?.lastName || server?.email))
|
|
31346
|
+
return null;
|
|
31347
|
+
const source = this.store.profileSignal()?.basicDetails;
|
|
31348
|
+
const fallback = source ?? {};
|
|
31349
|
+
const serverJobTitle = Array.isArray(server?.userJobTitle)
|
|
31350
|
+
? (server.userJobTitle[0] ?? '')
|
|
31351
|
+
: (server?.userJobTitle ?? server?.jobTitle ?? '');
|
|
31352
|
+
const parsedYears = Number(server?.yearsOfExperince ?? server?.yearsOfExperience);
|
|
31353
|
+
return {
|
|
31354
|
+
firstName: server?.firstName ?? fallback.firstName ?? '',
|
|
31355
|
+
lastName: server?.lastName ?? fallback.lastName ?? '',
|
|
31356
|
+
email: server?.email ?? fallback.email ?? '',
|
|
31357
|
+
phone: server?.phoneNumber ?? server?.phone ?? fallback.phone ?? '',
|
|
31358
|
+
address: server?.address1 ?? server?.address ?? fallback.address ?? '',
|
|
31359
|
+
city: server?.city ?? fallback.city ?? '',
|
|
31360
|
+
state: server?.state ?? fallback.state ?? '',
|
|
31361
|
+
zipCode: server?.zipcode ?? server?.zipCode ?? fallback.zipCode ?? '',
|
|
31362
|
+
country: server?.country ?? fallback.country ?? '',
|
|
31363
|
+
jobTitle: serverJobTitle || fallback.jobTitle || '',
|
|
31364
|
+
yearsOfExperience: Number.isFinite(parsedYears) ? parsedYears : Number(fallback.yearsOfExperience ?? 0),
|
|
31365
|
+
summary: server?.userBio ?? fallback.summary ?? '',
|
|
31366
|
+
};
|
|
31367
|
+
}
|
|
31368
|
+
hasUserDetailPayload(userDetailRes) {
|
|
31369
|
+
const payload = userDetailRes?.data?.userDetail ?? userDetailRes?.userDetail ?? userDetailRes?.data ?? userDetailRes;
|
|
31370
|
+
if (!payload)
|
|
31371
|
+
return false;
|
|
31372
|
+
if (Array.isArray(payload))
|
|
31373
|
+
return payload.length > 0;
|
|
31374
|
+
if (typeof payload !== 'object')
|
|
31375
|
+
return !!payload;
|
|
31376
|
+
return Object.keys(payload).length > 0;
|
|
31377
|
+
}
|
|
30708
31378
|
mapWorkExperience(data) {
|
|
30709
31379
|
return data.workExperience.map((exp) => {
|
|
30710
31380
|
return {
|
|
@@ -30869,12 +31539,54 @@ class PreviewComponent {
|
|
|
30869
31539
|
const d = new Date(date + "-01"); // add day
|
|
30870
31540
|
return d.toISOString();
|
|
30871
31541
|
}
|
|
31542
|
+
workActionLabel(index) {
|
|
31543
|
+
if (this.isAddingJob())
|
|
31544
|
+
return 'Save';
|
|
31545
|
+
if (index === null || index < 0)
|
|
31546
|
+
return 'Save';
|
|
31547
|
+
return this.hasUnsavedWorkItem(index) ? 'Save' : 'Update';
|
|
31548
|
+
}
|
|
31549
|
+
educationActionLabel(index) {
|
|
31550
|
+
if (this.isAddingEducation())
|
|
31551
|
+
return 'Save';
|
|
31552
|
+
if (index === null || index < 0)
|
|
31553
|
+
return 'Save';
|
|
31554
|
+
return this.hasUnsavedEducationItem(index) ? 'Save' : 'Update';
|
|
31555
|
+
}
|
|
31556
|
+
certificationActionLabel(index) {
|
|
31557
|
+
if (this.isAddingCertification())
|
|
31558
|
+
return 'Save';
|
|
31559
|
+
if (index === null || index < 0)
|
|
31560
|
+
return 'Save';
|
|
31561
|
+
return this.hasUnsavedCertificationItem(index) ? 'Save' : 'Update';
|
|
31562
|
+
}
|
|
31563
|
+
licenseActionLabel(index) {
|
|
31564
|
+
if (this.isAddingLicense())
|
|
31565
|
+
return 'Save';
|
|
31566
|
+
if (index === null || index < 0)
|
|
31567
|
+
return 'Save';
|
|
31568
|
+
return this.hasUnsavedLicenseItem(index) ? 'Save' : 'Update';
|
|
31569
|
+
}
|
|
31570
|
+
skillActionLabel(index) {
|
|
31571
|
+
if (this.isAddingSkill())
|
|
31572
|
+
return 'Save';
|
|
31573
|
+
if (index === null || index < 0)
|
|
31574
|
+
return 'Save';
|
|
31575
|
+
return this.hasUnsavedSkillItem(index) ? 'Save' : 'Update';
|
|
31576
|
+
}
|
|
31577
|
+
toolActionLabel(index) {
|
|
31578
|
+
if (this.isAddingTool())
|
|
31579
|
+
return 'Save';
|
|
31580
|
+
if (index === null || index < 0)
|
|
31581
|
+
return 'Save';
|
|
31582
|
+
return this.hasUnsavedToolItem(index) ? 'Save' : 'Update';
|
|
31583
|
+
}
|
|
30872
31584
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PreviewComponent, deps: [{ token: CredentialingStore }, { token: FileService }, { token: UserSkillSetService }, { token: UserToolService }, { token: UserDocumentService }, { token: UserEducationService }, { token: UserDetailService }, { token: UserExperienceService }, { token: i6.TokenService }, { token: LIBRARY_CONFIG }], target: i0.ɵɵFactoryTarget.Component });
|
|
30873
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: PreviewComponent, isStandalone: false, selector: "app-preview", inputs: { providerId: "providerId", providerName: "providerName", roleData: "roleData", cloudfrontUrl: "cloudfrontUrl" }, outputs: { backToParent: "backToParent" }, ngImport: i0, template: "\r\n<div class=\"preview-page-header\">\r\n <h2 class=\"preview-title\">Confirm based on your resume</h2>\r\n <p class=\"preview-subtitle\">Please confirm everything is accurate. It is based on your resume</p>\r\n</div>\r\n\r\n\r\n<div class=\"container py-4\">\r\n <div class=\"section mb-5\">\r\n <div *ngIf=\"details() as data; else loading\">\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"!isEditMode()\">\r\n <div class=\"card-body p-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-4\">\r\n <div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h2 class=\"fw-bold mb-1\">{{ data.firstName }} {{ data.lastName }}</h2>\r\n <span *ngIf=\"basicSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <p class=\"text-muted\">{{ data.jobTitle }} \u2022 {{ data.yearsOfExperience }} Years Exp.</p>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <span class=\"badge bg-primary-subtle text-primary border p-2\">ID: Verified</span>\r\n </div>\r\n\r\n <div class=\"row g-4\">\r\n <div class=\"col-12 border-bottom pb-2\">\r\n <h6 class=\"text-uppercase small fw-bold text-muted\">Summary</h6>\r\n <p class=\"text-secondary small mb-0\">{{ data.summary }}</p>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">EMAIL</label>\r\n <span class=\"fw-bold small\">{{ data.email }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">PHONE</label>\r\n <span class=\"fw-bold small\">{{ data.phone }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">LOCATION</label>\r\n <span class=\"small\">{{ data.address }},{{ data.city }},{{ data.state }},{{ data.zipCode }}, {{\r\n data.country }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-light text-end\">\r\n <button class=\"btn btn-sm btn-primary px-4\" (click)=\"toggleEdit()\">Edit Details</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"isEditMode()\">\r\n <div class=\"card-header bg-white fw-bold\">Update Profile</div>\r\n <div class=\"card-body p-4\">\r\n <form class=\"row g-3\" #basicForm=\"ngForm\" novalidate>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">First Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.firstName\"\r\n name=\"fName\"\r\n placeholder=\"First Name\"\r\n required\r\n #fName=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"fName.invalid && (fName.dirty || fName.touched)\">First name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Last Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.lastName\"\r\n name=\"lName\"\r\n placeholder=\"Last Name\"\r\n required\r\n #lName=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"lName.invalid && (lName.dirty || lName.touched)\">Last name is required</div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Summary</label>\r\n <textarea class=\"form-control\" rows=\"3\" [(ngModel)]=\"tempProfile.summary\" name=\"sum\"\r\n placeholder=\"Summary\"></textarea>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Email <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"email\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.email\"\r\n name=\"email\"\r\n placeholder=\"Email\"\r\n required\r\n email\r\n #email=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"email.invalid && (email.dirty || email.touched)\">\r\n <span *ngIf=\"email.errors?.['required']\">Email is required</span>\r\n <span *ngIf=\"email.errors?.['email']\">Email format is invalid</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Phone Number <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"tempProfile.phone\"\r\n (ngModelChange)=\"tempProfile.phone = sanitizePhone($event)\"\r\n name=\"phone\"\r\n placeholder=\"Phone (10 digits)\"\r\n required\r\n pattern=\"^\\d{10}$\"\r\n maxlength=\"10\"\r\n inputmode=\"numeric\"\r\n #phone=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"phone.invalid && (phone.dirty || phone.touched)\">\r\n <span *ngIf=\"phone.errors?.['required']\">Phone number is required</span>\r\n <span *ngIf=\"phone.errors?.['pattern']\">Phone number must be 10 digits</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-3\">\r\n <label class=\"small text-muted d-block\">Home Address <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.address\"\r\n name=\"address\"\r\n placeholder=\"Home Address\"\r\n required\r\n #address=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"address.invalid && (address.dirty || address.touched)\">Home address is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">City <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.city\"\r\n name=\"city\"\r\n placeholder=\"City\"\r\n required\r\n #city=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"city.invalid && (city.dirty || city.touched)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">State <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.state\"\r\n name=\"state\"\r\n placeholder=\"State\"\r\n required\r\n #state=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"state.invalid && (state.dirty || state.touched)\">State is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Zip Code <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.zipCode\"\r\n name=\"zipCode\"\r\n placeholder=\"Zip Code\"\r\n required\r\n #zipCode=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"zipCode.invalid && (zipCode.dirty || zipCode.touched)\">Zip code is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Country <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.country\"\r\n name=\"country\"\r\n placeholder=\"Country\"\r\n required\r\n #country=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"country.invalid && (country.dirty || country.touched)\">Country is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Job Title <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.jobTitle\"\r\n name=\"jobTitle\"\r\n placeholder=\"Job Title\"\r\n required\r\n #jobTitle=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"jobTitle.invalid && (jobTitle.dirty || jobTitle.touched)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.yearsOfExperience\"\r\n name=\"yearsOfExperience\"\r\n placeholder=\"Years of Experience\"\r\n required\r\n min=\"0\"\r\n #yearsOfExperience=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"yearsOfExperience.invalid && (yearsOfExperience.dirty || yearsOfExperience.touched)\">\r\n Years of experience is required\r\n </div>\r\n </div>\r\n\r\n\r\n </form>\r\n </div>\r\n <div class=\"card-footer bg-light text-end gap-2 d-flex justify-content-end\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancel()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" (click)=\"save()\">Save Changes</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Work Experience</h5>\r\n <span *ngIf=\"workSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add work experience\"\r\n (click)=\"addJob()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"experience().length === 0 && !isAddingJob()\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one work experience.</div>\r\n </div>\r\n <div class=\"list-group list-group-flush shadow-sm rounded\">\r\n <div *ngIf=\"isAddingJob() && tempJob()\" class=\"list-group-item p-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\"\r\n name=\"newJobTitle\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\"\r\n name=\"newCompany\"\r\n />\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\"\r\n name=\"newCity\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\"\r\n name=\"newStartDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\"\r\n (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"newEndDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\"\r\n name=\"newIsCurrent\"\r\n id=\"newIsCurrent\"\r\n />\r\n <label class=\"form-check-label\" for=\"newIsCurrent\">Current</label>\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\"\r\n name=\"newResponsibilities\"\r\n ></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\"\r\n name=\"newWorkAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"card-footer bg-light text-end mt-3\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\" (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let job of experience(); let i = index\" class=\"list-group-item p-3\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <div class=\"d-flex align-items-center cursor-pointer flex-grow-1\" (click)=\"toggleJob(i)\">\r\n <i class=\"bi bi-briefcase text-primary me-3 fs-4\"></i>\r\n <div>\r\n <h6 class=\"mb-0 fw-bold\">{{ job.jobTitle }}</h6>\r\n <small class=\"text-muted\">\r\n {{ job.company }} \u2022 {{ formatMonthYear(job.startDate) }} - {{ job.isCurrent ? 'Present' : formatMonthYear(job.endDate) }}\r\n </small>\r\n <div\r\n *ngIf=\"editingJobIndex() !== i && (workIssuesByIndex()[i] || []).length > 0\"\r\n class=\"alert alert-warning py-1 px-2 mt-2\"\r\n >\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center gap-2\">\r\n \r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditJob(i)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteJob(i);\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n \r\n\r\n <!-- <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-light p-0\"\r\n style=\"width: 34px; height: 34px;\"\r\n (click)=\"toggleJob(i)\"\r\n title=\"Expand\"\r\n >\r\n <i class=\"bi cursor-pointer\" [ngClass]=\"expandedIndex() === i ? 'bi-chevron-up' : 'bi-chevron-down'\"></i>\r\n </button> -->\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3 bg-light p-3 rounded small\" *ngIf=\"expandedIndex() === i\">\r\n <ng-container *ngIf=\"editingJobIndex() !== i; else editJobForm\">\r\n <ul class=\"mb-0\">\r\n <li *ngFor=\"let res of job.responsibilities\">{{ res }}</li>\r\n </ul>\r\n </ng-container>\r\n\r\n <ng-template #editJobForm>\r\n <div *ngIf=\"(workIssuesByIndex()[i] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\"\r\n name=\"jobTitle{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.jobTitle)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\"\r\n name=\"company{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.company)\">Company name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\"\r\n name=\"city{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\"\r\n name=\"startDate{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\"\r\n (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"endDate{{ i }}\"\r\n />\r\n <div\r\n class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\"\r\n >\r\n End date is required\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\"\r\n name=\"isCurrent{{ i }}\"\r\n id=\"isCurrent{{ i }}\"\r\n />\r\n <label class=\"form-check-label\" for=\"isCurrent{{ i }}\">Current</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\"\r\n name=\"responsibilities{{ i }}\"\r\n ></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\"\r\n name=\"workAttachment{{ i }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n </ng-template>\r\n </div>\r\n\r\n <div class=\"card-footer bg-light text-end\" *ngIf=\"editingJobIndex() === i\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\" (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div> \r\n\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Certifications</h5>\r\n <span *ngIf=\"certificationsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add certification\"\r\n (click)=\"addCertification()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"certs().length === 0 && !isAddingCertification() && editingCertificationIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one certification.</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingCertification() && tempCertification()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\"\r\n name=\"newCertName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"newCertOrg\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\"\r\n name=\"newCertState\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"newCertCredentialId\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\"\r\n name=\"newCertIssueDate\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\"\r\n name=\"newCertExpiryDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\"\r\n name=\"newCertAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\" (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : 'Save' }}\r\n </button> </div>\r\n </div>\r\n\r\n <div *ngFor=\"let cert of certs(); let ci = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingCertificationIndex() !== ci; else editCert\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ cert.name }}</div>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ cert.issuingOrganization || '\u2014' }}\r\n <span *ngIf=\"cert.state\"> \u2022 {{ cert.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ cert.issueDate ? formatMonthYear(cert.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ cert.expiryDate ? formatMonthYear(cert.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditCertification(ci)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteCertification(ci)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editCert>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\"\r\n name=\"certName{{ ci }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"certOrg{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\"\r\n name=\"certState{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"certCredentialId{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\"\r\n name=\"certIssue{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\"\r\n name=\"certExpiry{{ ci }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\"\r\n name=\"certAttachment{{ ci }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\" (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : 'Save' }}\r\n </button> </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Licenses</h5>\r\n <span *ngIf=\"licensesSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add license\"\r\n (click)=\"addLicense()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"licenses().length === 0 && !isAddingLicense() && editingLicenseIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one license.</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingLicense() && tempLicense()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\"\r\n name=\"newLicName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\"\r\n name=\"newLicAuthority\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\"\r\n name=\"newLicNumber\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\"\r\n name=\"newLicState\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\"\r\n name=\"newLicIssueDate\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\"\r\n name=\"newLicExpiryDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\"\r\n name=\"newLicenseAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\" (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let lic of licenses(); let li = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingLicenseIndex() !== li; else editLic\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ lic.name }}</div>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ lic.issuingAuthority || '\u2014' }}\r\n <span *ngIf=\"lic.state\"> \u2022 {{ lic.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ lic.issueDate ? formatMonthYear(lic.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ lic.expiryDate ? formatMonthYear(lic.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditLicense(li)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteLicense(li)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editLic>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\"\r\n name=\"licName{{ li }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\"\r\n name=\"licAuthority{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\"\r\n name=\"licNumber{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\"\r\n name=\"licState{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\"\r\n name=\"licIssue{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\"\r\n name=\"licExpiry{{ li }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\"\r\n name=\"licenseAttachment{{ li }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\" (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Tools</h5>\r\n <span *ngIf=\"toolsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add tool\"\r\n (click)=\"addTool()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let tool of tools(); let ti = index\" class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openToolEditor(ti)\"\r\n title=\"Edit\"\r\n >\r\n <span class=\"fw-normal text-dark\">{{ tool }}</span>\r\n <span\r\n *ngIf=\"(toolIssuesByIndex()[ti] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\"\r\n >\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Delete tool\"\r\n (click)=\"deleteTool(ti)\"\r\n >\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"tools().length === 0\" class=\"text-muted small\">No tools added.</span>\r\n </div>\r\n\r\n <!-- Tool edit panel (overlay) -->\r\n <div\r\n *ngIf=\"isToolEditorOpen()\"\r\n class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\"\r\n (click)=\"closeToolEditor()\"\r\n >\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeToolEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingTool() ? 'Add Tool' : ('Edit ' + (toolForm()?.name || '') + ' Tool') }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"toolFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ toolFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Tool Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.name\"\r\n (ngModelChange)=\"patchToolForm({ name: $event })\"\r\n name=\"toolName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(toolForm()?.name)\">Tool name is required</div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Service Provider</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.providerName\"\r\n (ngModelChange)=\"patchToolForm({ providerName: $event })\"\r\n name=\"toolProvider\"\r\n placeholder=\"Service Provider\"\r\n />\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n *ngFor=\"let s of [1,2,3,4,5]\"\r\n type=\"button\"\r\n class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempToolStars(s)\"\r\n [attr.aria-label]=\"'Set rating ' + s\"\r\n >\r\n <span [class]=\"(toolForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (toolForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ toolForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(toolForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.year\"\r\n (ngModelChange)=\"patchToolForm({ year: $event === '' ? null : +$event })\"\r\n name=\"toolYear\"\r\n min=\"1\"\r\n max=\"30\"\r\n placeholder=\"Years of Experience\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"toolForm()?.year === null || toolForm()?.year === undefined\">Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"toolForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchToolForm({ profileVisibility: $event })\"\r\n name=\"toolVisible\"\r\n id=\"toolVisible\"\r\n />\r\n <label class=\"form-check-label\" for=\"toolVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.notes\"\r\n (ngModelChange)=\"patchToolForm({ notes: $event })\"\r\n name=\"toolNotes\"\r\n placeholder=\"Comment your tool here...\"\r\n ></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button\r\n *ngIf=\"!isAddingTool() && editingToolIndex() !== null\"\r\n type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\"\r\n (click)=\"deleteTool(editingToolIndex()!)\"\r\n >\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeToolEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" (click)=\"saveToolEditor()\">\r\n {{ isAddingTool() ? 'Save' : 'Update' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Skills</h5>\r\n <span *ngIf=\"skillsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add skill\"\r\n (click)=\"addSkill()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let skill of skills(); let si = index\" class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openSkillEditor(si)\"\r\n title=\"Edit\"\r\n >\r\n <span class=\"fw-normal text-dark\">{{ skill }}</span>\r\n <span\r\n *ngIf=\"(skillIssuesByIndex()[si] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\"\r\n >\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Delete skill\"\r\n (click)=\"deleteSkill(si); $event.stopPropagation()\"\r\n >\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"skills().length === 0\" class=\"text-muted small\">No skills added.</span>\r\n </div>\r\n\r\n <!-- Skill edit panel (overlay) -->\r\n <div\r\n *ngIf=\"isSkillEditorOpen()\"\r\n class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\"\r\n (click)=\"closeSkillEditor()\"\r\n >\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeSkillEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingSkill() ? 'Add Skill' : ('Edit ' + (skillForm()?.name || '') + ' Skill') }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"skillFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ skillFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Skills Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.name\"\r\n (ngModelChange)=\"patchSkillForm({ name: $event })\"\r\n name=\"skillName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(skillForm()?.name)\">Skillset name is required</div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Service Provider</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.providerName\"\r\n (ngModelChange)=\"patchSkillForm({ providerName: $event })\"\r\n name=\"skillProvider\"\r\n placeholder=\"Service Provider\"\r\n />\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n *ngFor=\"let s of [1,2,3,4,5]\"\r\n type=\"button\"\r\n class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempSkillStars(s)\"\r\n [attr.aria-label]=\"'Set rating ' + s\"\r\n >\r\n <span [class]=\"(skillForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (skillForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ skillForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(skillForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.year\"\r\n (ngModelChange)=\"patchSkillForm({ year: $event === '' ? null : +$event })\"\r\n name=\"skillYear\"\r\n min=\"1\"\r\n max=\"30\"\r\n placeholder=\"Years of Experience\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"skillForm()?.year === null || skillForm()?.year === undefined\">Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"skillForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchSkillForm({ profileVisibility: $event })\"\r\n name=\"skillVisible\"\r\n id=\"skillVisible\"\r\n />\r\n <label class=\"form-check-label\" for=\"skillVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.notes\"\r\n (ngModelChange)=\"patchSkillForm({ notes: $event })\"\r\n name=\"skillNotes\"\r\n placeholder=\"Comment your skill here...\"\r\n ></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button\r\n *ngIf=\"!isAddingSkill() && editingSkillIndex() !== null\"\r\n type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\"\r\n (click)=\"deleteSkill(editingSkillIndex()!)\"\r\n >\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeSkillEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" (click)=\"saveSkillEditor()\">\r\n {{ isAddingSkill() ? 'Save' : 'Update' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Education</h5>\r\n <span *ngIf=\"educationSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add education\"\r\n (click)=\"addEducation()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0 && !isAddingEducation() && editingEducationIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one education.</div>\r\n </div>\r\n\r\n <div *ngIf=\"isAddingEducation() && tempEducation()\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\"\r\n name=\"newEduDegree\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\"\r\n name=\"newEduInstitution\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\"\r\n name=\"newEduDegreeType\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\"\r\n name=\"newEduStartDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\"\r\n name=\"newEduEndDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" (click)=\"saveEducation()\">Save</button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let edu of educationList(); let ei = index\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <div class=\"d-flex justify-content-between align-items-start\">\r\n <div>\r\n <h6 class=\"fw-bold mb-1\">{{ edu.degree }}</h6>\r\n <p class=\"small text-primary mb-0\">{{ edu.institution }}</p>\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\" *ngIf=\"editingEducationIndex() !== ei\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditEducation(ei)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteEducation(ei)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"editingEducationIndex() === ei\" class=\"mt-3\">\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\"\r\n name=\"eduDegree{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\"\r\n name=\"eduInstitution{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.institution)\">Institution name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\"\r\n name=\"eduDegreeType{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">City</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.city\"\r\n (ngModelChange)=\"patchTempEducation({ city: $event })\"\r\n name=\"eduCity{{ ei }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\"\r\n name=\"eduStartDate{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\"\r\n name=\"eduEndDate{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" (click)=\"saveEducation()\">Save</button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0\" class=\"text-muted small\">No education added.</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex gap-2 justify-content-center mt-5\">\r\n <button class=\"btn btn-outline-secondary px-5\" (click)=\"onBackClick()\">Back</button>\r\n <button\r\n class=\"btn btn-primary px-5 shadow\"\r\n [disabled]=\"!canConfirmAndContinue()\"\r\n [title]=\"canConfirmAndContinue() ? '' : 'Please fill all required fields in all mandatory sections.'\"\r\n (click)=\"saveResumedetails()\"\r\n >\r\n Confirm All & Continue\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<ng-template #loading>\r\n <div class=\"text-center p-5\">\r\n <div class=\"spinner-border text-primary\"></div>\r\n <p class=\"text-muted mt-2\">Loading data...</p>\r\n </div>\r\n</ng-template>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showPopup\" >\r\n <div class=\"status-modal-card\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"status-modal-header\">\r\n <h3 class=\"mb-1\">Saving Resume Details</h3>\r\n <p class=\"text-muted mb-0\">Please wait while we process each section.</p>\r\n </div>\r\n\r\n <div class=\"status-modal-body\">\r\n <div class=\"status-modal-row\" *ngFor=\"let item of statusList\">\r\n <div class=\"status-modal-name\">{{ item.name }}</div>\r\n\r\n <div class=\"status-modal-state pending\" *ngIf=\"item.status === 'pending'\">\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span> In progress\r\n </div>\r\n\r\n <div class=\"status-modal-state success\" *ngIf=\"item.status === 'success'\">\r\n <span class=\"me-1\">\u2713</span> Saved\r\n </div>\r\n\r\n <div class=\"status-modal-state partial\" *ngIf=\"item.status === 'partial'\">\r\n <span class=\"me-1\">!</span> {{ item.successCount }} Success / {{ item.failCount }} Failed\r\n </div>\r\n\r\n <div class=\"status-modal-state error\" *ngIf=\"item.status === 'error'\">\r\n <span class=\"me-1\">\u2716</span> {{ item.error || 'Failed' }}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"status-modal-footer\">\r\n <!-- <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeSavePopup()\">Back to Preview</button> -->\r\n <div class=\"d-flex justify-content-end\">\r\n <button\r\n [disabled]=\"showLoader\" [ng2-loading]=\"showLoader\"\r\n *ngIf=\"allSaveStepsSucceeded()\"\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"goToDashboard()\"\r\n >\r\n Go to Dashboard\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showBackConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">Leave this page?</h4>\r\n <p class=\"text-muted mb-4\">Your data might be lost if you go back now.</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"stayOnPreview()\">Stay</button>\r\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"proceedBack()\">Proceed Back</button>\r\n </div>\r\n </div>\r\n</div>", styles: [".container{max-width:900px;margin:30px auto;font-family:Arial,sans-serif;color:#333}.preview-page-header{max-width:900px;margin:50px auto 10px;padding:0 12px}.preview-title{font-weight:600;margin:0}.preview-subtitle{margin-top:7px;font-size:16px}.section{margin-bottom:25px}.section-title{font-weight:600;display:block;margin-bottom:8px}.section-title .sub{font-weight:400;color:#777;font-size:13px}.dropdown-box{border:1px solid #ddd;border-radius:8px;padding:12px 15px;display:flex;justify-content:space-between;align-items:center}.icons{display:flex;gap:10px}.icon{cursor:pointer;font-size:14px;color:#777}.card{border:1px solid #ddd;border-radius:10px;padding:20px;background:#fff}.category{border-bottom:1px solid #eee;padding:15px 0}.category:last-child{border-bottom:none}.category-header{display:flex;justify-content:space-between;font-weight:600;margin-bottom:10px}.tags{display:flex;flex-wrap:wrap;gap:10px}.tag{background:#f2f4f7;padding:6px 12px;border-radius:6px;font-size:13px}.footer{text-align:center;margin-top:15px;color:#777;cursor:pointer}.actions{display:flex;justify-content:space-between;margin:50px 0 27px}.text-secondary{white-space:pre-line;font-size:.95rem}.extra-small{font-size:.8rem}.card{transition:transform .2s ease,box-shadow .2s ease}.card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem #0000001a!important}.achievement-section .badge{font-size:.75rem;white-space:normal;text-align:left}.skill-tag .badge{font-weight:500;font-size:.9rem;transition:all .2s ease;cursor:default}.skill-tag .badge:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.skill-tag .btn-close{opacity:.5}.skill-tag .btn-close:hover{opacity:1}.bg-light.text-dark.border{background-color:#f8f9fa!important;border-color:#dee2e6!important}.card{transition:all .3s cubic-bezier(.25,.8,.25,1)}.card:hover{box-shadow:0 10px 20px #0000001a!important}.card:hover .bg-light{background-color:#e8f5e9!important}.position-fixed .card:hover{transform:none!important}code{background-color:#f8f9fa;padding:2px 6px;border-radius:4px}.list-group-item{transition:all .2s ease-in-out}.list-group-item:hover{background-color:#fcfcfc;transform:translate(5px);box-shadow:-5px 0 15px #0000000d}.border-dashed{border-style:dashed!important}.tool-card{transition:all .2s ease-in-out;border-radius:12px}.tool-card:hover{transform:translateY(-5px);border-color:var(--bs-primary)!important;box-shadow:0 10px 15px #0000001a!important}.tool-card:hover .icon-box{background-color:var(--bs-primary-subtle)!important}.tool-card .icon-box{width:60px;height:60px;transition:background-color .2s ease}.border-dashed{border:2px dashed #dee2e6!important}.border-dashed:hover{border-color:var(--bs-primary)!important;background-color:#fff!important}.popup{position:fixed;top:20%;right:20px;width:320px;background:#fff;border-radius:10px;padding:16px;box-shadow:0 4px 12px #0003}.status-row{display:flex;justify-content:space-between;margin:10px 0}.success{color:#2e7d32;font-weight:700}.error{color:#d32f2f}.pending{color:#f9a825}.section-flag{font-weight:600}button.btn.btn-sm.btn-outline-primary{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:10px}button.btn.btn-sm.btn-outline-primary:hover{background-color:#356895;border-color:#356895;color:#fff}button.btn.btn-sm.btn-outline-danger{border-color:#dc3545;color:#dc3545;border-radius:10px}button.btn.btn-sm.btn-outline-danger:hover{background-color:#bb2d3b;border-color:#bb2d3b;color:#fff}button.btn.btn-sm.btn-outline-primary.rounded-circle{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:50%!important}button.action-icon-btn{min-width:22px;width:22px;height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:0!important;background:transparent!important;border:none!important;box-shadow:none!important;line-height:1}button.action-icon-btn.btn-outline-primary{color:#4077ad!important;border-color:#4077ad!important}button.action-icon-btn.btn-outline-danger{color:#dc3545!important}.action-icon-image{width:20px;height:20px;object-fit:contain;display:inline-block}.action-icon-image.edit-icon{filter:brightness(0) saturate(100%) invert(41%) sepia(39%) saturate(774%) hue-rotate(170deg) brightness(91%) contrast(89%)}.action-icon-image.delete-icon{filter:brightness(0) saturate(100%) invert(24%) sepia(79%) saturate(4008%) hue-rotate(344deg) brightness(91%) contrast(90%)}.modal-overlay{position:fixed;inset:0;background:#11182773;display:flex;align-items:center;justify-content:center;z-index:2500;padding:20px}.status-modal-card{width:min(760px,96vw);max-height:85vh;overflow:auto;background:#fff;border-radius:16px;box-shadow:0 20px 40px #0f172a40;border:1px solid #e5e7eb}.status-modal-header{padding:20px 24px 14px;border-bottom:1px solid #eef2f7}.status-modal-body{padding:12px 20px 20px}.status-modal-footer{padding:12px 20px 20px;border-top:1px solid #eef2f7;display:flex;justify-content:end;align-items:center;gap:12px}.status-modal-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 10px;border-bottom:1px solid #f1f5f9}.status-modal-row:last-child{border-bottom:0}.status-modal-name{font-weight:600;color:#1f2937}.status-modal-state{font-size:.92rem;font-weight:600}.status-modal-state.pending{color:#a16207}.status-modal-state.success{color:#166534}.status-modal-state.partial{color:#b45309}.status-modal-state.error{color:#b91c1c}.confirm-modal-card{width:min(520px,96vw);background:#fff;border-radius:14px;box-shadow:0 20px 35px #0f172a38;border:1px solid #e5e7eb;padding:24px}.year-experience-select{max-height:150px}\n"], dependencies: [{ kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i8.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i8.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i8.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i8.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i8.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i8.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i8.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: Ng2LoadingSpinnerDirective, selector: "[ng2-loading]", inputs: ["ng2-loading", "config", "template"] }] });
|
|
31585
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: PreviewComponent, isStandalone: false, selector: "app-preview", inputs: { providerId: "providerId", providerName: "providerName", roleData: "roleData", cloudfrontUrl: "cloudfrontUrl" }, outputs: { backToParent: "backToParent" }, ngImport: i0, template: "<div class=\"preview-page-header\">\r\n <h2 class=\"preview-title\">Confirm based on your resume</h2>\r\n <p class=\"preview-subtitle\">Please confirm everything is accurate. It is based on your resume</p>\r\n</div>\r\n\r\n\r\n<div class=\"container py-4\">\r\n <div class=\"section mb-5\">\r\n <div *ngIf=\"details() as data; else loading\">\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"!isEditMode()\">\r\n <div class=\"card-body p-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-4\">\r\n <div>\r\n <div class=\"d-flex align-items-center gap-2 flex-wrap\">\r\n <h2 class=\"fw-bold mb-1\">{{ data.firstName }} {{ data.lastName }}</h2>\r\n <span *ngIf=\"basicSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n <span class=\"badge border section-flag\"\r\n [ngClass]=\"basicDetailsSaved ? 'bg-success-subtle text-success' : 'bg-warning-subtle text-warning'\">\r\n {{ basicDetailsSaved ? 'Saved' : 'Not saved yet' }}\r\n </span>\r\n </div>\r\n <p class=\"text-muted\">{{ data.jobTitle }} \u2022 {{ data.yearsOfExperience }} Years Exp.</p>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <span class=\"badge bg-primary-subtle text-primary border p-2\">ID: Verified</span>\r\n </div>\r\n\r\n <div class=\"row g-4\">\r\n <div class=\"col-12 border-bottom pb-2\">\r\n <h6 class=\"text-uppercase small fw-bold text-muted\">Summary</h6>\r\n <p class=\"text-secondary small mb-0\">{{ data.summary }}</p>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">EMAIL</label>\r\n <span class=\"fw-bold small\">{{ data.email }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">PHONE</label>\r\n <span class=\"fw-bold small\">{{ data.phone }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">LOCATION</label>\r\n <span class=\"small\">{{ data.address }},{{ data.city }},{{ data.state }},{{ data.zipCode }}, {{\r\n data.country }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-light d-flex align-items-center justify-content-between\">\r\n <span *ngIf=\"!basicDetailsSaved\" class=\"small text-warning fw-semibold\">\r\n <i class=\"bi bi-exclamation-circle me-1\"></i> Click \"Edit Details\" to review and save your basic info.\r\n </span>\r\n <span *ngIf=\"basicDetailsSaved\" class=\"small text-success fw-semibold\">\r\n <i class=\"bi bi-check-circle me-1\"></i> Basic details saved.\r\n </span>\r\n <button class=\"btn btn-sm btn-primary px-4 ms-auto\" (click)=\"toggleEdit()\">Edit Details</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"isEditMode()\">\r\n <div class=\"card-header bg-white fw-bold\">Update Profile</div>\r\n <div class=\"card-body p-4\">\r\n <form class=\"row g-3\" #basicForm=\"ngForm\" novalidate>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">First Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.firstName\" name=\"fName\"\r\n placeholder=\"First Name\" required #fName=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"fName.invalid && (fName.dirty || fName.touched)\">First name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Last Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.lastName\" name=\"lName\"\r\n placeholder=\"Last Name\" required #lName=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"lName.invalid && (lName.dirty || lName.touched)\">Last name is\r\n required</div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Summary</label>\r\n <textarea class=\"form-control\" rows=\"3\" [(ngModel)]=\"tempProfile.summary\" name=\"sum\"\r\n placeholder=\"Summary\"></textarea>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Email <span class=\"text-danger\">*</span></label>\r\n <input type=\"email\" class=\"form-control\" [(ngModel)]=\"tempProfile.email\" name=\"email\" placeholder=\"Email\"\r\n required email #email=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"email.invalid && (email.dirty || email.touched)\">\r\n <span *ngIf=\"email.errors?.['required']\">Email is required</span>\r\n <span *ngIf=\"email.errors?.['email']\">Email format is invalid</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Phone Number <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"tempProfile.phone\"\r\n (ngModelChange)=\"tempProfile.phone = sanitizePhone($event)\" name=\"phone\" placeholder=\"Phone (10 digits)\"\r\n required pattern=\"^\\d{10}$\" maxlength=\"10\" inputmode=\"numeric\" #phone=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"phone.invalid && (phone.dirty || phone.touched)\">\r\n <span *ngIf=\"phone.errors?.['required']\">Phone number is required</span>\r\n <span *ngIf=\"phone.errors?.['pattern']\">Phone number must be 10 digits</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-3\">\r\n <label class=\"small text-muted d-block\">Home Address <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.address\" name=\"address\"\r\n placeholder=\"Home Address\" required #address=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"address.invalid && (address.dirty || address.touched)\">Home\r\n address is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">City <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.city\" name=\"city\" placeholder=\"City\"\r\n required #city=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"city.invalid && (city.dirty || city.touched)\">City is required\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">State <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.state\" name=\"state\" placeholder=\"State\"\r\n required #state=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"state.invalid && (state.dirty || state.touched)\">State is\r\n required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Zip Code <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"tempProfile.zipCode\"\r\n (ngModelChange)=\"tempProfile.zipCode = sanitizeZipCode($event)\" name=\"zipCode\" placeholder=\"Zip Code\"\r\n required pattern=\"^\\d{1,6}$\" maxlength=\"6\" inputmode=\"numeric\" #zipCode=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"zipCode.invalid && (zipCode.dirty || zipCode.touched)\">Zip code\r\n is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"zipCode.errors?.['pattern'] && (zipCode.dirty || zipCode.touched)\">\r\n Zip code must be up to 6 digits\r\n </div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Country <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.country\" name=\"country\"\r\n placeholder=\"Country\" required #country=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"country.invalid && (country.dirty || country.touched)\">Country\r\n is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Job Title <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.jobTitle\" name=\"jobTitle\"\r\n placeholder=\"Job Title\" required #jobTitle=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"jobTitle.invalid && (jobTitle.dirty || jobTitle.touched)\">Job\r\n title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [(ngModel)]=\"tempProfile.yearsOfExperience\"\r\n name=\"yearsOfExperience\" placeholder=\"Years of Experience\" required min=\"0\"\r\n #yearsOfExperience=\"ngModel\">\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"yearsOfExperience.invalid && (yearsOfExperience.dirty || yearsOfExperience.touched)\">\r\n Years of experience is required\r\n </div>\r\n </div>\r\n\r\n\r\n </form>\r\n </div>\r\n <div class=\"card-footer bg-light text-end gap-2 d-flex justify-content-end\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancel()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingBasic\" (click)=\"save()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Saving...' : 'Save Changes' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Work Experience</h5>\r\n <span *ngIf=\"experience().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"workSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ workSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add work experience\" (click)=\"addJob()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"experience().length === 0 && !isAddingJob()\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No work experience added</div>\r\n </div>\r\n <div class=\"list-group list-group-flush shadow-sm rounded\">\r\n <div *ngIf=\"isAddingJob() && tempJob()\" class=\"list-group-item p-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\" name=\"newJobTitle\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\" name=\"newCompany\" />\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\" name=\"newCity\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\" name=\"newStartDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\" (ngModelChange)=\"patchTempJob({ endDate: $event })\" name=\"newEndDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">End date is\r\n required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\" name=\"newIsCurrent\" id=\"newIsCurrent\" />\r\n <label class=\"form-check-label\" for=\"newIsCurrent\">Current</label>\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea rows=\"4\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\" name=\"newResponsibilities\"></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\" name=\"newWorkAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"card-footer bg-light text-end mt-3\">\r\n <button class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\"\r\n (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : workActionLabel(editingJobIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngFor=\"let job of experience(); let i = index\" class=\"list-group-item p-3\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <div class=\"d-flex align-items-center cursor-pointer flex-grow-1\" (click)=\"toggleJob(i)\">\r\n <i class=\"bi bi-briefcase text-primary me-3 fs-4\"></i>\r\n <div>\r\n <h6 class=\"mb-0 fw-bold\">{{ job.jobTitle }}</h6>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedWorkItem(i) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedWorkItem(i) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <small class=\"text-muted\">\r\n {{ job.company }} \u2022 {{ formatMonthYear(job.startDate) }} - {{ job.isCurrent ? 'Present' :\r\n formatMonthYear(job.endDate) }}\r\n </small>\r\n <div *ngIf=\"editingJobIndex() !== i && (workIssuesByIndex()[i] || []).length > 0\"\r\n class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\" (click)=\"startEditJob(i)\"\r\n title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteJob(i);\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n\r\n <!-- <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-light p-0\"\r\n style=\"width: 34px; height: 34px;\"\r\n (click)=\"toggleJob(i)\"\r\n title=\"Expand\"\r\n >\r\n <i class=\"bi cursor-pointer\" [ngClass]=\"expandedIndex() === i ? 'bi-chevron-up' : 'bi-chevron-down'\"></i>\r\n </button> -->\r\n </div>\r\n </div>\r\n\r\n\r\n\r\n <div class=\"mt-3 bg-light p-3 rounded small\" *ngIf=\"expandedIndex() === i\">\r\n <ng-container *ngIf=\"editingJobIndex() !== i; else editJobForm\">\r\n <ul class=\"mb-0\">\r\n <li *ngFor=\"let res of job.responsibilities\">{{ res }}</li>\r\n </ul>\r\n </ng-container>\r\n\r\n <ng-template #editJobForm>\r\n <div *ngIf=\"(workIssuesByIndex()[i] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\" name=\"jobTitle{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.jobTitle)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\" name=\"company{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.company)\">Company name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\" name=\"city{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\" name=\"startDate{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\" (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"endDate{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">\r\n End date is required\r\n </div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\" name=\"isCurrent{{ i }}\" id=\"isCurrent{{ i }}\" />\r\n <label class=\"form-check-label\" for=\"isCurrent{{ i }}\">Current</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea rows=\"4\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\" name=\"responsibilities{{ i }}\"></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\" name=\"workAttachment{{ i }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n </ng-template>\r\n </div>\r\n\r\n <div class=\"card-footer bg-light text-end\" *ngIf=\"editingJobIndex() === i\">\r\n <button class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\"\r\n (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : workActionLabel(editingJobIndex()) }}\r\n </button>\r\n </div>\r\n\r\n\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Certifications</h5>\r\n <span *ngIf=\"certs().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"certificationSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ certificationSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add certification\" (click)=\"addCertification()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"certs().length === 0 && !isAddingCertification() && editingCertificationIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No certification added</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingCertification() && tempCertification()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\" name=\"newCertName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\" name=\"newCertOrg\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\" name=\"newCertState\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\" name=\"newCertCredentialId\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\" name=\"newCertIssueDate\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\" name=\"newCertExpiryDate\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\" name=\"newCertAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\"\r\n (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : certificationActionLabel(editingCertificationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let cert of certs(); let ci = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingCertificationIndex() !== ci; else editCert\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ cert.name }}</div>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedCertificationItem(ci) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedCertificationItem(ci) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ cert.issuingOrganization || '\u2014' }}\r\n <span *ngIf=\"cert.state\"> \u2022 {{ cert.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ cert.issueDate ? formatMonthYear(cert.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ cert.expiryDate ? formatMonthYear(cert.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditCertification(ci)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteCertification(ci)\" title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editCert>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\" name=\"certName{{ ci }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"certOrg{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\" name=\"certState{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"certCredentialId{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\" name=\"certIssue{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\" name=\"certExpiry{{ ci }}\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\" name=\"certAttachment{{ ci }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\"\r\n (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : certificationActionLabel(editingCertificationIndex()) }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Licenses</h5>\r\n <span *ngIf=\"licenses().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"licenseSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ licenseSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add license\" (click)=\"addLicense()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"licenses().length === 0 && !isAddingLicense() && editingLicenseIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No license added</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingLicense() && tempLicense()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\" name=\"newLicName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\" name=\"newLicAuthority\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\" name=\"newLicNumber\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\" name=\"newLicState\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\" name=\"newLicIssueDate\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\" name=\"newLicExpiryDate\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\" name=\"newLicenseAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\"\r\n (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : licenseActionLabel(editingLicenseIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let lic of licenses(); let li = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingLicenseIndex() !== li; else editLic\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ lic.name }}</div>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedLicenseItem(li) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedLicenseItem(li) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ lic.issuingAuthority || '\u2014' }}\r\n <span *ngIf=\"lic.state\"> \u2022 {{ lic.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ lic.issueDate ? formatMonthYear(lic.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ lic.expiryDate ? formatMonthYear(lic.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditLicense(li)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteLicense(li)\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editLic>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\" name=\"licName{{ li }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\" name=\"licAuthority{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\" name=\"licNumber{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\" name=\"licState{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\" name=\"licIssue{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\" name=\"licExpiry{{ li }}\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\" name=\"licenseAttachment{{ li }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\"\r\n (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : licenseActionLabel(editingLicenseIndex()) }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Tools</h5>\r\n <span *ngIf=\"tools().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"toolsSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ toolsSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add tool\" (click)=\"addTool()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let tool of tools(); let ti = index\" class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\" class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openToolEditor(ti)\" title=\"Edit\">\r\n <span class=\"fw-normal text-dark\">{{ tool }}</span>\r\n <span class=\"badge border ms-1\"\r\n [ngClass]=\"hasUnsavedToolItem(ti) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedToolItem(ti) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <span *ngIf=\"(toolIssuesByIndex()[ti] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\">\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Delete tool\" (click)=\"deleteTool(ti)\">\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"tools().length === 0\" class=\"text-muted small\">No tools added.</span>\r\n </div>\r\n\r\n <!-- Tool edit panel (overlay) -->\r\n <div *ngIf=\"isToolEditorOpen()\" class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\" (click)=\"closeToolEditor()\">\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeToolEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingTool() ? 'Add Tool' : ('Edit ' + (toolForm()?.name || '') + ' Tool') }}\r\n </div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"toolFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ toolFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Tool Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"toolForm()?.name\"\r\n (ngModelChange)=\"patchToolForm({ name: $event })\" name=\"toolName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(toolForm()?.name)\">Tool name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span\r\n class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button *ngFor=\"let s of [1,2,3,4,5]\" type=\"button\" class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempToolStars(s)\" [attr.aria-label]=\"'Set rating ' + s\">\r\n <span [class]=\"(toolForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (toolForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ toolForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(toolForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [ngModel]=\"toolForm()?.year\"\r\n (ngModelChange)=\"patchToolForm({ year: $event === '' ? null : +$event })\" name=\"toolYear\" min=\"1\"\r\n max=\"30\" placeholder=\"Years of Experience\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"toolForm()?.year === null || toolForm()?.year === undefined\">\r\n Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"toolForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchToolForm({ profileVisibility: $event })\" name=\"toolVisible\"\r\n id=\"toolVisible\" />\r\n <label class=\"form-check-label\" for=\"toolVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea rows=\"4\" class=\"form-control\" [ngModel]=\"toolForm()?.notes\"\r\n (ngModelChange)=\"patchToolForm({ notes: $event })\" name=\"toolNotes\"\r\n placeholder=\"Comment your tool here...\"></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button *ngIf=\"!isAddingTool() && editingToolIndex() !== null\" type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\" (click)=\"deleteTool(editingToolIndex()!)\">\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeToolEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" [disabled]=\"isSavingTool\" (click)=\"saveToolEditor()\">\r\n <span *ngIf=\"isSavingTool\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingTool ? 'Saving...' : toolActionLabel(editingToolIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Skills</h5>\r\n <span *ngIf=\"skills().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"skillsSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ skillsSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add skill\" (click)=\"addSkill()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let skill of skills(); let si = index\" class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openSkillEditor(si)\" title=\"Edit\">\r\n <span class=\"fw-normal text-dark\">{{ skill }}</span>\r\n <span class=\"badge border ms-1\"\r\n [ngClass]=\"hasUnsavedSkillItem(si) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedSkillItem(si) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <span *ngIf=\"(skillIssuesByIndex()[si] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\">\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Delete skill\"\r\n (click)=\"deleteSkill(si); $event.stopPropagation()\">\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"skills().length === 0\" class=\"text-muted small\">No skills added.</span>\r\n </div>\r\n <!-- Skill edit panel (overlay) -->\r\n <div *ngIf=\"isSkillEditorOpen()\" class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\" (click)=\"closeSkillEditor()\">\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeSkillEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingSkill() ? 'Add Skill' : ('Edit ' + (skillForm()?.name || '') + ' Skill')\r\n }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"skillFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ skillFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Skills Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"skillForm()?.name\"\r\n (ngModelChange)=\"patchSkillForm({ name: $event })\" name=\"skillName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(skillForm()?.name)\">Skillset name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span\r\n class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button *ngFor=\"let s of [1,2,3,4,5]\" type=\"button\" class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempSkillStars(s)\" [attr.aria-label]=\"'Set rating ' + s\">\r\n <span [class]=\"(skillForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (skillForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ skillForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(skillForm()?.stars || 0) <= 0\">Star rating is required\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [ngModel]=\"skillForm()?.year\"\r\n (ngModelChange)=\"patchSkillForm({ year: $event === '' ? null : +$event })\" name=\"skillYear\" min=\"1\"\r\n max=\"30\" placeholder=\"Years of Experience\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"skillForm()?.year === null || skillForm()?.year === undefined\">Years of experience is\r\n required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"skillForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchSkillForm({ profileVisibility: $event })\" name=\"skillVisible\"\r\n id=\"skillVisible\" />\r\n <label class=\"form-check-label\" for=\"skillVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea rows=\"4\" class=\"form-control\" [ngModel]=\"skillForm()?.notes\"\r\n (ngModelChange)=\"patchSkillForm({ notes: $event })\" name=\"skillNotes\"\r\n placeholder=\"Comment your skill here...\"></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button *ngIf=\"!isAddingSkill() && editingSkillIndex() !== null\" type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\" (click)=\"deleteSkill(editingSkillIndex()!)\">\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeSkillEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" [disabled]=\"isSavingSkill\" (click)=\"saveSkillEditor()\">\r\n <span *ngIf=\"isSavingSkill\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingSkill ? 'Saving...' : skillActionLabel(editingSkillIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Education</h5>\r\n <span *ngIf=\"educationList().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"educationSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ educationSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add education\" (click)=\"addEducation()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0 && !isAddingEducation() && editingEducationIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No education added</div>\r\n <!-- <div class=\"small\">Add at least one education.</div> -->\r\n </div>\r\n\r\n <div *ngIf=\"isAddingEducation() && tempEducation()\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\" name=\"newEduDegree\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\" name=\"newEduInstitution\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\" name=\"newEduDegreeType\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\" name=\"newEduStartDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\" name=\"newEduEndDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">EDUCATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onEducationFileSelected($event)\" name=\"newEducationAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempEducation()?.fileName\">\r\n <span>{{ tempEducation()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempEducation())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingEducation\"\r\n (click)=\"saveEducation()\">\r\n <span *ngIf=\"isSavingEducation\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingEducation ? 'Uploading...' : educationActionLabel(editingEducationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let edu of educationList(); let ei = index\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <div class=\"d-flex justify-content-between align-items-start\">\r\n <div>\r\n <h6 class=\"fw-bold mb-1\">{{ edu.degree }}</h6>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedEducationItem(ei) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedEducationItem(ei) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <p class=\"small text-primary mb-0\">{{ edu.institution }}</p>\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\" *ngIf=\"editingEducationIndex() !== ei\">\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditEducation(ei)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteEducation(ei)\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"editingEducationIndex() === ei\" class=\"mt-3\">\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\" name=\"eduDegree{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\" name=\"eduInstitution{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.institution)\">Institution name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\" name=\"eduDegreeType{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">City</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.city\"\r\n (ngModelChange)=\"patchTempEducation({ city: $event })\" name=\"eduCity{{ ei }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\" name=\"eduStartDate{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\" name=\"eduEndDate{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">EDUCATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onEducationFileSelected($event)\" name=\"educationAttachment{{ ei }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempEducation()?.fileName\">\r\n <span>{{ tempEducation()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempEducation())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingEducation\" (click)=\"saveEducation()\">\r\n <span *ngIf=\"isSavingEducation\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingEducation ? 'Uploading...' : educationActionLabel(editingEducationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex gap-2 justify-content-center mt-5\">\r\n <button class=\"btn btn-outline-secondary px-5\" (click)=\"onBackClick()\">Back</button>\r\n <button class=\"btn btn-primary px-5 shadow\" [disabled]=\"!canConfirmAndContinue() || isSavingBasic\"\r\n (click)=\"onGoToDashboardClick()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Saving...' : 'Go to Dashboard' }}\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<ng-template #loading>\r\n <div class=\"text-center p-5\">\r\n <div class=\"spinner-border text-primary\"></div>\r\n <p class=\"text-muted mt-2\">Loading data...</p>\r\n </div>\r\n</ng-template>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showBackConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">Leave this page?</h4>\r\n <p class=\"text-muted mb-4\">If you go back, only saved data will be retained.</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"stayOnPreview()\">Stay</button>\r\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"proceedBack()\">Proceed Back</button>\r\n </div>\r\n </div>\r\n\r\n</div>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showDashboardConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">You are redirecting to dashboard</h4>\r\n <p class=\"text-muted mb-4\">Proceed to save initial setup completion and continue?</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" [disabled]=\"isSavingBasic\"\r\n (click)=\"cancelDashboardRedirect()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"isSavingBasic\" (click)=\"goToDashboard()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Proceeding...' : 'Proceed' }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".container{max-width:900px;margin:30px auto;font-family:Arial,sans-serif;color:#333}.preview-page-header{max-width:900px;margin:50px auto 10px;padding:0 12px}.preview-title{font-weight:600;margin:0}.preview-subtitle{margin-top:7px;font-size:16px}.section{margin-bottom:25px}.section-title{font-weight:600;display:block;margin-bottom:8px}.section-title .sub{font-weight:400;color:#777;font-size:13px}.dropdown-box{border:1px solid #ddd;border-radius:8px;padding:12px 15px;display:flex;justify-content:space-between;align-items:center}.icons{display:flex;gap:10px}.icon{cursor:pointer;font-size:14px;color:#777}.card{border:1px solid #ddd;border-radius:10px;padding:20px;background:#fff}.category{border-bottom:1px solid #eee;padding:15px 0}.category:last-child{border-bottom:none}.category-header{display:flex;justify-content:space-between;font-weight:600;margin-bottom:10px}.tags{display:flex;flex-wrap:wrap;gap:10px}.tag{background:#f2f4f7;padding:6px 12px;border-radius:6px;font-size:13px}.footer{text-align:center;margin-top:15px;color:#777;cursor:pointer}.actions{display:flex;justify-content:space-between;margin:50px 0 27px}.text-secondary{white-space:pre-line;font-size:.95rem}.extra-small{font-size:.8rem}.card{transition:transform .2s ease,box-shadow .2s ease}.card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem #0000001a!important}.achievement-section .badge{font-size:.75rem;white-space:normal;text-align:left}.skill-tag .badge{font-weight:500;font-size:.9rem;transition:all .2s ease;cursor:default}.skill-tag .badge:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.skill-tag .btn-close{opacity:.5}.skill-tag .btn-close:hover{opacity:1}.bg-light.text-dark.border{background-color:#f8f9fa!important;border-color:#dee2e6!important}.card{transition:all .3s cubic-bezier(.25,.8,.25,1)}.card:hover{box-shadow:0 10px 20px #0000001a!important}.card:hover .bg-light{background-color:#e8f5e9!important}.position-fixed .card:hover{transform:none!important}code{background-color:#f8f9fa;padding:2px 6px;border-radius:4px}.list-group-item{transition:all .2s ease-in-out}.list-group-item:hover{background-color:#fcfcfc;transform:translate(5px);box-shadow:-5px 0 15px #0000000d}.border-dashed{border-style:dashed!important}.tool-card{transition:all .2s ease-in-out;border-radius:12px}.tool-card:hover{transform:translateY(-5px);border-color:var(--bs-primary)!important;box-shadow:0 10px 15px #0000001a!important}.tool-card:hover .icon-box{background-color:var(--bs-primary-subtle)!important}.tool-card .icon-box{width:60px;height:60px;transition:background-color .2s ease}.border-dashed{border:2px dashed #dee2e6!important}.border-dashed:hover{border-color:var(--bs-primary)!important;background-color:#fff!important}.popup{position:fixed;top:20%;right:20px;width:320px;background:#fff;border-radius:10px;padding:16px;box-shadow:0 4px 12px #0003}.status-row{display:flex;justify-content:space-between;margin:10px 0}.success{color:#2e7d32;font-weight:700}.error{color:#d32f2f}.pending{color:#f9a825}.section-flag{font-weight:600}button.btn.btn-sm.btn-outline-primary{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:10px}button.btn.btn-sm.btn-outline-primary:hover{background-color:#356895;border-color:#356895;color:#fff}button.btn.btn-sm.btn-outline-danger{border-color:#dc3545;color:#dc3545;border-radius:10px}button.btn.btn-sm.btn-outline-danger:hover{background-color:#bb2d3b;border-color:#bb2d3b;color:#fff}button.btn.btn-sm.btn-outline-primary.rounded-circle{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:50%!important}button.action-icon-btn{min-width:22px;width:22px;height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:0!important;background:transparent!important;border:none!important;box-shadow:none!important;line-height:1}button.action-icon-btn.btn-outline-primary{color:#4077ad!important;border-color:#4077ad!important}button.action-icon-btn.btn-outline-danger{color:#dc3545!important}.action-icon-image{width:20px;height:20px;object-fit:contain;display:inline-block}.action-icon-image.edit-icon{filter:brightness(0) saturate(100%) invert(41%) sepia(39%) saturate(774%) hue-rotate(170deg) brightness(91%) contrast(89%)}.action-icon-image.delete-icon{filter:brightness(0) saturate(100%) invert(24%) sepia(79%) saturate(4008%) hue-rotate(344deg) brightness(91%) contrast(90%)}.modal-overlay{position:fixed;inset:0;background:#11182773;display:flex;align-items:center;justify-content:center;z-index:2500;padding:20px}.status-modal-card{width:min(760px,96vw);max-height:85vh;overflow:auto;background:#fff;border-radius:16px;box-shadow:0 20px 40px #0f172a40;border:1px solid #e5e7eb}.status-modal-header{padding:20px 24px 14px;border-bottom:1px solid #eef2f7}.status-modal-body{padding:12px 20px 20px}.status-modal-footer{padding:12px 20px 20px;border-top:1px solid #eef2f7;display:flex;justify-content:end;align-items:center;gap:12px}.status-modal-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 10px;border-bottom:1px solid #f1f5f9}.status-modal-row:last-child{border-bottom:0}.status-modal-name{font-weight:600;color:#1f2937}.status-modal-state{font-size:.92rem;font-weight:600}.status-modal-state.pending{color:#a16207}.status-modal-state.success{color:#166534}.status-modal-state.partial{color:#b45309}.status-modal-state.error{color:#b91c1c}.confirm-modal-card{width:min(520px,96vw);background:#fff;border-radius:14px;box-shadow:0 20px 35px #0f172a38;border:1px solid #e5e7eb;padding:24px}.year-experience-select{max-height:150px}\n"], dependencies: [{ kind: "directive", type: i11.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i8.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i8.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i8.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i8.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i8.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i8.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i8.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i8.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i8.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i8.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i8.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
30874
31586
|
}
|
|
30875
31587
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PreviewComponent, decorators: [{
|
|
30876
31588
|
type: Component,
|
|
30877
|
-
args: [{ selector: 'app-preview', standalone: false, template: "\r\n<div class=\"preview-page-header\">\r\n <h2 class=\"preview-title\">Confirm based on your resume</h2>\r\n <p class=\"preview-subtitle\">Please confirm everything is accurate. It is based on your resume</p>\r\n</div>\r\n\r\n\r\n<div class=\"container py-4\">\r\n <div class=\"section mb-5\">\r\n <div *ngIf=\"details() as data; else loading\">\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"!isEditMode()\">\r\n <div class=\"card-body p-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-4\">\r\n <div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h2 class=\"fw-bold mb-1\">{{ data.firstName }} {{ data.lastName }}</h2>\r\n <span *ngIf=\"basicSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <p class=\"text-muted\">{{ data.jobTitle }} \u2022 {{ data.yearsOfExperience }} Years Exp.</p>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <span class=\"badge bg-primary-subtle text-primary border p-2\">ID: Verified</span>\r\n </div>\r\n\r\n <div class=\"row g-4\">\r\n <div class=\"col-12 border-bottom pb-2\">\r\n <h6 class=\"text-uppercase small fw-bold text-muted\">Summary</h6>\r\n <p class=\"text-secondary small mb-0\">{{ data.summary }}</p>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">EMAIL</label>\r\n <span class=\"fw-bold small\">{{ data.email }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">PHONE</label>\r\n <span class=\"fw-bold small\">{{ data.phone }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">LOCATION</label>\r\n <span class=\"small\">{{ data.address }},{{ data.city }},{{ data.state }},{{ data.zipCode }}, {{\r\n data.country }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-light text-end\">\r\n <button class=\"btn btn-sm btn-primary px-4\" (click)=\"toggleEdit()\">Edit Details</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"isEditMode()\">\r\n <div class=\"card-header bg-white fw-bold\">Update Profile</div>\r\n <div class=\"card-body p-4\">\r\n <form class=\"row g-3\" #basicForm=\"ngForm\" novalidate>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">First Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.firstName\"\r\n name=\"fName\"\r\n placeholder=\"First Name\"\r\n required\r\n #fName=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"fName.invalid && (fName.dirty || fName.touched)\">First name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Last Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.lastName\"\r\n name=\"lName\"\r\n placeholder=\"Last Name\"\r\n required\r\n #lName=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"lName.invalid && (lName.dirty || lName.touched)\">Last name is required</div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Summary</label>\r\n <textarea class=\"form-control\" rows=\"3\" [(ngModel)]=\"tempProfile.summary\" name=\"sum\"\r\n placeholder=\"Summary\"></textarea>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Email <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"email\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.email\"\r\n name=\"email\"\r\n placeholder=\"Email\"\r\n required\r\n email\r\n #email=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"email.invalid && (email.dirty || email.touched)\">\r\n <span *ngIf=\"email.errors?.['required']\">Email is required</span>\r\n <span *ngIf=\"email.errors?.['email']\">Email format is invalid</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Phone Number <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"tempProfile.phone\"\r\n (ngModelChange)=\"tempProfile.phone = sanitizePhone($event)\"\r\n name=\"phone\"\r\n placeholder=\"Phone (10 digits)\"\r\n required\r\n pattern=\"^\\d{10}$\"\r\n maxlength=\"10\"\r\n inputmode=\"numeric\"\r\n #phone=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"phone.invalid && (phone.dirty || phone.touched)\">\r\n <span *ngIf=\"phone.errors?.['required']\">Phone number is required</span>\r\n <span *ngIf=\"phone.errors?.['pattern']\">Phone number must be 10 digits</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-3\">\r\n <label class=\"small text-muted d-block\">Home Address <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.address\"\r\n name=\"address\"\r\n placeholder=\"Home Address\"\r\n required\r\n #address=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"address.invalid && (address.dirty || address.touched)\">Home address is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">City <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.city\"\r\n name=\"city\"\r\n placeholder=\"City\"\r\n required\r\n #city=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"city.invalid && (city.dirty || city.touched)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">State <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.state\"\r\n name=\"state\"\r\n placeholder=\"State\"\r\n required\r\n #state=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"state.invalid && (state.dirty || state.touched)\">State is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Zip Code <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.zipCode\"\r\n name=\"zipCode\"\r\n placeholder=\"Zip Code\"\r\n required\r\n #zipCode=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"zipCode.invalid && (zipCode.dirty || zipCode.touched)\">Zip code is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Country <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.country\"\r\n name=\"country\"\r\n placeholder=\"Country\"\r\n required\r\n #country=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"country.invalid && (country.dirty || country.touched)\">Country is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Job Title <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.jobTitle\"\r\n name=\"jobTitle\"\r\n placeholder=\"Job Title\"\r\n required\r\n #jobTitle=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"jobTitle.invalid && (jobTitle.dirty || jobTitle.touched)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [(ngModel)]=\"tempProfile.yearsOfExperience\"\r\n name=\"yearsOfExperience\"\r\n placeholder=\"Years of Experience\"\r\n required\r\n min=\"0\"\r\n #yearsOfExperience=\"ngModel\"\r\n >\r\n <div class=\"small text-danger mt-1\" *ngIf=\"yearsOfExperience.invalid && (yearsOfExperience.dirty || yearsOfExperience.touched)\">\r\n Years of experience is required\r\n </div>\r\n </div>\r\n\r\n\r\n </form>\r\n </div>\r\n <div class=\"card-footer bg-light text-end gap-2 d-flex justify-content-end\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancel()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" (click)=\"save()\">Save Changes</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Work Experience</h5>\r\n <span *ngIf=\"workSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add work experience\"\r\n (click)=\"addJob()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"experience().length === 0 && !isAddingJob()\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one work experience.</div>\r\n </div>\r\n <div class=\"list-group list-group-flush shadow-sm rounded\">\r\n <div *ngIf=\"isAddingJob() && tempJob()\" class=\"list-group-item p-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\"\r\n name=\"newJobTitle\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\"\r\n name=\"newCompany\"\r\n />\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\"\r\n name=\"newCity\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\"\r\n name=\"newStartDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\"\r\n (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"newEndDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\"\r\n name=\"newIsCurrent\"\r\n id=\"newIsCurrent\"\r\n />\r\n <label class=\"form-check-label\" for=\"newIsCurrent\">Current</label>\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\"\r\n name=\"newResponsibilities\"\r\n ></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\"\r\n name=\"newWorkAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"card-footer bg-light text-end mt-3\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\" (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let job of experience(); let i = index\" class=\"list-group-item p-3\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <div class=\"d-flex align-items-center cursor-pointer flex-grow-1\" (click)=\"toggleJob(i)\">\r\n <i class=\"bi bi-briefcase text-primary me-3 fs-4\"></i>\r\n <div>\r\n <h6 class=\"mb-0 fw-bold\">{{ job.jobTitle }}</h6>\r\n <small class=\"text-muted\">\r\n {{ job.company }} \u2022 {{ formatMonthYear(job.startDate) }} - {{ job.isCurrent ? 'Present' : formatMonthYear(job.endDate) }}\r\n </small>\r\n <div\r\n *ngIf=\"editingJobIndex() !== i && (workIssuesByIndex()[i] || []).length > 0\"\r\n class=\"alert alert-warning py-1 px-2 mt-2\"\r\n >\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center gap-2\">\r\n \r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditJob(i)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteJob(i);\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n \r\n\r\n <!-- <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-light p-0\"\r\n style=\"width: 34px; height: 34px;\"\r\n (click)=\"toggleJob(i)\"\r\n title=\"Expand\"\r\n >\r\n <i class=\"bi cursor-pointer\" [ngClass]=\"expandedIndex() === i ? 'bi-chevron-up' : 'bi-chevron-down'\"></i>\r\n </button> -->\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3 bg-light p-3 rounded small\" *ngIf=\"expandedIndex() === i\">\r\n <ng-container *ngIf=\"editingJobIndex() !== i; else editJobForm\">\r\n <ul class=\"mb-0\">\r\n <li *ngFor=\"let res of job.responsibilities\">{{ res }}</li>\r\n </ul>\r\n </ng-container>\r\n\r\n <ng-template #editJobForm>\r\n <div *ngIf=\"(workIssuesByIndex()[i] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\"\r\n name=\"jobTitle{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.jobTitle)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\"\r\n name=\"company{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.company)\">Company name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\"\r\n name=\"city{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\"\r\n name=\"startDate{{ i }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\"\r\n (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"endDate{{ i }}\"\r\n />\r\n <div\r\n class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\"\r\n >\r\n End date is required\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\"\r\n name=\"isCurrent{{ i }}\"\r\n id=\"isCurrent{{ i }}\"\r\n />\r\n <label class=\"form-check-label\" for=\"isCurrent{{ i }}\">Current</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\"\r\n name=\"responsibilities{{ i }}\"\r\n ></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\"\r\n name=\"workAttachment{{ i }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n </ng-template>\r\n </div>\r\n\r\n <div class=\"card-footer bg-light text-end\" *ngIf=\"editingJobIndex() === i\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\" (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div> \r\n\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Certifications</h5>\r\n <span *ngIf=\"certificationsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add certification\"\r\n (click)=\"addCertification()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"certs().length === 0 && !isAddingCertification() && editingCertificationIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one certification.</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingCertification() && tempCertification()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\"\r\n name=\"newCertName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"newCertOrg\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\"\r\n name=\"newCertState\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"newCertCredentialId\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\"\r\n name=\"newCertIssueDate\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\"\r\n name=\"newCertExpiryDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\"\r\n name=\"newCertAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\" (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : 'Save' }}\r\n </button> </div>\r\n </div>\r\n\r\n <div *ngFor=\"let cert of certs(); let ci = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingCertificationIndex() !== ci; else editCert\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ cert.name }}</div>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ cert.issuingOrganization || '\u2014' }}\r\n <span *ngIf=\"cert.state\"> \u2022 {{ cert.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ cert.issueDate ? formatMonthYear(cert.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ cert.expiryDate ? formatMonthYear(cert.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditCertification(ci)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteCertification(ci)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editCert>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\"\r\n name=\"certName{{ ci }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"certOrg{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\"\r\n name=\"certState{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"certCredentialId{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\"\r\n name=\"certIssue{{ ci }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\"\r\n name=\"certExpiry{{ ci }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\"\r\n name=\"certAttachment{{ ci }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\" (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : 'Save' }}\r\n </button> </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Licenses</h5>\r\n <span *ngIf=\"licensesSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add license\"\r\n (click)=\"addLicense()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"licenses().length === 0 && !isAddingLicense() && editingLicenseIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one license.</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingLicense() && tempLicense()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\"\r\n name=\"newLicName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\"\r\n name=\"newLicAuthority\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\"\r\n name=\"newLicNumber\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\"\r\n name=\"newLicState\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\"\r\n name=\"newLicIssueDate\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\"\r\n name=\"newLicExpiryDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\"\r\n name=\"newLicenseAttachment\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\" (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let lic of licenses(); let li = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingLicenseIndex() !== li; else editLic\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ lic.name }}</div>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ lic.issuingAuthority || '\u2014' }}\r\n <span *ngIf=\"lic.state\"> \u2022 {{ lic.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ lic.issueDate ? formatMonthYear(lic.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ lic.expiryDate ? formatMonthYear(lic.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditLicense(li)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteLicense(li)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editLic>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\"\r\n name=\"licName{{ li }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\"\r\n name=\"licAuthority{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\"\r\n name=\"licNumber{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\"\r\n name=\"licState{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\"\r\n name=\"licIssue{{ li }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\"\r\n name=\"licExpiry{{ li }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input\r\n type=\"file\"\r\n accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\"\r\n class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\"\r\n name=\"licenseAttachment{{ li }}\"\r\n />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\" (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\" (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : 'Save' }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Tools</h5>\r\n <span *ngIf=\"toolsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add tool\"\r\n (click)=\"addTool()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let tool of tools(); let ti = index\" class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openToolEditor(ti)\"\r\n title=\"Edit\"\r\n >\r\n <span class=\"fw-normal text-dark\">{{ tool }}</span>\r\n <span\r\n *ngIf=\"(toolIssuesByIndex()[ti] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\"\r\n >\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Delete tool\"\r\n (click)=\"deleteTool(ti)\"\r\n >\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"tools().length === 0\" class=\"text-muted small\">No tools added.</span>\r\n </div>\r\n\r\n <!-- Tool edit panel (overlay) -->\r\n <div\r\n *ngIf=\"isToolEditorOpen()\"\r\n class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\"\r\n (click)=\"closeToolEditor()\"\r\n >\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeToolEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingTool() ? 'Add Tool' : ('Edit ' + (toolForm()?.name || '') + ' Tool') }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"toolFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ toolFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Tool Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.name\"\r\n (ngModelChange)=\"patchToolForm({ name: $event })\"\r\n name=\"toolName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(toolForm()?.name)\">Tool name is required</div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Service Provider</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.providerName\"\r\n (ngModelChange)=\"patchToolForm({ providerName: $event })\"\r\n name=\"toolProvider\"\r\n placeholder=\"Service Provider\"\r\n />\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n *ngFor=\"let s of [1,2,3,4,5]\"\r\n type=\"button\"\r\n class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempToolStars(s)\"\r\n [attr.aria-label]=\"'Set rating ' + s\"\r\n >\r\n <span [class]=\"(toolForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (toolForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ toolForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(toolForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.year\"\r\n (ngModelChange)=\"patchToolForm({ year: $event === '' ? null : +$event })\"\r\n name=\"toolYear\"\r\n min=\"1\"\r\n max=\"30\"\r\n placeholder=\"Years of Experience\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"toolForm()?.year === null || toolForm()?.year === undefined\">Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"toolForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchToolForm({ profileVisibility: $event })\"\r\n name=\"toolVisible\"\r\n id=\"toolVisible\"\r\n />\r\n <label class=\"form-check-label\" for=\"toolVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control\"\r\n [ngModel]=\"toolForm()?.notes\"\r\n (ngModelChange)=\"patchToolForm({ notes: $event })\"\r\n name=\"toolNotes\"\r\n placeholder=\"Comment your tool here...\"\r\n ></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button\r\n *ngIf=\"!isAddingTool() && editingToolIndex() !== null\"\r\n type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\"\r\n (click)=\"deleteTool(editingToolIndex()!)\"\r\n >\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeToolEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" (click)=\"saveToolEditor()\">\r\n {{ isAddingTool() ? 'Save' : 'Update' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Skills</h5>\r\n <span *ngIf=\"skillsSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add skill\"\r\n (click)=\"addSkill()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let skill of skills(); let si = index\" class=\"d-flex align-items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openSkillEditor(si)\"\r\n title=\"Edit\"\r\n >\r\n <span class=\"fw-normal text-dark\">{{ skill }}</span>\r\n <span\r\n *ngIf=\"(skillIssuesByIndex()[si] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\"\r\n >\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Delete skill\"\r\n (click)=\"deleteSkill(si); $event.stopPropagation()\"\r\n >\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"skills().length === 0\" class=\"text-muted small\">No skills added.</span>\r\n </div>\r\n\r\n <!-- Skill edit panel (overlay) -->\r\n <div\r\n *ngIf=\"isSkillEditorOpen()\"\r\n class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\"\r\n (click)=\"closeSkillEditor()\"\r\n >\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeSkillEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingSkill() ? 'Add Skill' : ('Edit ' + (skillForm()?.name || '') + ' Skill') }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"skillFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ skillFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Skills Name <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.name\"\r\n (ngModelChange)=\"patchSkillForm({ name: $event })\"\r\n name=\"skillName\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(skillForm()?.name)\">Skillset name is required</div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Service Provider</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.providerName\"\r\n (ngModelChange)=\"patchSkillForm({ providerName: $event })\"\r\n name=\"skillProvider\"\r\n placeholder=\"Service Provider\"\r\n />\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button\r\n *ngFor=\"let s of [1,2,3,4,5]\"\r\n type=\"button\"\r\n class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempSkillStars(s)\"\r\n [attr.aria-label]=\"'Set rating ' + s\"\r\n >\r\n <span [class]=\"(skillForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (skillForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ skillForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(skillForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"number\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.year\"\r\n (ngModelChange)=\"patchSkillForm({ year: $event === '' ? null : +$event })\"\r\n name=\"skillYear\"\r\n min=\"1\"\r\n max=\"30\"\r\n placeholder=\"Years of Experience\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"skillForm()?.year === null || skillForm()?.year === undefined\">Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [ngModel]=\"skillForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchSkillForm({ profileVisibility: $event })\"\r\n name=\"skillVisible\"\r\n id=\"skillVisible\"\r\n />\r\n <label class=\"form-check-label\" for=\"skillVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea\r\n rows=\"4\"\r\n class=\"form-control\"\r\n [ngModel]=\"skillForm()?.notes\"\r\n (ngModelChange)=\"patchSkillForm({ notes: $event })\"\r\n name=\"skillNotes\"\r\n placeholder=\"Comment your skill here...\"\r\n ></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button\r\n *ngIf=\"!isAddingSkill() && editingSkillIndex() !== null\"\r\n type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\"\r\n (click)=\"deleteSkill(editingSkillIndex()!)\"\r\n >\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeSkillEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" (click)=\"saveSkillEditor()\">\r\n {{ isAddingSkill() ? 'Save' : 'Update' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Education</h5>\r\n <span *ngIf=\"educationSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\"\r\n title=\"Add education\"\r\n (click)=\"addEducation()\"\r\n >\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0 && !isAddingEducation() && editingEducationIndex() === null\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">Add at least one education.</div>\r\n </div>\r\n\r\n <div *ngIf=\"isAddingEducation() && tempEducation()\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\"\r\n name=\"newEduDegree\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\"\r\n name=\"newEduInstitution\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\"\r\n name=\"newEduDegreeType\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\"\r\n name=\"newEduStartDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\"\r\n name=\"newEduEndDate\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" (click)=\"saveEducation()\">Save</button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let edu of educationList(); let ei = index\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <div class=\"d-flex justify-content-between align-items-start\">\r\n <div>\r\n <h6 class=\"fw-bold mb-1\">{{ edu.degree }}</h6>\r\n <p class=\"small text-primary mb-0\">{{ edu.institution }}</p>\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\" *ngIf=\"editingEducationIndex() !== ei\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditEducation(ei)\"\r\n title=\"Edit\"\r\n >\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteEducation(ei)\"\r\n title=\"Delete\"\r\n >\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"editingEducationIndex() === ei\" class=\"mt-3\">\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\"\r\n name=\"eduDegree{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\"\r\n name=\"eduInstitution{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.institution)\">Institution name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\"\r\n name=\"eduDegreeType{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">City</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.city\"\r\n (ngModelChange)=\"patchTempEducation({ city: $event })\"\r\n name=\"eduCity{{ ei }}\"\r\n />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\"\r\n name=\"eduStartDate{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input\r\n type=\"month\"\r\n class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\"\r\n name=\"eduEndDate{{ ei }}\"\r\n />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" (click)=\"saveEducation()\">Save</button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0\" class=\"text-muted small\">No education added.</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex gap-2 justify-content-center mt-5\">\r\n <button class=\"btn btn-outline-secondary px-5\" (click)=\"onBackClick()\">Back</button>\r\n <button\r\n class=\"btn btn-primary px-5 shadow\"\r\n [disabled]=\"!canConfirmAndContinue()\"\r\n [title]=\"canConfirmAndContinue() ? '' : 'Please fill all required fields in all mandatory sections.'\"\r\n (click)=\"saveResumedetails()\"\r\n >\r\n Confirm All & Continue\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<ng-template #loading>\r\n <div class=\"text-center p-5\">\r\n <div class=\"spinner-border text-primary\"></div>\r\n <p class=\"text-muted mt-2\">Loading data...</p>\r\n </div>\r\n</ng-template>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showPopup\" >\r\n <div class=\"status-modal-card\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"status-modal-header\">\r\n <h3 class=\"mb-1\">Saving Resume Details</h3>\r\n <p class=\"text-muted mb-0\">Please wait while we process each section.</p>\r\n </div>\r\n\r\n <div class=\"status-modal-body\">\r\n <div class=\"status-modal-row\" *ngFor=\"let item of statusList\">\r\n <div class=\"status-modal-name\">{{ item.name }}</div>\r\n\r\n <div class=\"status-modal-state pending\" *ngIf=\"item.status === 'pending'\">\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span> In progress\r\n </div>\r\n\r\n <div class=\"status-modal-state success\" *ngIf=\"item.status === 'success'\">\r\n <span class=\"me-1\">\u2713</span> Saved\r\n </div>\r\n\r\n <div class=\"status-modal-state partial\" *ngIf=\"item.status === 'partial'\">\r\n <span class=\"me-1\">!</span> {{ item.successCount }} Success / {{ item.failCount }} Failed\r\n </div>\r\n\r\n <div class=\"status-modal-state error\" *ngIf=\"item.status === 'error'\">\r\n <span class=\"me-1\">\u2716</span> {{ item.error || 'Failed' }}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"status-modal-footer\">\r\n <!-- <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeSavePopup()\">Back to Preview</button> -->\r\n <div class=\"d-flex justify-content-end\">\r\n <button\r\n [disabled]=\"showLoader\" [ng2-loading]=\"showLoader\"\r\n *ngIf=\"allSaveStepsSucceeded()\"\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"goToDashboard()\"\r\n >\r\n Go to Dashboard\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showBackConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">Leave this page?</h4>\r\n <p class=\"text-muted mb-4\">Your data might be lost if you go back now.</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"stayOnPreview()\">Stay</button>\r\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"proceedBack()\">Proceed Back</button>\r\n </div>\r\n </div>\r\n</div>", styles: [".container{max-width:900px;margin:30px auto;font-family:Arial,sans-serif;color:#333}.preview-page-header{max-width:900px;margin:50px auto 10px;padding:0 12px}.preview-title{font-weight:600;margin:0}.preview-subtitle{margin-top:7px;font-size:16px}.section{margin-bottom:25px}.section-title{font-weight:600;display:block;margin-bottom:8px}.section-title .sub{font-weight:400;color:#777;font-size:13px}.dropdown-box{border:1px solid #ddd;border-radius:8px;padding:12px 15px;display:flex;justify-content:space-between;align-items:center}.icons{display:flex;gap:10px}.icon{cursor:pointer;font-size:14px;color:#777}.card{border:1px solid #ddd;border-radius:10px;padding:20px;background:#fff}.category{border-bottom:1px solid #eee;padding:15px 0}.category:last-child{border-bottom:none}.category-header{display:flex;justify-content:space-between;font-weight:600;margin-bottom:10px}.tags{display:flex;flex-wrap:wrap;gap:10px}.tag{background:#f2f4f7;padding:6px 12px;border-radius:6px;font-size:13px}.footer{text-align:center;margin-top:15px;color:#777;cursor:pointer}.actions{display:flex;justify-content:space-between;margin:50px 0 27px}.text-secondary{white-space:pre-line;font-size:.95rem}.extra-small{font-size:.8rem}.card{transition:transform .2s ease,box-shadow .2s ease}.card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem #0000001a!important}.achievement-section .badge{font-size:.75rem;white-space:normal;text-align:left}.skill-tag .badge{font-weight:500;font-size:.9rem;transition:all .2s ease;cursor:default}.skill-tag .badge:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.skill-tag .btn-close{opacity:.5}.skill-tag .btn-close:hover{opacity:1}.bg-light.text-dark.border{background-color:#f8f9fa!important;border-color:#dee2e6!important}.card{transition:all .3s cubic-bezier(.25,.8,.25,1)}.card:hover{box-shadow:0 10px 20px #0000001a!important}.card:hover .bg-light{background-color:#e8f5e9!important}.position-fixed .card:hover{transform:none!important}code{background-color:#f8f9fa;padding:2px 6px;border-radius:4px}.list-group-item{transition:all .2s ease-in-out}.list-group-item:hover{background-color:#fcfcfc;transform:translate(5px);box-shadow:-5px 0 15px #0000000d}.border-dashed{border-style:dashed!important}.tool-card{transition:all .2s ease-in-out;border-radius:12px}.tool-card:hover{transform:translateY(-5px);border-color:var(--bs-primary)!important;box-shadow:0 10px 15px #0000001a!important}.tool-card:hover .icon-box{background-color:var(--bs-primary-subtle)!important}.tool-card .icon-box{width:60px;height:60px;transition:background-color .2s ease}.border-dashed{border:2px dashed #dee2e6!important}.border-dashed:hover{border-color:var(--bs-primary)!important;background-color:#fff!important}.popup{position:fixed;top:20%;right:20px;width:320px;background:#fff;border-radius:10px;padding:16px;box-shadow:0 4px 12px #0003}.status-row{display:flex;justify-content:space-between;margin:10px 0}.success{color:#2e7d32;font-weight:700}.error{color:#d32f2f}.pending{color:#f9a825}.section-flag{font-weight:600}button.btn.btn-sm.btn-outline-primary{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:10px}button.btn.btn-sm.btn-outline-primary:hover{background-color:#356895;border-color:#356895;color:#fff}button.btn.btn-sm.btn-outline-danger{border-color:#dc3545;color:#dc3545;border-radius:10px}button.btn.btn-sm.btn-outline-danger:hover{background-color:#bb2d3b;border-color:#bb2d3b;color:#fff}button.btn.btn-sm.btn-outline-primary.rounded-circle{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:50%!important}button.action-icon-btn{min-width:22px;width:22px;height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:0!important;background:transparent!important;border:none!important;box-shadow:none!important;line-height:1}button.action-icon-btn.btn-outline-primary{color:#4077ad!important;border-color:#4077ad!important}button.action-icon-btn.btn-outline-danger{color:#dc3545!important}.action-icon-image{width:20px;height:20px;object-fit:contain;display:inline-block}.action-icon-image.edit-icon{filter:brightness(0) saturate(100%) invert(41%) sepia(39%) saturate(774%) hue-rotate(170deg) brightness(91%) contrast(89%)}.action-icon-image.delete-icon{filter:brightness(0) saturate(100%) invert(24%) sepia(79%) saturate(4008%) hue-rotate(344deg) brightness(91%) contrast(90%)}.modal-overlay{position:fixed;inset:0;background:#11182773;display:flex;align-items:center;justify-content:center;z-index:2500;padding:20px}.status-modal-card{width:min(760px,96vw);max-height:85vh;overflow:auto;background:#fff;border-radius:16px;box-shadow:0 20px 40px #0f172a40;border:1px solid #e5e7eb}.status-modal-header{padding:20px 24px 14px;border-bottom:1px solid #eef2f7}.status-modal-body{padding:12px 20px 20px}.status-modal-footer{padding:12px 20px 20px;border-top:1px solid #eef2f7;display:flex;justify-content:end;align-items:center;gap:12px}.status-modal-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 10px;border-bottom:1px solid #f1f5f9}.status-modal-row:last-child{border-bottom:0}.status-modal-name{font-weight:600;color:#1f2937}.status-modal-state{font-size:.92rem;font-weight:600}.status-modal-state.pending{color:#a16207}.status-modal-state.success{color:#166534}.status-modal-state.partial{color:#b45309}.status-modal-state.error{color:#b91c1c}.confirm-modal-card{width:min(520px,96vw);background:#fff;border-radius:14px;box-shadow:0 20px 35px #0f172a38;border:1px solid #e5e7eb;padding:24px}.year-experience-select{max-height:150px}\n"] }]
|
|
31589
|
+
args: [{ selector: 'app-preview', standalone: false, template: "<div class=\"preview-page-header\">\r\n <h2 class=\"preview-title\">Confirm based on your resume</h2>\r\n <p class=\"preview-subtitle\">Please confirm everything is accurate. It is based on your resume</p>\r\n</div>\r\n\r\n\r\n<div class=\"container py-4\">\r\n <div class=\"section mb-5\">\r\n <div *ngIf=\"details() as data; else loading\">\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"!isEditMode()\">\r\n <div class=\"card-body p-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-4\">\r\n <div>\r\n <div class=\"d-flex align-items-center gap-2 flex-wrap\">\r\n <h2 class=\"fw-bold mb-1\">{{ data.firstName }} {{ data.lastName }}</h2>\r\n <span *ngIf=\"basicSectionHasIssues()\" class=\"badge bg-warning-subtle text-warning border section-flag\">\r\n Missing info\r\n </span>\r\n <span class=\"badge border section-flag\"\r\n [ngClass]=\"basicDetailsSaved ? 'bg-success-subtle text-success' : 'bg-warning-subtle text-warning'\">\r\n {{ basicDetailsSaved ? 'Saved' : 'Not saved yet' }}\r\n </span>\r\n </div>\r\n <p class=\"text-muted\">{{ data.jobTitle }} \u2022 {{ data.yearsOfExperience }} Years Exp.</p>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <span class=\"badge bg-primary-subtle text-primary border p-2\">ID: Verified</span>\r\n </div>\r\n\r\n <div class=\"row g-4\">\r\n <div class=\"col-12 border-bottom pb-2\">\r\n <h6 class=\"text-uppercase small fw-bold text-muted\">Summary</h6>\r\n <p class=\"text-secondary small mb-0\">{{ data.summary }}</p>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">EMAIL</label>\r\n <span class=\"fw-bold small\">{{ data.email }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">PHONE</label>\r\n <span class=\"fw-bold small\">{{ data.phone }}</span>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">LOCATION</label>\r\n <span class=\"small\">{{ data.address }},{{ data.city }},{{ data.state }},{{ data.zipCode }}, {{\r\n data.country }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-light d-flex align-items-center justify-content-between\">\r\n <span *ngIf=\"!basicDetailsSaved\" class=\"small text-warning fw-semibold\">\r\n <i class=\"bi bi-exclamation-circle me-1\"></i> Click \"Edit Details\" to review and save your basic info.\r\n </span>\r\n <span *ngIf=\"basicDetailsSaved\" class=\"small text-success fw-semibold\">\r\n <i class=\"bi bi-check-circle me-1\"></i> Basic details saved.\r\n </span>\r\n <button class=\"btn btn-sm btn-primary px-4 ms-auto\" (click)=\"toggleEdit()\">Edit Details</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card shadow-sm border-0\" *ngIf=\"isEditMode()\">\r\n <div class=\"card-header bg-white fw-bold\">Update Profile</div>\r\n <div class=\"card-body p-4\">\r\n <form class=\"row g-3\" #basicForm=\"ngForm\" novalidate>\r\n <div *ngIf=\"basicIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ basicIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">First Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.firstName\" name=\"fName\"\r\n placeholder=\"First Name\" required #fName=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"fName.invalid && (fName.dirty || fName.touched)\">First name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Last Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.lastName\" name=\"lName\"\r\n placeholder=\"Last Name\" required #lName=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"lName.invalid && (lName.dirty || lName.touched)\">Last name is\r\n required</div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Summary</label>\r\n <textarea class=\"form-control\" rows=\"3\" [(ngModel)]=\"tempProfile.summary\" name=\"sum\"\r\n placeholder=\"Summary\"></textarea>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Email <span class=\"text-danger\">*</span></label>\r\n <input type=\"email\" class=\"form-control\" [(ngModel)]=\"tempProfile.email\" name=\"email\" placeholder=\"Email\"\r\n required email #email=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"email.invalid && (email.dirty || email.touched)\">\r\n <span *ngIf=\"email.errors?.['required']\">Email is required</span>\r\n <span *ngIf=\"email.errors?.['email']\">Email format is invalid</span>\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Phone Number <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"tempProfile.phone\"\r\n (ngModelChange)=\"tempProfile.phone = sanitizePhone($event)\" name=\"phone\" placeholder=\"Phone (10 digits)\"\r\n required pattern=\"^\\d{10}$\" maxlength=\"10\" inputmode=\"numeric\" #phone=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"phone.invalid && (phone.dirty || phone.touched)\">\r\n <span *ngIf=\"phone.errors?.['required']\">Phone number is required</span>\r\n <span *ngIf=\"phone.errors?.['pattern']\">Phone number must be 10 digits</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-3\">\r\n <label class=\"small text-muted d-block\">Home Address <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.address\" name=\"address\"\r\n placeholder=\"Home Address\" required #address=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"address.invalid && (address.dirty || address.touched)\">Home\r\n address is required</div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">City <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.city\" name=\"city\" placeholder=\"City\"\r\n required #city=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"city.invalid && (city.dirty || city.touched)\">City is required\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">State <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.state\" name=\"state\" placeholder=\"State\"\r\n required #state=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"state.invalid && (state.dirty || state.touched)\">State is\r\n required</div>\r\n </div>\r\n\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Zip Code <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"tempProfile.zipCode\"\r\n (ngModelChange)=\"tempProfile.zipCode = sanitizeZipCode($event)\" name=\"zipCode\" placeholder=\"Zip Code\"\r\n required pattern=\"^\\d{1,6}$\" maxlength=\"6\" inputmode=\"numeric\" #zipCode=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"zipCode.invalid && (zipCode.dirty || zipCode.touched)\">Zip code\r\n is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"zipCode.errors?.['pattern'] && (zipCode.dirty || zipCode.touched)\">\r\n Zip code must be up to 6 digits\r\n </div>\r\n </div>\r\n <div class=\"col-md-2\">\r\n <label class=\"small text-muted d-block\">Country <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.country\" name=\"country\"\r\n placeholder=\"Country\" required #country=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"country.invalid && (country.dirty || country.touched)\">Country\r\n is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Job Title <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"tempProfile.jobTitle\" name=\"jobTitle\"\r\n placeholder=\"Job Title\" required #jobTitle=\"ngModel\">\r\n <div class=\"small text-danger mt-1\" *ngIf=\"jobTitle.invalid && (jobTitle.dirty || jobTitle.touched)\">Job\r\n title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [(ngModel)]=\"tempProfile.yearsOfExperience\"\r\n name=\"yearsOfExperience\" placeholder=\"Years of Experience\" required min=\"0\"\r\n #yearsOfExperience=\"ngModel\">\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"yearsOfExperience.invalid && (yearsOfExperience.dirty || yearsOfExperience.touched)\">\r\n Years of experience is required\r\n </div>\r\n </div>\r\n\r\n\r\n </form>\r\n </div>\r\n <div class=\"card-footer bg-light text-end gap-2 d-flex justify-content-end\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancel()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingBasic\" (click)=\"save()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Saving...' : 'Save Changes' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Work Experience</h5>\r\n <span *ngIf=\"experience().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"workSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ workSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add work experience\" (click)=\"addJob()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"experience().length === 0 && !isAddingJob()\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No work experience added</div>\r\n </div>\r\n <div class=\"list-group list-group-flush shadow-sm rounded\">\r\n <div *ngIf=\"isAddingJob() && tempJob()\" class=\"list-group-item p-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\" name=\"newJobTitle\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\" name=\"newCompany\" />\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\" name=\"newCity\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\" name=\"newStartDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\" (ngModelChange)=\"patchTempJob({ endDate: $event })\" name=\"newEndDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">End date is\r\n required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\" name=\"newIsCurrent\" id=\"newIsCurrent\" />\r\n <label class=\"form-check-label\" for=\"newIsCurrent\">Current</label>\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea rows=\"4\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\" name=\"newResponsibilities\"></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\" name=\"newWorkAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"card-footer bg-light text-end mt-3\">\r\n <button class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\"\r\n (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : workActionLabel(editingJobIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n\r\n <div *ngFor=\"let job of experience(); let i = index\" class=\"list-group-item p-3\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <div class=\"d-flex align-items-center cursor-pointer flex-grow-1\" (click)=\"toggleJob(i)\">\r\n <i class=\"bi bi-briefcase text-primary me-3 fs-4\"></i>\r\n <div>\r\n <h6 class=\"mb-0 fw-bold\">{{ job.jobTitle }}</h6>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedWorkItem(i) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedWorkItem(i) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <small class=\"text-muted\">\r\n {{ job.company }} \u2022 {{ formatMonthYear(job.startDate) }} - {{ job.isCurrent ? 'Present' :\r\n formatMonthYear(job.endDate) }}\r\n </small>\r\n <div *ngIf=\"editingJobIndex() !== i && (workIssuesByIndex()[i] || []).length > 0\"\r\n class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\" (click)=\"startEditJob(i)\"\r\n title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteJob(i);\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n\r\n <!-- <button\r\n type=\"button\"\r\n class=\"btn btn-sm btn-light p-0\"\r\n style=\"width: 34px; height: 34px;\"\r\n (click)=\"toggleJob(i)\"\r\n title=\"Expand\"\r\n >\r\n <i class=\"bi cursor-pointer\" [ngClass]=\"expandedIndex() === i ? 'bi-chevron-up' : 'bi-chevron-down'\"></i>\r\n </button> -->\r\n </div>\r\n </div>\r\n\r\n\r\n\r\n <div class=\"mt-3 bg-light p-3 rounded small\" *ngIf=\"expandedIndex() === i\">\r\n <ng-container *ngIf=\"editingJobIndex() !== i; else editJobForm\">\r\n <ul class=\"mb-0\">\r\n <li *ngFor=\"let res of job.responsibilities\">{{ res }}</li>\r\n </ul>\r\n </ng-container>\r\n\r\n <ng-template #editJobForm>\r\n <div *ngIf=\"(workIssuesByIndex()[i] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (workIssuesByIndex()[i] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">JOB TITLE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.jobTitle\"\r\n (ngModelChange)=\"patchTempJob({ jobTitle: $event })\" name=\"jobTitle{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.jobTitle)\">Job title is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">COMPANY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.company\"\r\n (ngModelChange)=\"patchTempJob({ company: $event })\" name=\"company{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.company)\">Company name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">CITY <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.city\"\r\n (ngModelChange)=\"patchTempJob({ city: $event })\" name=\"city{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.city)\">City is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempJob()?.startDate\"\r\n (ngModelChange)=\"patchTempJob({ startDate: $event })\" name=\"startDate{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempJob()?.startDate)\">Start date is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [disabled]=\"tempJob()?.isCurrent\"\r\n [ngModel]=\"tempJob()?.endDate\" (ngModelChange)=\"patchTempJob({ endDate: $event })\"\r\n name=\"endDate{{ i }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"!tempJob()?.isCurrent && isBlank(tempJob()?.endDate)\">\r\n End date is required\r\n </div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"!tempJob()?.isCurrent && isMonthRangeInvalid(tempJob()?.startDate, tempJob()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4 d-flex align-items-end\">\r\n <div class=\"form-check\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"tempJob()?.isCurrent\"\r\n (ngModelChange)=\"setTempJobIsCurrent($event)\" name=\"isCurrent{{ i }}\" id=\"isCurrent{{ i }}\" />\r\n <label class=\"form-check-label\" for=\"isCurrent{{ i }}\">Current</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">RESPONSIBILITIES (one per line)</label>\r\n <textarea rows=\"4\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"(tempJob()?.responsibilities || []).join('\\n')\"\r\n (ngModelChange)=\"updateTempResponsibilities($event)\" name=\"responsibilities{{ i }}\"></textarea>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onWorkExperienceFileSelected($event)\" name=\"workAttachment{{ i }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempJob()?.fileName\">\r\n <span>{{ tempJob()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempJob())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n </ng-template>\r\n </div>\r\n\r\n <div class=\"card-footer bg-light text-end\" *ngIf=\"editingJobIndex() === i\">\r\n <button class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditJob(); $event.stopPropagation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingWork\"\r\n (click)=\"saveEditJob(); $event.stopPropagation()\">\r\n <span *ngIf=\"isSavingWork\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingWork ? 'Uploading...' : workActionLabel(editingJobIndex()) }}\r\n </button>\r\n </div>\r\n\r\n\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Certifications</h5>\r\n <span *ngIf=\"certs().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"certificationSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ certificationSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add certification\" (click)=\"addCertification()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"certs().length === 0 && !isAddingCertification() && editingCertificationIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No certification added</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingCertification() && tempCertification()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\" name=\"newCertName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is required\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\" name=\"newCertOrg\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\" name=\"newCertState\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\" name=\"newCertCredentialId\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\" name=\"newCertIssueDate\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\" name=\"newCertExpiryDate\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\" name=\"newCertAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\"\r\n (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : certificationActionLabel(editingCertificationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let cert of certs(); let ci = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingCertificationIndex() !== ci; else editCert\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ cert.name }}</div>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedCertificationItem(ci) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedCertificationItem(ci) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ cert.issuingOrganization || '\u2014' }}\r\n <span *ngIf=\"cert.state\"> \u2022 {{ cert.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ cert.issueDate ? formatMonthYear(cert.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ cert.expiryDate ? formatMonthYear(cert.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditCertification(ci)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\"\r\n (click)=\"deleteCertification(ci)\" title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editCert>\r\n <div *ngIf=\"(certIssuesByIndex()[ci] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (certIssuesByIndex()[ci] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.name\"\r\n (ngModelChange)=\"patchTempCertification({ name: $event })\" name=\"certName{{ ci }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempCertification()?.name)\">Certificate name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Organization</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.issuingOrganization || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issuingOrganization: $event || null })\"\r\n name=\"certOrg{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.state || ''\"\r\n (ngModelChange)=\"patchTempCertification({ state: $event || null })\" name=\"certState{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Credential ID</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\"\r\n [ngModel]=\"tempCertification()?.credentialId || ''\"\r\n (ngModelChange)=\"patchTempCertification({ credentialId: $event || null })\"\r\n name=\"certCredentialId{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ issueDate: $event || null })\" name=\"certIssue{{ ci }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempCertification()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempCertification({ expiryDate: $event || null })\" name=\"certExpiry{{ ci }}\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempCertification()?.issueDate || null, tempCertification()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">CERTIFICATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onCertificationFileSelected($event)\" name=\"certAttachment{{ ci }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempCertification()?.fileName\">\r\n <span>{{ tempCertification()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempCertification())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditCertification()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingCertification\"\r\n (click)=\"saveCertificationEditor()\">\r\n <span *ngIf=\"isSavingCertification\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingCertification ? 'Uploading...' : certificationActionLabel(editingCertificationIndex()) }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Licenses</h5>\r\n <span *ngIf=\"licenses().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"licenseSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ licenseSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add license\" (click)=\"addLicense()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n <div *ngIf=\"licenses().length === 0 && !isAddingLicense() && editingLicenseIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No license added</div>\r\n </div>\r\n\r\n <div class=\"list-group list-group-flush shadow-sm rounded border\">\r\n <div *ngIf=\"isAddingLicense() && tempLicense()\" class=\"list-group-item py-3\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\" name=\"newLicName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\" name=\"newLicAuthority\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\" name=\"newLicNumber\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\" name=\"newLicState\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\" name=\"newLicIssueDate\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\" name=\"newLicExpiryDate\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\" name=\"newLicenseAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\"\r\n (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : licenseActionLabel(editingLicenseIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let lic of licenses(); let li = index\" class=\"list-group-item py-3\">\r\n <ng-container *ngIf=\"editingLicenseIndex() !== li; else editLic\">\r\n <div class=\"d-flex justify-content-between align-items-start gap-3\">\r\n <div>\r\n <div class=\"fw-semibold\">{{ lic.name }}</div>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedLicenseItem(li) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedLicenseItem(li) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"small text-muted\">\r\n {{ lic.issuingAuthority || '\u2014' }}\r\n <span *ngIf=\"lic.state\"> \u2022 {{ lic.state }}</span>\r\n </div>\r\n <div class=\"small text-muted\">\r\n Issue: {{ lic.issueDate ? formatMonthYear(lic.issueDate) : '\u2014' }}\r\n <span class=\"mx-1\">|</span>\r\n Expiry: {{ lic.expiryDate ? formatMonthYear(lic.expiryDate) : '\u2014' }}\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditLicense(li)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteLicense(li)\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-template #editLic>\r\n <div *ngIf=\"(licenseIssuesByIndex()[li] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (licenseIssuesByIndex()[li] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.name\"\r\n (ngModelChange)=\"patchTempLicense({ name: $event })\" name=\"licName{{ li }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempLicense()?.name)\">License name is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issuing Authority</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issuingAuthority || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issuingAuthority: $event || null })\" name=\"licAuthority{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">License Number</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.licenseNumber || ''\"\r\n (ngModelChange)=\"patchTempLicense({ licenseNumber: $event || null })\" name=\"licNumber{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">State</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.state || ''\"\r\n (ngModelChange)=\"patchTempLicense({ state: $event || null })\" name=\"licState{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Issue Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.issueDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ issueDate: $event || null })\" name=\"licIssue{{ li }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">Expiry Date</label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempLicense()?.expiryDate || ''\"\r\n (ngModelChange)=\"patchTempLicense({ expiryDate: $event || null })\" name=\"licExpiry{{ li }}\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempLicense()?.issueDate || null, tempLicense()?.expiryDate || null)\">\r\n Issued date must be less than expiry date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">LICENSE FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onLicenseFileSelected($event)\" name=\"licenseAttachment{{ li }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempLicense()?.fileName\">\r\n <span>{{ tempLicense()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempLicense())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditLicense()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingLicense\"\r\n (click)=\"saveLicenseEditor()\">\r\n <span *ngIf=\"isSavingLicense\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingLicense ? 'Uploading...' : licenseActionLabel(editingLicenseIndex()) }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"section mb-5\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Tools</h5>\r\n <span *ngIf=\"tools().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"toolsSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ toolsSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add tool\" (click)=\"addTool()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let tool of tools(); let ti = index\" class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\" class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openToolEditor(ti)\" title=\"Edit\">\r\n <span class=\"fw-normal text-dark\">{{ tool }}</span>\r\n <span class=\"badge border ms-1\"\r\n [ngClass]=\"hasUnsavedToolItem(ti) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedToolItem(ti) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <span *ngIf=\"(toolIssuesByIndex()[ti] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\">\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Delete tool\" (click)=\"deleteTool(ti)\">\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"tools().length === 0\" class=\"text-muted small\">No tools added.</span>\r\n </div>\r\n\r\n <!-- Tool edit panel (overlay) -->\r\n <div *ngIf=\"isToolEditorOpen()\" class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\" (click)=\"closeToolEditor()\">\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeToolEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingTool() ? 'Add Tool' : ('Edit ' + (toolForm()?.name || '') + ' Tool') }}\r\n </div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"toolFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ toolFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Tool Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"toolForm()?.name\"\r\n (ngModelChange)=\"patchToolForm({ name: $event })\" name=\"toolName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(toolForm()?.name)\">Tool name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span\r\n class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button *ngFor=\"let s of [1,2,3,4,5]\" type=\"button\" class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempToolStars(s)\" [attr.aria-label]=\"'Set rating ' + s\">\r\n <span [class]=\"(toolForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (toolForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ toolForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(toolForm()?.stars || 0) <= 0\">Star rating is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [ngModel]=\"toolForm()?.year\"\r\n (ngModelChange)=\"patchToolForm({ year: $event === '' ? null : +$event })\" name=\"toolYear\" min=\"1\"\r\n max=\"30\" placeholder=\"Years of Experience\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"toolForm()?.year === null || toolForm()?.year === undefined\">\r\n Years of experience is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"toolForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchToolForm({ profileVisibility: $event })\" name=\"toolVisible\"\r\n id=\"toolVisible\" />\r\n <label class=\"form-check-label\" for=\"toolVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea rows=\"4\" class=\"form-control\" [ngModel]=\"toolForm()?.notes\"\r\n (ngModelChange)=\"patchToolForm({ notes: $event })\" name=\"toolNotes\"\r\n placeholder=\"Comment your tool here...\"></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button *ngIf=\"!isAddingTool() && editingToolIndex() !== null\" type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\" (click)=\"deleteTool(editingToolIndex()!)\">\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeToolEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" [disabled]=\"isSavingTool\" (click)=\"saveToolEditor()\">\r\n <span *ngIf=\"isSavingTool\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingTool ? 'Saving...' : toolActionLabel(editingToolIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Skills</h5>\r\n <span *ngIf=\"skills().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"skillsSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ skillsSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add skill\" (click)=\"addSkill()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"d-flex flex-wrap gap-2 p-3 bg-white border rounded shadow-sm\">\r\n <div *ngFor=\"let skill of skills(); let si = index\" class=\"d-flex align-items-center gap-2\">\r\n <button type=\"button\"\r\n class=\"btn btn-light border rounded-pill d-inline-flex align-items-center gap-2 px-3 py-2\"\r\n (click)=\"openSkillEditor(si)\" title=\"Edit\">\r\n <span class=\"fw-normal text-dark\">{{ skill }}</span>\r\n <span class=\"badge border ms-1\"\r\n [ngClass]=\"hasUnsavedSkillItem(si) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedSkillItem(si) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <span *ngIf=\"(skillIssuesByIndex()[si] || []).length > 0\"\r\n class=\"badge bg-warning-subtle text-warning border ms-1\">\r\n Missing info\r\n </span>\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-danger rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Delete skill\"\r\n (click)=\"deleteSkill(si); $event.stopPropagation()\">\r\n <span class=\"fw-bold\">\u00D7</span>\r\n </button>\r\n </div>\r\n\r\n <span *ngIf=\"skills().length === 0\" class=\"text-muted small\">No skills added.</span>\r\n </div>\r\n <!-- Skill edit panel (overlay) -->\r\n <div *ngIf=\"isSkillEditorOpen()\" class=\"position-fixed top-0 start-0 w-100 h-100\"\r\n style=\"background: rgba(0,0,0,0.35); z-index: 2000;\" (click)=\"closeSkillEditor()\">\r\n <div class=\"container h-100 d-flex align-items-start justify-content-center pt-4\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"card shadow border-0 w-100\" style=\"max-width: 900px;\">\r\n <div class=\"card-header bg-white d-flex align-items-center gap-3\">\r\n <button type=\"button\" class=\"btn btn-link p-0 text-decoration-none\" (click)=\"closeSkillEditor()\">\r\n <i class=\"bi bi-arrow-left fs-5\"></i>\r\n </button>\r\n <div class=\"fw-bold\">{{ isAddingSkill() ? 'Add Skill' : ('Edit ' + (skillForm()?.name || '') + ' Skill')\r\n }}</div>\r\n </div>\r\n <div class=\"card-body p-4\">\r\n <div *ngIf=\"skillFormIssues().length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ skillFormIssues().join(' \u2022 ') }}</div>\r\n </div>\r\n <div class=\"row g-3\">\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Skills Name <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control\" [ngModel]=\"skillForm()?.name\"\r\n (ngModelChange)=\"patchSkillForm({ name: $event })\" name=\"skillName\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(skillForm()?.name)\">Skillset name is required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block mb-1\">Self-ability Rating <span\r\n class=\"text-danger\">*</span></label>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <button *ngFor=\"let s of [1,2,3,4,5]\" type=\"button\" class=\"btn btn-link p-0 text-decoration-none\"\r\n (click)=\"setTempSkillStars(s)\" [attr.aria-label]=\"'Set rating ' + s\">\r\n <span [class]=\"(skillForm()?.stars || 0) >= s ? 'text-warning fs-5' : 'text-muted fs-5'\">\r\n {{ (skillForm()?.stars || 0) >= s ? '\u2605' : '\u2606' }}\r\n </span>\r\n </button>\r\n <span class=\"small text-muted\">{{ skillForm()?.stars || 0 }}/5</span>\r\n </div>\r\n <div class=\"small text-danger mt-1\" *ngIf=\"(skillForm()?.stars || 0) <= 0\">Star rating is required\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Years of Experience <span class=\"text-danger\">*</span></label>\r\n <input type=\"number\" class=\"form-control\" [ngModel]=\"skillForm()?.year\"\r\n (ngModelChange)=\"patchSkillForm({ year: $event === '' ? null : +$event })\" name=\"skillYear\" min=\"1\"\r\n max=\"30\" placeholder=\"Years of Experience\" />\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"skillForm()?.year === null || skillForm()?.year === undefined\">Years of experience is\r\n required</div>\r\n </div>\r\n\r\n <div class=\"col-md-4\">\r\n <label class=\"small text-muted d-block\">Profile Visibility</label>\r\n <div class=\"form-check form-switch mt-2\">\r\n <input class=\"form-check-input\" type=\"checkbox\" [ngModel]=\"skillForm()?.profileVisibility\"\r\n (ngModelChange)=\"patchSkillForm({ profileVisibility: $event })\" name=\"skillVisible\"\r\n id=\"skillVisible\" />\r\n <label class=\"form-check-label\" for=\"skillVisible\">Visible</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">Comment</label>\r\n <textarea rows=\"4\" class=\"form-control\" [ngModel]=\"skillForm()?.notes\"\r\n (ngModelChange)=\"patchSkillForm({ notes: $event })\" name=\"skillNotes\"\r\n placeholder=\"Comment your skill here...\"></textarea>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"card-footer bg-white d-flex justify-content-end gap-2\">\r\n <button *ngIf=\"!isAddingSkill() && editingSkillIndex() !== null\" type=\"button\"\r\n class=\"btn btn-link text-danger me-auto\" (click)=\"deleteSkill(editingSkillIndex()!)\">\r\n Delete\r\n </button>\r\n <button type=\"button\" class=\"btn btn-link text-secondary\" (click)=\"closeSkillEditor()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary px-4\" [disabled]=\"isSavingSkill\" (click)=\"saveSkillEditor()\">\r\n <span *ngIf=\"isSavingSkill\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingSkill ? 'Saving...' : skillActionLabel(editingSkillIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"row g-4 mb-5\">\r\n <div class=\"col-md-12\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <h5 class=\"fw-bold mb-0\">Education</h5>\r\n <span *ngIf=\"educationList().length > 0\"\r\n class=\"badge border section-flag\"\r\n [ngClass]=\"educationSectionHasUnsavedItems() ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ educationSectionHasUnsavedItems() ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"btn btn-sm btn-outline-primary rounded-circle d-inline-flex align-items-center justify-content-center\"\r\n style=\"width: 32px; height: 32px; line-height: 1;\" title=\"Add education\" (click)=\"addEducation()\">\r\n <span class=\"fw-bold fs-5\">+</span>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"educationList().length === 0 && !isAddingEducation() && editingEducationIndex() === null\"\r\n class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">No education added</div>\r\n <!-- <div class=\"small\">Add at least one education.</div> -->\r\n </div>\r\n\r\n <div *ngIf=\"isAddingEducation() && tempEducation()\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\" name=\"newEduDegree\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\" name=\"newEduInstitution\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\" name=\"newEduDegreeType\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\" name=\"newEduStartDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\" name=\"newEduEndDate\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">EDUCATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onEducationFileSelected($event)\" name=\"newEducationAttachment\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempEducation()?.fileName\">\r\n <span>{{ tempEducation()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempEducation())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button type=\"button\" class=\"btn btn-sm btn-link text-secondary\"\r\n (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingEducation\"\r\n (click)=\"saveEducation()\">\r\n <span *ngIf=\"isSavingEducation\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingEducation ? 'Uploading...' : educationActionLabel(editingEducationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let edu of educationList(); let ei = index\" class=\"p-3 bg-white border rounded shadow-sm mb-2\">\r\n <div class=\"d-flex justify-content-between align-items-start\">\r\n <div>\r\n <h6 class=\"fw-bold mb-1\">{{ edu.degree }}</h6>\r\n <span class=\"badge border mt-1\"\r\n [ngClass]=\"hasUnsavedEducationItem(ei) ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success'\">\r\n {{ hasUnsavedEducationItem(ei) ? 'Not saved yet' : 'Saved' }}\r\n </span>\r\n <p class=\"small text-primary mb-0\">{{ edu.institution }}</p>\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-1 px-2 mt-2\">\r\n <div class=\"fw-semibold small\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n </div>\r\n <div class=\"d-flex align-items-center gap-2\" *ngIf=\"editingEducationIndex() !== ei\">\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary action-icon-btn\"\r\n (click)=\"startEditEducation(ei)\" title=\"Edit\">\r\n <img class=\"action-icon-image edit-icon\" src=\"/assets/images/icons/edit-text.png\" alt=\"Edit\" />\r\n </button>\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger action-icon-btn\" (click)=\"deleteEducation(ei)\"\r\n title=\"Delete\">\r\n <img class=\"action-icon-image delete-icon\" src=\"/assets/images/icons/delete.png\" alt=\"Delete\" />\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"editingEducationIndex() === ei\" class=\"mt-3\">\r\n <div *ngIf=\"(educationIssuesByIndex()[ei] || []).length > 0\" class=\"alert alert-warning py-2 px-3 mb-3\">\r\n <div class=\"fw-semibold\">Missing required fields</div>\r\n <div class=\"small\">{{ (educationIssuesByIndex()[ei] || []).join(' \u2022 ') }}</div>\r\n </div>\r\n <form class=\"row g-2\">\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degree\"\r\n (ngModelChange)=\"patchTempEducation({ degree: $event })\" name=\"eduDegree{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degree)\">Degree / Course name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">INSTITUTION <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.institution\"\r\n (ngModelChange)=\"patchTempEducation({ institution: $event })\" name=\"eduInstitution{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.institution)\">Institution name is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">DEGREE / COURSE TYPE <span class=\"text-danger\">*</span></label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.degreeType\"\r\n (ngModelChange)=\"patchTempEducation({ degreeType: $event })\" name=\"eduDegreeType{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.degreeType)\">Degree / Course type is\r\n required</div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">City</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.city\"\r\n (ngModelChange)=\"patchTempEducation({ city: $event })\" name=\"eduCity{{ ei }}\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">START DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.startDate\"\r\n (ngModelChange)=\"patchTempEducation({ startDate: $event })\" name=\"eduStartDate{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.startDate)\">Start date is required\r\n </div>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"small text-muted d-block\">END DATE <span class=\"text-danger\">*</span></label>\r\n <input type=\"month\" class=\"form-control form-control-sm\" [ngModel]=\"tempEducation()?.endDate\"\r\n (ngModelChange)=\"patchTempEducation({ endDate: $event })\" name=\"eduEndDate{{ ei }}\" />\r\n <div class=\"small text-danger mt-1\" *ngIf=\"isBlank(tempEducation()?.endDate)\">End date is required</div>\r\n <div class=\"small text-danger mt-1\"\r\n *ngIf=\"isMonthRangeInvalid(tempEducation()?.startDate, tempEducation()?.endDate)\">\r\n Start date must be less than end date\r\n </div>\r\n </div>\r\n <div class=\"col-12\">\r\n <label class=\"small text-muted d-block\">EDUCATION FILE</label>\r\n <input type=\"file\" accept=\".pdf,.doc,.docx,.jpg,.jpeg,.png\" class=\"form-control form-control-sm\"\r\n (change)=\"onEducationFileSelected($event)\" name=\"educationAttachment{{ ei }}\" />\r\n <div class=\"small text-muted mt-1 d-flex align-items-center gap-2\" *ngIf=\"tempEducation()?.fileName\">\r\n <span>{{ tempEducation()?.fileName }}</span>\r\n <button type=\"button\" class=\"btn btn-link btn-sm p-0\"\r\n (click)=\"previewSelectedFile(tempEducation())\">Preview</button>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <div class=\"d-flex justify-content-end gap-2 mt-2\">\r\n <button class=\"btn btn-sm btn-link text-secondary\" (click)=\"cancelEditEducation()\">Cancel</button>\r\n <button class=\"btn btn-sm btn-success px-4\" [disabled]=\"isSavingEducation\" (click)=\"saveEducation()\">\r\n <span *ngIf=\"isSavingEducation\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingEducation ? 'Uploading...' : educationActionLabel(editingEducationIndex()) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <div class=\"d-flex gap-2 justify-content-center mt-5\">\r\n <button class=\"btn btn-outline-secondary px-5\" (click)=\"onBackClick()\">Back</button>\r\n <button class=\"btn btn-primary px-5 shadow\" [disabled]=\"!canConfirmAndContinue() || isSavingBasic\"\r\n (click)=\"onGoToDashboardClick()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Saving...' : 'Go to Dashboard' }}\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<ng-template #loading>\r\n <div class=\"text-center p-5\">\r\n <div class=\"spinner-border text-primary\"></div>\r\n <p class=\"text-muted mt-2\">Loading data...</p>\r\n </div>\r\n</ng-template>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showBackConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">Leave this page?</h4>\r\n <p class=\"text-muted mb-4\">If you go back, only saved data will be retained.</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"stayOnPreview()\">Stay</button>\r\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"proceedBack()\">Proceed Back</button>\r\n </div>\r\n </div>\r\n\r\n</div>\r\n\r\n<div class=\"modal-overlay\" *ngIf=\"showDashboardConfirmPopup\">\r\n <div class=\"confirm-modal-card\">\r\n <h4 class=\"mb-2\">You are redirecting to dashboard</h4>\r\n <p class=\"text-muted mb-4\">Proceed to save initial setup completion and continue?</p>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" [disabled]=\"isSavingBasic\"\r\n (click)=\"cancelDashboardRedirect()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"isSavingBasic\" (click)=\"goToDashboard()\">\r\n <span *ngIf=\"isSavingBasic\" class=\"spinner-border spinner-border-sm me-1\"></span>\r\n {{ isSavingBasic ? 'Proceeding...' : 'Proceed' }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".container{max-width:900px;margin:30px auto;font-family:Arial,sans-serif;color:#333}.preview-page-header{max-width:900px;margin:50px auto 10px;padding:0 12px}.preview-title{font-weight:600;margin:0}.preview-subtitle{margin-top:7px;font-size:16px}.section{margin-bottom:25px}.section-title{font-weight:600;display:block;margin-bottom:8px}.section-title .sub{font-weight:400;color:#777;font-size:13px}.dropdown-box{border:1px solid #ddd;border-radius:8px;padding:12px 15px;display:flex;justify-content:space-between;align-items:center}.icons{display:flex;gap:10px}.icon{cursor:pointer;font-size:14px;color:#777}.card{border:1px solid #ddd;border-radius:10px;padding:20px;background:#fff}.category{border-bottom:1px solid #eee;padding:15px 0}.category:last-child{border-bottom:none}.category-header{display:flex;justify-content:space-between;font-weight:600;margin-bottom:10px}.tags{display:flex;flex-wrap:wrap;gap:10px}.tag{background:#f2f4f7;padding:6px 12px;border-radius:6px;font-size:13px}.footer{text-align:center;margin-top:15px;color:#777;cursor:pointer}.actions{display:flex;justify-content:space-between;margin:50px 0 27px}.text-secondary{white-space:pre-line;font-size:.95rem}.extra-small{font-size:.8rem}.card{transition:transform .2s ease,box-shadow .2s ease}.card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem #0000001a!important}.achievement-section .badge{font-size:.75rem;white-space:normal;text-align:left}.skill-tag .badge{font-weight:500;font-size:.9rem;transition:all .2s ease;cursor:default}.skill-tag .badge:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.skill-tag .btn-close{opacity:.5}.skill-tag .btn-close:hover{opacity:1}.bg-light.text-dark.border{background-color:#f8f9fa!important;border-color:#dee2e6!important}.card{transition:all .3s cubic-bezier(.25,.8,.25,1)}.card:hover{box-shadow:0 10px 20px #0000001a!important}.card:hover .bg-light{background-color:#e8f5e9!important}.position-fixed .card:hover{transform:none!important}code{background-color:#f8f9fa;padding:2px 6px;border-radius:4px}.list-group-item{transition:all .2s ease-in-out}.list-group-item:hover{background-color:#fcfcfc;transform:translate(5px);box-shadow:-5px 0 15px #0000000d}.border-dashed{border-style:dashed!important}.tool-card{transition:all .2s ease-in-out;border-radius:12px}.tool-card:hover{transform:translateY(-5px);border-color:var(--bs-primary)!important;box-shadow:0 10px 15px #0000001a!important}.tool-card:hover .icon-box{background-color:var(--bs-primary-subtle)!important}.tool-card .icon-box{width:60px;height:60px;transition:background-color .2s ease}.border-dashed{border:2px dashed #dee2e6!important}.border-dashed:hover{border-color:var(--bs-primary)!important;background-color:#fff!important}.popup{position:fixed;top:20%;right:20px;width:320px;background:#fff;border-radius:10px;padding:16px;box-shadow:0 4px 12px #0003}.status-row{display:flex;justify-content:space-between;margin:10px 0}.success{color:#2e7d32;font-weight:700}.error{color:#d32f2f}.pending{color:#f9a825}.section-flag{font-weight:600}button.btn.btn-sm.btn-outline-primary{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:10px}button.btn.btn-sm.btn-outline-primary:hover{background-color:#356895;border-color:#356895;color:#fff}button.btn.btn-sm.btn-outline-danger{border-color:#dc3545;color:#dc3545;border-radius:10px}button.btn.btn-sm.btn-outline-danger:hover{background-color:#bb2d3b;border-color:#bb2d3b;color:#fff}button.btn.btn-sm.btn-outline-primary.rounded-circle{background-color:#4077ad;border-color:#4077ad;color:#fff;border-radius:50%!important}button.action-icon-btn{min-width:22px;width:22px;height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:0!important;background:transparent!important;border:none!important;box-shadow:none!important;line-height:1}button.action-icon-btn.btn-outline-primary{color:#4077ad!important;border-color:#4077ad!important}button.action-icon-btn.btn-outline-danger{color:#dc3545!important}.action-icon-image{width:20px;height:20px;object-fit:contain;display:inline-block}.action-icon-image.edit-icon{filter:brightness(0) saturate(100%) invert(41%) sepia(39%) saturate(774%) hue-rotate(170deg) brightness(91%) contrast(89%)}.action-icon-image.delete-icon{filter:brightness(0) saturate(100%) invert(24%) sepia(79%) saturate(4008%) hue-rotate(344deg) brightness(91%) contrast(90%)}.modal-overlay{position:fixed;inset:0;background:#11182773;display:flex;align-items:center;justify-content:center;z-index:2500;padding:20px}.status-modal-card{width:min(760px,96vw);max-height:85vh;overflow:auto;background:#fff;border-radius:16px;box-shadow:0 20px 40px #0f172a40;border:1px solid #e5e7eb}.status-modal-header{padding:20px 24px 14px;border-bottom:1px solid #eef2f7}.status-modal-body{padding:12px 20px 20px}.status-modal-footer{padding:12px 20px 20px;border-top:1px solid #eef2f7;display:flex;justify-content:end;align-items:center;gap:12px}.status-modal-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 10px;border-bottom:1px solid #f1f5f9}.status-modal-row:last-child{border-bottom:0}.status-modal-name{font-weight:600;color:#1f2937}.status-modal-state{font-size:.92rem;font-weight:600}.status-modal-state.pending{color:#a16207}.status-modal-state.success{color:#166534}.status-modal-state.partial{color:#b45309}.status-modal-state.error{color:#b91c1c}.confirm-modal-card{width:min(520px,96vw);background:#fff;border-radius:14px;box-shadow:0 20px 35px #0f172a38;border:1px solid #e5e7eb;padding:24px}.year-experience-select{max-height:150px}\n"] }]
|
|
30878
31590
|
}], ctorParameters: () => [{ type: CredentialingStore }, { type: FileService }, { type: UserSkillSetService }, { type: UserToolService }, { type: UserDocumentService }, { type: UserEducationService }, { type: UserDetailService }, { type: UserExperienceService }, { type: i6.TokenService }, { type: undefined, decorators: [{
|
|
30879
31591
|
type: Inject,
|
|
30880
31592
|
args: [LIBRARY_CONFIG]
|
|
@@ -30884,10 +31596,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
30884
31596
|
type: Input
|
|
30885
31597
|
}], roleData: [{
|
|
30886
31598
|
type: Input
|
|
30887
|
-
}], cloudfrontUrl: [{
|
|
30888
|
-
type: Input
|
|
30889
31599
|
}], backToParent: [{
|
|
30890
31600
|
type: Output
|
|
31601
|
+
}], cloudfrontUrl: [{
|
|
31602
|
+
type: Input
|
|
30891
31603
|
}] } });
|
|
30892
31604
|
|
|
30893
31605
|
class CredentialingComponent {
|