@idsoftsource/initial-process 1.4.7 → 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) => {
|
|
@@ -29163,16 +29162,20 @@ class FirstComponent {
|
|
|
29163
29162
|
}
|
|
29164
29163
|
}
|
|
29165
29164
|
onFileSelected(event) {
|
|
29166
|
-
|
|
29165
|
+
const input = event.target;
|
|
29166
|
+
this.fileData = input.files?.[0];
|
|
29167
29167
|
if (this.fileData) {
|
|
29168
29168
|
this.resumeName = this.fileData.name;
|
|
29169
29169
|
this.model.fileName = this.resumeName;
|
|
29170
29170
|
this.isUploading = true;
|
|
29171
29171
|
setTimeout(() => {
|
|
29172
29172
|
this.store.addSuccess(true);
|
|
29173
|
-
this.saveAWSFile();
|
|
29173
|
+
// this.saveAWSFile();
|
|
29174
|
+
this.uploadresume();
|
|
29174
29175
|
}, 1500);
|
|
29175
29176
|
}
|
|
29177
|
+
// ✅ MUST ADD THIS LINE
|
|
29178
|
+
input.value = '';
|
|
29176
29179
|
}
|
|
29177
29180
|
openFile(fileInput, event) {
|
|
29178
29181
|
event.stopPropagation();
|
|
@@ -29185,70 +29188,72 @@ class FirstComponent {
|
|
|
29185
29188
|
this.store.addSuccess(false);
|
|
29186
29189
|
this.store.nextStep();
|
|
29187
29190
|
}
|
|
29188
|
-
async saveAWSFile() {
|
|
29189
|
-
|
|
29190
|
-
|
|
29191
|
-
|
|
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
|
-
|
|
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
|
+
// }
|
|
29223
29254
|
uploadresume() {
|
|
29224
|
-
this.
|
|
29225
|
-
|
|
29226
|
-
s3Key: this.model.fileUrl,
|
|
29227
|
-
userId: this.userId
|
|
29228
|
-
};
|
|
29229
|
-
this.resumedetail.uploadResume(model).subscribe({
|
|
29230
|
-
next: (res) => {
|
|
29231
|
-
if (res?.failed) {
|
|
29232
|
-
this.isUploading = false;
|
|
29233
|
-
this.resumeName = null; // ✅ RESET
|
|
29234
|
-
this.fileData = null; // ✅ RESET
|
|
29235
|
-
this.model = { fileId: null, fileUrl: null, fileName: null }; // ✅ RESET
|
|
29236
|
-
return;
|
|
29237
|
-
}
|
|
29238
|
-
this.handleAutoNavigation();
|
|
29239
|
-
this.resumeData = res;
|
|
29240
|
-
this.store.setProfile(this.resumeData);
|
|
29241
|
-
console.log('Resume Loaded Successfully:', this.resumeData);
|
|
29242
|
-
console.log(`Working with ${this.resumeData.basicDetails.firstName}`);
|
|
29243
|
-
this.isUploading = false;
|
|
29244
|
-
},
|
|
29245
|
-
error: (err) => {
|
|
29246
|
-
console.error('Upload failed:', err);
|
|
29247
|
-
this.isUploading = false;
|
|
29248
|
-
this.resumeName = null; // ✅ RESET
|
|
29249
|
-
this.fileData = null; // ✅ RESET
|
|
29250
|
-
}
|
|
29251
|
-
});
|
|
29255
|
+
this.store.setProfile(this.sample);
|
|
29256
|
+
this.handleAutoNavigation();
|
|
29252
29257
|
}
|
|
29253
29258
|
onBackClick() {
|
|
29254
29259
|
this.backToParent.emit();
|
|
@@ -29279,7 +29284,6 @@ class PreviewComponent {
|
|
|
29279
29284
|
providerId;
|
|
29280
29285
|
providerName;
|
|
29281
29286
|
roleData;
|
|
29282
|
-
cloudfrontUrl;
|
|
29283
29287
|
backToParent = new EventEmitter();
|
|
29284
29288
|
fileData = null;
|
|
29285
29289
|
model = { fileId: null, fileUrl: null, fileName: null };
|
|
@@ -29290,14 +29294,37 @@ class PreviewComponent {
|
|
|
29290
29294
|
isSavingEducation = false;
|
|
29291
29295
|
isSavingCertification = false;
|
|
29292
29296
|
isSavingLicense = false;
|
|
29297
|
+
isSavingBasic = false;
|
|
29298
|
+
basicDetailsSaved = false;
|
|
29299
|
+
hasUserDetailData = false;
|
|
29300
|
+
isSavingSkill = false;
|
|
29301
|
+
isSavingTool = false;
|
|
29293
29302
|
payloadUserId;
|
|
29294
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
|
+
};
|
|
29295
29323
|
resumeData;
|
|
29296
29324
|
statusList = [];
|
|
29297
29325
|
showPopup = false;
|
|
29298
29326
|
showBackConfirmPopup = false;
|
|
29299
|
-
|
|
29300
|
-
userName;
|
|
29327
|
+
showDashboardConfirmPopup = false;
|
|
29301
29328
|
constructor(store, fileService, userSkillSetService, userToolService, userDocumentService, userEducation, userDetailService, userExperienceService, tokenService, libConfig) {
|
|
29302
29329
|
this.store = store;
|
|
29303
29330
|
this.fileService = fileService;
|
|
@@ -29312,13 +29339,6 @@ class PreviewComponent {
|
|
|
29312
29339
|
this.resumeData = this.store.profileSignal();
|
|
29313
29340
|
}
|
|
29314
29341
|
email;
|
|
29315
|
-
showLoader = false;
|
|
29316
|
-
uploadFolderBySection = {
|
|
29317
|
-
work: 'Experience',
|
|
29318
|
-
education: 'Education',
|
|
29319
|
-
certification: 'Certification',
|
|
29320
|
-
license: 'License'
|
|
29321
|
-
};
|
|
29322
29342
|
expYears = [];
|
|
29323
29343
|
details = signal(undefined, ...(ngDevMode ? [{ debugName: "details" }] : []));
|
|
29324
29344
|
async ngOnInit() {
|
|
@@ -29328,14 +29348,11 @@ class PreviewComponent {
|
|
|
29328
29348
|
this.details.set(initialData);
|
|
29329
29349
|
}
|
|
29330
29350
|
this.prefillSectionFieldsFromBasicDetails();
|
|
29331
|
-
// Use 1..30 years of experience.
|
|
29332
29351
|
this.expYears = Array.from({ length: 30 }, (_, i) => i + 1);
|
|
29352
|
+
await this.loadSavedSectionsFromApis();
|
|
29333
29353
|
}
|
|
29334
29354
|
experience = computed(() => this.store.profileSignal()?.workExperience || [], ...(ngDevMode ? [{ debugName: "experience" }] : []));
|
|
29335
|
-
educationList = computed(() => {
|
|
29336
|
-
const list = this.store.profileSignal()?.education || [];
|
|
29337
|
-
return [...list].sort((a, b) => b.endDate.localeCompare(a.endDate));
|
|
29338
|
-
}, ...(ngDevMode ? [{ debugName: "educationList" }] : []));
|
|
29355
|
+
educationList = computed(() => this.store.profileSignal()?.education || [], ...(ngDevMode ? [{ debugName: "educationList" }] : []));
|
|
29339
29356
|
certs = computed(() => this.store.profileSignal()?.certifications || [], ...(ngDevMode ? [{ debugName: "certs" }] : []));
|
|
29340
29357
|
licenses = computed(() => this.store.profileSignal()?.licenses || [], ...(ngDevMode ? [{ debugName: "licenses" }] : []));
|
|
29341
29358
|
skills = computed(() => this.store.profileSignal()?.skills || [], ...(ngDevMode ? [{ debugName: "skills" }] : []));
|
|
@@ -29352,6 +29369,10 @@ class PreviewComponent {
|
|
|
29352
29369
|
const digits = (value ?? '').toString().replace(/\D/g, '');
|
|
29353
29370
|
return digits.slice(0, 10);
|
|
29354
29371
|
}
|
|
29372
|
+
sanitizeZipCode(value) {
|
|
29373
|
+
const digits = (value ?? '').toString().replace(/\D/g, '');
|
|
29374
|
+
return digits.slice(0, 6);
|
|
29375
|
+
}
|
|
29355
29376
|
isMonthRangeInvalid(start, end) {
|
|
29356
29377
|
if (this.isBlankOrNull(start) || this.isBlankOrNull(end))
|
|
29357
29378
|
return false;
|
|
@@ -29457,6 +29478,9 @@ class PreviewComponent {
|
|
|
29457
29478
|
issues.push('State is required');
|
|
29458
29479
|
if (this.isBlank(item.zipCode))
|
|
29459
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
|
+
}
|
|
29460
29484
|
if (this.isBlank(item.country))
|
|
29461
29485
|
issues.push('Country is required');
|
|
29462
29486
|
if (this.isBlank(item.phone))
|
|
@@ -29602,26 +29626,66 @@ class PreviewComponent {
|
|
|
29602
29626
|
return issues;
|
|
29603
29627
|
}, ...(ngDevMode ? [{ debugName: "toolFormIssues" }] : []));
|
|
29604
29628
|
workSectionHasIssues = computed(() => {
|
|
29605
|
-
|
|
29629
|
+
const hasValidationIssues = this.workIssuesByIndex().some((x) => x.length > 0);
|
|
29630
|
+
const hasUnsavedItems = this.workSectionHasUnsavedItems();
|
|
29631
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29606
29632
|
}, ...(ngDevMode ? [{ debugName: "workSectionHasIssues" }] : []));
|
|
29607
29633
|
educationSectionHasIssues = computed(() => {
|
|
29608
|
-
|
|
29634
|
+
const hasValidationIssues = this.educationIssuesByIndex().some((x) => x.length > 0);
|
|
29635
|
+
const hasUnsavedItems = this.educationSectionHasUnsavedItems();
|
|
29636
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29609
29637
|
}, ...(ngDevMode ? [{ debugName: "educationSectionHasIssues" }] : []));
|
|
29610
29638
|
certificationsSectionHasIssues = computed(() => {
|
|
29611
|
-
|
|
29639
|
+
const hasValidationIssues = this.certIssuesByIndex().some((x) => x.length > 0);
|
|
29640
|
+
const hasUnsavedItems = this.certificationSectionHasUnsavedItems();
|
|
29641
|
+
return hasValidationIssues || hasUnsavedItems;
|
|
29612
29642
|
}, ...(ngDevMode ? [{ debugName: "certificationsSectionHasIssues" }] : []));
|
|
29613
|
-
licensesSectionHasIssues = computed(() =>
|
|
29614
|
-
|
|
29615
|
-
|
|
29616
|
-
|
|
29617
|
-
|
|
29618
|
-
|
|
29619
|
-
|
|
29620
|
-
|
|
29621
|
-
|
|
29622
|
-
|
|
29623
|
-
|
|
29624
|
-
|
|
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
|
+
}
|
|
29625
29689
|
expandedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "expandedIndex" }] : []));
|
|
29626
29690
|
toggleJob(index) {
|
|
29627
29691
|
this.expandedIndex.update(current => current === index ? -1 : index);
|
|
@@ -29659,6 +29723,493 @@ class PreviewComponent {
|
|
|
29659
29723
|
}
|
|
29660
29724
|
return value;
|
|
29661
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
|
+
}
|
|
29662
30213
|
formatMonthYear(value) {
|
|
29663
30214
|
if (!value)
|
|
29664
30215
|
return '';
|
|
@@ -29705,6 +30256,9 @@ class PreviewComponent {
|
|
|
29705
30256
|
const job = current?.workExperience?.[jobIndex];
|
|
29706
30257
|
if (!current || !job)
|
|
29707
30258
|
return;
|
|
30259
|
+
if (!this.workExperienceServerIds[jobIndex]) {
|
|
30260
|
+
void this.ensureWorkServerId(jobIndex, job);
|
|
30261
|
+
}
|
|
29708
30262
|
this.jobEditor.set({
|
|
29709
30263
|
mode: 'edit', index: jobIndex, data: {
|
|
29710
30264
|
...job,
|
|
@@ -29724,9 +30278,9 @@ class PreviewComponent {
|
|
|
29724
30278
|
const nextJob = this.tempJob();
|
|
29725
30279
|
if (!current || !nextJob)
|
|
29726
30280
|
return;
|
|
29727
|
-
|
|
29728
|
-
|
|
29729
|
-
|
|
30281
|
+
this.isSavingWork = true;
|
|
30282
|
+
try {
|
|
30283
|
+
if (nextJob.fileObject) {
|
|
29730
30284
|
this.fileData = nextJob.fileObject;
|
|
29731
30285
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.work);
|
|
29732
30286
|
nextJob.fileId = uploaded.fileId;
|
|
@@ -29734,33 +30288,21 @@ class PreviewComponent {
|
|
|
29734
30288
|
nextJob.fileName = uploaded.fileName;
|
|
29735
30289
|
nextJob.fileObject = null;
|
|
29736
30290
|
}
|
|
29737
|
-
|
|
29738
|
-
|
|
29739
|
-
|
|
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;
|
|
29740
30303
|
}
|
|
29741
|
-
const next = this.isAddingJob()
|
|
29742
|
-
? { ...current, workExperience: [...(current.workExperience ?? []), nextJob] }
|
|
29743
|
-
: {
|
|
29744
|
-
...current,
|
|
29745
|
-
workExperience: current.workExperience.map((j, idx) => (idx === jobIndex ? nextJob : j))
|
|
29746
|
-
};
|
|
29747
|
-
this.commitProfile(next);
|
|
29748
30304
|
this.cancelEditJob();
|
|
29749
30305
|
}
|
|
29750
|
-
onWorkExperienceFileSelected(event) {
|
|
29751
|
-
this.revokeObjectUrl(this.tempJob()?.filePreviewUrl);
|
|
29752
|
-
this.selectFile(event);
|
|
29753
|
-
const file = this.fileData;
|
|
29754
|
-
if (!file)
|
|
29755
|
-
return;
|
|
29756
|
-
this.patchTempJob({
|
|
29757
|
-
fileId: null,
|
|
29758
|
-
fileUrl: null,
|
|
29759
|
-
fileName: file.name,
|
|
29760
|
-
fileObject: file,
|
|
29761
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
29762
|
-
});
|
|
29763
|
-
}
|
|
29764
30306
|
deleteJob(index) {
|
|
29765
30307
|
const current = this.store.profileSignal();
|
|
29766
30308
|
if (!current?.workExperience?.length)
|
|
@@ -29773,6 +30315,8 @@ class PreviewComponent {
|
|
|
29773
30315
|
...current,
|
|
29774
30316
|
workExperience: current.workExperience.filter((_, i) => i !== index),
|
|
29775
30317
|
});
|
|
30318
|
+
this.workExperienceServerIds = this.reindexServerIdsAfterDelete(this.workExperienceServerIds, index);
|
|
30319
|
+
this.workExperienceLocallySaved = this.reindexSavedFlagsAfterDelete(this.workExperienceLocallySaved, index);
|
|
29776
30320
|
if (this.editingJobIndex() === index)
|
|
29777
30321
|
this.cancelEditJob();
|
|
29778
30322
|
if (this.expandedIndex() === index)
|
|
@@ -29794,6 +30338,20 @@ class PreviewComponent {
|
|
|
29794
30338
|
return;
|
|
29795
30339
|
this.jobEditor.update((e) => ({ ...e, data: { ...job, ...patch } }));
|
|
29796
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
|
+
}
|
|
29797
30355
|
setTempJobIsCurrent(isCurrent) {
|
|
29798
30356
|
const job = this.tempJob();
|
|
29799
30357
|
if (!job)
|
|
@@ -29822,6 +30380,9 @@ class PreviewComponent {
|
|
|
29822
30380
|
const current = this.store.profileSignal();
|
|
29823
30381
|
if (!current || !current.skills?.length || index < 0 || index >= current.skills.length)
|
|
29824
30382
|
return;
|
|
30383
|
+
if (!this.skillServerIds[index]) {
|
|
30384
|
+
void this.ensureSkillServerId(index);
|
|
30385
|
+
}
|
|
29825
30386
|
const meta = this.store.resumeSkillMeta()?.[index] ?? {};
|
|
29826
30387
|
this.skillEditor.set({
|
|
29827
30388
|
mode: 'edit',
|
|
@@ -29849,7 +30410,7 @@ class PreviewComponent {
|
|
|
29849
30410
|
setTempSkillStars(stars) {
|
|
29850
30411
|
this.patchSkillForm({ stars });
|
|
29851
30412
|
}
|
|
29852
|
-
saveSkillEditor() {
|
|
30413
|
+
async saveSkillEditor() {
|
|
29853
30414
|
const current = this.store.profileSignal();
|
|
29854
30415
|
const idx = this.editingSkillIndex();
|
|
29855
30416
|
if (!current)
|
|
@@ -29864,31 +30425,38 @@ class PreviewComponent {
|
|
|
29864
30425
|
return;
|
|
29865
30426
|
if (form.year === null || form.year === undefined)
|
|
29866
30427
|
return;
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
nextSkills.
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
|
|
29881
|
-
|
|
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
|
+
}
|
|
29882
30457
|
}
|
|
29883
|
-
|
|
29884
|
-
|
|
29885
|
-
this.store.setResumeSkillMeta(metaIndex, {
|
|
29886
|
-
providerName: (form.providerName || '').trim() || undefined,
|
|
29887
|
-
starRating: (form.stars || 0) * 2,
|
|
29888
|
-
year: form.year,
|
|
29889
|
-
profileVisibility: form.profileVisibility,
|
|
29890
|
-
notes: form.notes,
|
|
29891
|
-
});
|
|
30458
|
+
finally {
|
|
30459
|
+
this.isSavingSkill = false;
|
|
29892
30460
|
}
|
|
29893
30461
|
this.closeSkillEditor();
|
|
29894
30462
|
}
|
|
@@ -29917,6 +30485,8 @@ class PreviewComponent {
|
|
|
29917
30485
|
const nextSkills = current.skills.filter((_, i) => i !== index);
|
|
29918
30486
|
this.commitProfile({ ...current, skills: nextSkills });
|
|
29919
30487
|
this.reindexSkillMetaAfterDelete(index);
|
|
30488
|
+
this.skillServerIds = this.reindexServerIdsAfterDelete(this.skillServerIds, index);
|
|
30489
|
+
this.skillLocallySaved = this.reindexSavedFlagsAfterDelete(this.skillLocallySaved, index);
|
|
29920
30490
|
if (this.editingSkillIndex() === index)
|
|
29921
30491
|
this.closeSkillEditor();
|
|
29922
30492
|
}
|
|
@@ -29936,6 +30506,9 @@ class PreviewComponent {
|
|
|
29936
30506
|
const current = this.store.profileSignal();
|
|
29937
30507
|
if (!current || !current.tools?.length || index < 0 || index >= current.tools.length)
|
|
29938
30508
|
return;
|
|
30509
|
+
if (!this.toolServerIds[index]) {
|
|
30510
|
+
void this.ensureToolServerId(index);
|
|
30511
|
+
}
|
|
29939
30512
|
const meta = this.store.resumeToolMeta()?.[index] ?? {};
|
|
29940
30513
|
this.toolEditor.set({
|
|
29941
30514
|
mode: 'edit',
|
|
@@ -29963,7 +30536,7 @@ class PreviewComponent {
|
|
|
29963
30536
|
setTempToolStars(stars) {
|
|
29964
30537
|
this.patchToolForm({ stars });
|
|
29965
30538
|
}
|
|
29966
|
-
saveToolEditor() {
|
|
30539
|
+
async saveToolEditor() {
|
|
29967
30540
|
const current = this.store.profileSignal();
|
|
29968
30541
|
const idx = this.editingToolIndex();
|
|
29969
30542
|
if (!current)
|
|
@@ -29978,31 +30551,38 @@ class PreviewComponent {
|
|
|
29978
30551
|
return;
|
|
29979
30552
|
if (form.year === null || form.year === undefined)
|
|
29980
30553
|
return;
|
|
29981
|
-
|
|
29982
|
-
|
|
29983
|
-
nextTools.
|
|
29984
|
-
|
|
29985
|
-
|
|
29986
|
-
|
|
29987
|
-
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
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
|
+
}
|
|
29996
30583
|
}
|
|
29997
|
-
|
|
29998
|
-
|
|
29999
|
-
this.store.setResumeToolMeta(metaIndex, {
|
|
30000
|
-
providerName: (form.providerName || '').trim() || undefined,
|
|
30001
|
-
starRating: (form.stars || 0) * 2,
|
|
30002
|
-
year: form.year,
|
|
30003
|
-
profileVisibility: form.profileVisibility,
|
|
30004
|
-
notes: form.notes,
|
|
30005
|
-
});
|
|
30584
|
+
finally {
|
|
30585
|
+
this.isSavingTool = false;
|
|
30006
30586
|
}
|
|
30007
30587
|
this.closeToolEditor();
|
|
30008
30588
|
}
|
|
@@ -30031,6 +30611,8 @@ class PreviewComponent {
|
|
|
30031
30611
|
const nextTools = current.tools.filter((_, i) => i !== index);
|
|
30032
30612
|
this.commitProfile({ ...current, tools: nextTools });
|
|
30033
30613
|
this.reindexToolMetaAfterDelete(index);
|
|
30614
|
+
this.toolServerIds = this.reindexServerIdsAfterDelete(this.toolServerIds, index);
|
|
30615
|
+
this.toolLocallySaved = this.reindexSavedFlagsAfterDelete(this.toolLocallySaved, index);
|
|
30034
30616
|
if (this.editingToolIndex() === index)
|
|
30035
30617
|
this.closeToolEditor();
|
|
30036
30618
|
}
|
|
@@ -30065,25 +30647,14 @@ class PreviewComponent {
|
|
|
30065
30647
|
}
|
|
30066
30648
|
});
|
|
30067
30649
|
}
|
|
30068
|
-
onCertificationFileSelected(event) {
|
|
30069
|
-
this.revokeObjectUrl(this.tempCertification()?.filePreviewUrl);
|
|
30070
|
-
this.selectFile(event);
|
|
30071
|
-
const file = this.fileData;
|
|
30072
|
-
if (!file)
|
|
30073
|
-
return;
|
|
30074
|
-
this.patchTempCertification({
|
|
30075
|
-
fileId: null,
|
|
30076
|
-
fileUrl: null,
|
|
30077
|
-
fileName: file.name,
|
|
30078
|
-
fileObject: file,
|
|
30079
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30080
|
-
});
|
|
30081
|
-
}
|
|
30082
30650
|
startEditCertification(index) {
|
|
30083
30651
|
const current = this.store.profileSignal();
|
|
30084
30652
|
const cert = current?.certifications?.[index];
|
|
30085
30653
|
if (!current || !cert)
|
|
30086
30654
|
return;
|
|
30655
|
+
if (!this.certificationServerIds[index]) {
|
|
30656
|
+
void this.ensureCertificationServerId(index, cert);
|
|
30657
|
+
}
|
|
30087
30658
|
this.certificationEditor.set({
|
|
30088
30659
|
mode: 'edit', index, data: {
|
|
30089
30660
|
...cert,
|
|
@@ -30101,15 +30672,29 @@ class PreviewComponent {
|
|
|
30101
30672
|
return;
|
|
30102
30673
|
this.certificationEditor.update((e) => ({ ...e, data: { ...cert, ...patch } }));
|
|
30103
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
|
+
}
|
|
30104
30689
|
async saveCertificationEditor() {
|
|
30105
30690
|
const current = this.store.profileSignal();
|
|
30106
30691
|
const idx = this.editingCertificationIndex();
|
|
30107
30692
|
const nextCert = this.tempCertification();
|
|
30108
30693
|
if (!current || !nextCert)
|
|
30109
30694
|
return;
|
|
30110
|
-
|
|
30111
|
-
|
|
30112
|
-
|
|
30695
|
+
this.isSavingCertification = true;
|
|
30696
|
+
try {
|
|
30697
|
+
if (nextCert.fileObject) {
|
|
30113
30698
|
this.fileData = nextCert.fileObject;
|
|
30114
30699
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.certification);
|
|
30115
30700
|
nextCert.fileId = uploaded.fileId;
|
|
@@ -30117,26 +30702,30 @@ class PreviewComponent {
|
|
|
30117
30702
|
nextCert.fileName = uploaded.fileName;
|
|
30118
30703
|
nextCert.fileObject = null;
|
|
30119
30704
|
}
|
|
30120
|
-
|
|
30121
|
-
|
|
30122
|
-
|
|
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;
|
|
30123
30728
|
}
|
|
30124
|
-
const name = this.normalizeNullableString(nextCert.name);
|
|
30125
|
-
if (!name)
|
|
30126
|
-
return;
|
|
30127
|
-
const normalized = {
|
|
30128
|
-
...nextCert,
|
|
30129
|
-
name,
|
|
30130
|
-
issuingOrganization: this.normalizeNullableString(nextCert.issuingOrganization),
|
|
30131
|
-
state: this.normalizeNullableString(nextCert.state),
|
|
30132
|
-
credentialId: this.normalizeNullableString(nextCert.credentialId),
|
|
30133
|
-
issueDate: this.normalizeMonthInput(nextCert.issueDate),
|
|
30134
|
-
expiryDate: this.normalizeMonthInput(nextCert.expiryDate),
|
|
30135
|
-
};
|
|
30136
|
-
if (this.isAddingCertification())
|
|
30137
|
-
this.appendToMainModel('certifications', normalized);
|
|
30138
|
-
else if (idx !== null)
|
|
30139
|
-
this.updateInMainModel('certifications', idx, normalized);
|
|
30140
30729
|
this.cancelEditCertification();
|
|
30141
30730
|
}
|
|
30142
30731
|
deleteCertification(index) {
|
|
@@ -30151,6 +30740,8 @@ class PreviewComponent {
|
|
|
30151
30740
|
...current,
|
|
30152
30741
|
certifications: current.certifications.filter((_, i) => i !== index),
|
|
30153
30742
|
});
|
|
30743
|
+
this.certificationServerIds = this.reindexServerIdsAfterDelete(this.certificationServerIds, index);
|
|
30744
|
+
this.certificationLocallySaved = this.reindexSavedFlagsAfterDelete(this.certificationLocallySaved, index);
|
|
30154
30745
|
if (this.editingCertificationIndex() === index)
|
|
30155
30746
|
this.cancelEditCertification();
|
|
30156
30747
|
}
|
|
@@ -30175,25 +30766,14 @@ class PreviewComponent {
|
|
|
30175
30766
|
}
|
|
30176
30767
|
});
|
|
30177
30768
|
}
|
|
30178
|
-
onLicenseFileSelected(event) {
|
|
30179
|
-
this.revokeObjectUrl(this.tempLicense()?.filePreviewUrl);
|
|
30180
|
-
this.selectFile(event);
|
|
30181
|
-
const file = this.fileData;
|
|
30182
|
-
if (!file)
|
|
30183
|
-
return;
|
|
30184
|
-
this.patchTempLicense({
|
|
30185
|
-
fileId: null,
|
|
30186
|
-
fileUrl: null,
|
|
30187
|
-
fileName: file.name,
|
|
30188
|
-
fileObject: file,
|
|
30189
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30190
|
-
});
|
|
30191
|
-
}
|
|
30192
30769
|
startEditLicense(index) {
|
|
30193
30770
|
const current = this.store.profileSignal();
|
|
30194
30771
|
const lic = current?.licenses?.[index];
|
|
30195
30772
|
if (!current || !lic)
|
|
30196
30773
|
return;
|
|
30774
|
+
if (!this.licenseServerIds[index]) {
|
|
30775
|
+
void this.ensureLicenseServerId(index, lic);
|
|
30776
|
+
}
|
|
30197
30777
|
this.licenseEditor.set({
|
|
30198
30778
|
mode: 'edit', index, data: {
|
|
30199
30779
|
...lic,
|
|
@@ -30211,15 +30791,29 @@ class PreviewComponent {
|
|
|
30211
30791
|
return;
|
|
30212
30792
|
this.licenseEditor.update((e) => ({ ...e, data: { ...lic, ...patch } }));
|
|
30213
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
|
+
}
|
|
30214
30808
|
async saveLicenseEditor() {
|
|
30215
30809
|
const current = this.store.profileSignal();
|
|
30216
30810
|
const idx = this.editingLicenseIndex();
|
|
30217
30811
|
const nextLic = this.tempLicense();
|
|
30218
30812
|
if (!current || !nextLic)
|
|
30219
30813
|
return;
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30814
|
+
this.isSavingLicense = true;
|
|
30815
|
+
try {
|
|
30816
|
+
if (nextLic.fileObject) {
|
|
30223
30817
|
this.fileData = nextLic.fileObject;
|
|
30224
30818
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.license);
|
|
30225
30819
|
nextLic.fileId = uploaded.fileId;
|
|
@@ -30227,26 +30821,30 @@ class PreviewComponent {
|
|
|
30227
30821
|
nextLic.fileName = uploaded.fileName;
|
|
30228
30822
|
nextLic.fileObject = null;
|
|
30229
30823
|
}
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
|
|
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;
|
|
30233
30847
|
}
|
|
30234
|
-
const name = this.normalizeNullableString(nextLic.name);
|
|
30235
|
-
if (!name)
|
|
30236
|
-
return;
|
|
30237
|
-
const normalized = {
|
|
30238
|
-
...nextLic,
|
|
30239
|
-
name,
|
|
30240
|
-
issuingAuthority: this.normalizeNullableString(nextLic.issuingAuthority),
|
|
30241
|
-
licenseNumber: this.normalizeNullableString(nextLic.licenseNumber),
|
|
30242
|
-
state: this.normalizeNullableString(nextLic.state),
|
|
30243
|
-
issueDate: this.normalizeMonthInput(nextLic.issueDate),
|
|
30244
|
-
expiryDate: this.normalizeMonthInput(nextLic.expiryDate),
|
|
30245
|
-
};
|
|
30246
|
-
if (this.isAddingLicense())
|
|
30247
|
-
this.appendToMainModel('licenses', normalized);
|
|
30248
|
-
else if (idx !== null)
|
|
30249
|
-
this.updateInMainModel('licenses', idx, normalized);
|
|
30250
30848
|
this.cancelEditLicense();
|
|
30251
30849
|
}
|
|
30252
30850
|
deleteLicense(index) {
|
|
@@ -30261,6 +30859,8 @@ class PreviewComponent {
|
|
|
30261
30859
|
...current,
|
|
30262
30860
|
licenses: current.licenses.filter((_, i) => i !== index),
|
|
30263
30861
|
});
|
|
30862
|
+
this.licenseServerIds = this.reindexServerIdsAfterDelete(this.licenseServerIds, index);
|
|
30863
|
+
this.licenseLocallySaved = this.reindexSavedFlagsAfterDelete(this.licenseLocallySaved, index);
|
|
30264
30864
|
if (this.editingLicenseIndex() === index)
|
|
30265
30865
|
this.cancelEditLicense();
|
|
30266
30866
|
}
|
|
@@ -30288,25 +30888,14 @@ class PreviewComponent {
|
|
|
30288
30888
|
}
|
|
30289
30889
|
});
|
|
30290
30890
|
}
|
|
30291
|
-
onEducationFileSelected(event) {
|
|
30292
|
-
this.revokeObjectUrl(this.tempEducation()?.filePreviewUrl);
|
|
30293
|
-
this.selectFile(event);
|
|
30294
|
-
const file = this.fileData;
|
|
30295
|
-
if (!file)
|
|
30296
|
-
return;
|
|
30297
|
-
this.patchTempEducation({
|
|
30298
|
-
fileId: null,
|
|
30299
|
-
fileUrl: null,
|
|
30300
|
-
fileName: file.name,
|
|
30301
|
-
fileObject: file,
|
|
30302
|
-
filePreviewUrl: URL.createObjectURL(file)
|
|
30303
|
-
});
|
|
30304
|
-
}
|
|
30305
30891
|
startEditEducation(index) {
|
|
30306
30892
|
const current = this.store.profileSignal();
|
|
30307
30893
|
const edu = current?.education?.[index];
|
|
30308
30894
|
if (!current || !edu)
|
|
30309
30895
|
return;
|
|
30896
|
+
if (!this.educationServerIds[index]) {
|
|
30897
|
+
void this.ensureEducationServerId(index, edu);
|
|
30898
|
+
}
|
|
30310
30899
|
this.educationEditor.set({
|
|
30311
30900
|
mode: 'edit', index, data: {
|
|
30312
30901
|
...edu,
|
|
@@ -30325,15 +30914,29 @@ class PreviewComponent {
|
|
|
30325
30914
|
return;
|
|
30326
30915
|
this.educationEditor.update((e) => ({ ...e, data: { ...edu, ...patch } }));
|
|
30327
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
|
+
}
|
|
30328
30931
|
async saveEducation() {
|
|
30329
30932
|
const current = this.store.profileSignal();
|
|
30330
30933
|
const idx = this.editingEducationIndex();
|
|
30331
30934
|
const nextEdu = this.tempEducation();
|
|
30332
30935
|
if (!current || !nextEdu)
|
|
30333
30936
|
return;
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
|
|
30937
|
+
this.isSavingEducation = true;
|
|
30938
|
+
try {
|
|
30939
|
+
if (nextEdu.fileObject) {
|
|
30337
30940
|
this.fileData = nextEdu.fileObject;
|
|
30338
30941
|
const uploaded = await this.saveAWSFile(this.uploadFolderBySection.education);
|
|
30339
30942
|
nextEdu.fileId = uploaded.fileId;
|
|
@@ -30341,16 +30944,20 @@ class PreviewComponent {
|
|
|
30341
30944
|
nextEdu.fileName = uploaded.fileName;
|
|
30342
30945
|
nextEdu.fileObject = null;
|
|
30343
30946
|
}
|
|
30344
|
-
|
|
30345
|
-
this.
|
|
30346
|
-
|
|
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;
|
|
30347
30960
|
}
|
|
30348
|
-
if (this.isAddingEducation())
|
|
30349
|
-
this.appendToMainModel('education', nextEdu);
|
|
30350
|
-
else if (idx !== null)
|
|
30351
|
-
this.updateInMainModel('education', idx, nextEdu);
|
|
30352
|
-
else
|
|
30353
|
-
return;
|
|
30354
30961
|
this.cancelEditEducation();
|
|
30355
30962
|
}
|
|
30356
30963
|
deleteEducation(index) {
|
|
@@ -30365,6 +30972,8 @@ class PreviewComponent {
|
|
|
30365
30972
|
...current,
|
|
30366
30973
|
education: current.education.filter((_, i) => i !== index),
|
|
30367
30974
|
});
|
|
30975
|
+
this.educationServerIds = this.reindexServerIdsAfterDelete(this.educationServerIds, index);
|
|
30976
|
+
this.educationLocallySaved = this.reindexSavedFlagsAfterDelete(this.educationLocallySaved, index);
|
|
30368
30977
|
if (this.editingEducationIndex() === index)
|
|
30369
30978
|
this.cancelEditEducation();
|
|
30370
30979
|
}
|
|
@@ -30374,74 +30983,39 @@ class PreviewComponent {
|
|
|
30374
30983
|
stayOnPreview() {
|
|
30375
30984
|
this.showBackConfirmPopup = false;
|
|
30376
30985
|
}
|
|
30377
|
-
|
|
30378
|
-
this.
|
|
30379
|
-
|
|
30380
|
-
|
|
30381
|
-
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
}, 2000); // 2 seconds
|
|
30395
|
-
},
|
|
30396
|
-
error: (err) => {
|
|
30397
|
-
console.error('Error while saving initial setup', err);
|
|
30398
|
-
this.showLoader = false;
|
|
30399
|
-
}
|
|
30400
|
-
});
|
|
30401
|
-
}
|
|
30402
|
-
mapBasicDetailsDashboard(data) {
|
|
30403
|
-
const basic = data.basicDetails;
|
|
30404
|
-
this.email = data.email;
|
|
30405
|
-
const jobTitle = (basic?.jobTitle ?? '').toString().trim() || 'Drone Pilot';
|
|
30406
|
-
console.log(basic);
|
|
30407
|
-
this.payloadUserName = basic?.firstName + ' ' + basic?.lastName;
|
|
30408
|
-
console.log(basic);
|
|
30409
|
-
return {
|
|
30410
|
-
providerName: this.providerName,
|
|
30411
|
-
providerId: this.providerId,
|
|
30412
|
-
UserRoleId: this.roleData.roleInfo.id,
|
|
30413
|
-
userDetail: {
|
|
30414
|
-
userBio: basic.summary || "",
|
|
30415
|
-
firstName: basic.firstName,
|
|
30416
|
-
lastName: basic.lastName,
|
|
30417
|
-
email: basic.email,
|
|
30418
|
-
address1: basic.address,
|
|
30419
|
-
address2: "",
|
|
30420
|
-
city: basic.city,
|
|
30421
|
-
state: basic.state,
|
|
30422
|
-
zipcode: basic.zipCode,
|
|
30423
|
-
county: "", // you don’t have this → keep empty or derive
|
|
30424
|
-
country: basic.country,
|
|
30425
|
-
latitude: null, // optional (if using Google API later)
|
|
30426
|
-
longitude: null,
|
|
30427
|
-
yearsActive: 0,
|
|
30428
|
-
isInitialSetupCompleted: true,
|
|
30429
|
-
id: this.payloadUserId,
|
|
30430
|
-
phoneNumber: basic.phone,
|
|
30431
|
-
yearsOfExperince: basic.yearsOfExperience,
|
|
30432
|
-
userJobTitle: [jobTitle],
|
|
30433
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
30434
|
-
datePattern: new Date().toLocaleDateString(),
|
|
30435
|
-
dateTimePattern: new Date().toLocaleString(),
|
|
30436
|
-
timePattern: new Date().toLocaleTimeString()
|
|
30437
|
-
}
|
|
30438
|
-
};
|
|
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
|
+
}
|
|
30439
31003
|
}
|
|
30440
31004
|
proceedBack() {
|
|
30441
31005
|
this.showBackConfirmPopup = false;
|
|
30442
31006
|
this.backToParent.emit();
|
|
30443
31007
|
this.store.currentStep.set(1);
|
|
30444
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
|
+
}
|
|
30445
31019
|
tempProfile;
|
|
30446
31020
|
isEditMode = signal(false, ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
30447
31021
|
toggleEdit() {
|
|
@@ -30454,141 +31028,165 @@ class PreviewComponent {
|
|
|
30454
31028
|
cancel() {
|
|
30455
31029
|
this.isEditMode.set(false);
|
|
30456
31030
|
}
|
|
30457
|
-
save() {
|
|
31031
|
+
async save() {
|
|
30458
31032
|
this.details.update(() => ({ ...this.tempProfile }));
|
|
30459
|
-
this.
|
|
30460
|
-
|
|
30461
|
-
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
{
|
|
30469
|
-
name: 'Basic Details',
|
|
30470
|
-
type: 'single',
|
|
30471
|
-
call: () => {
|
|
30472
|
-
const payload = this.mapBasicDetailsToUserDetail(this.resumeData);
|
|
30473
|
-
return this.userDetailService.initialSetUpCreateUserDetail(payload);
|
|
30474
|
-
}
|
|
30475
|
-
},
|
|
30476
|
-
{
|
|
30477
|
-
name: 'Work Experience',
|
|
30478
|
-
type: 'multiple',
|
|
30479
|
-
data: () => this.mapWorkExperience(this.resumeData),
|
|
30480
|
-
call: (item) => this.userExperienceService.createUserExperience(item)
|
|
30481
|
-
},
|
|
30482
|
-
{
|
|
30483
|
-
name: 'Education',
|
|
30484
|
-
type: 'multiple',
|
|
30485
|
-
data: () => this.mapEducation(this.resumeData),
|
|
30486
|
-
call: (item) => this.userEducation.createUserEducation(item)
|
|
30487
|
-
},
|
|
30488
|
-
{
|
|
30489
|
-
name: 'Certification',
|
|
30490
|
-
type: 'multiple',
|
|
30491
|
-
data: () => this.mapCertifications(this.resumeData),
|
|
30492
|
-
call: (item) => this.userDocumentService.createUserDocument(item)
|
|
30493
|
-
},
|
|
30494
|
-
{
|
|
30495
|
-
name: 'License',
|
|
30496
|
-
type: 'multiple',
|
|
30497
|
-
data: () => this.mapLicenses(this.resumeData),
|
|
30498
|
-
call: (item) => this.userDocumentService.createUserDocument(item)
|
|
30499
|
-
},
|
|
30500
|
-
{
|
|
30501
|
-
name: 'Skills',
|
|
30502
|
-
type: 'batch',
|
|
30503
|
-
data: () => this.mapSkills(this.resumeData),
|
|
30504
|
-
call: (items) => this.userSkillSetService.createUserSkillSet(items)
|
|
30505
|
-
},
|
|
30506
|
-
{
|
|
30507
|
-
name: 'Tools',
|
|
30508
|
-
type: 'batch',
|
|
30509
|
-
data: () => this.mapTools(this.resumeData),
|
|
30510
|
-
call: (items) => this.userToolService.createUserTool(items)
|
|
30511
|
-
}
|
|
30512
|
-
];
|
|
30513
|
-
// Skip empty sections and continue with available ones.
|
|
30514
|
-
const steps = rawSteps.filter((step) => {
|
|
30515
|
-
if (step.type === 'multiple' || step.type === 'batch') {
|
|
30516
|
-
const data = step.data(); // ✅ call function
|
|
30517
|
-
return Array.isArray(data) && data.length > 0;
|
|
30518
|
-
}
|
|
30519
|
-
return true;
|
|
30520
|
-
});
|
|
30521
|
-
this.statusList = [];
|
|
30522
|
-
from(steps)
|
|
30523
|
-
.pipe(concatMap((step) => {
|
|
30524
|
-
const stepStatus = {
|
|
30525
|
-
name: step.name,
|
|
30526
|
-
status: 'pending',
|
|
30527
|
-
successCount: 0,
|
|
30528
|
-
failCount: 0
|
|
30529
|
-
};
|
|
30530
|
-
this.statusList.push(stepStatus);
|
|
30531
|
-
// ✅ SINGLE API
|
|
30532
|
-
if (step.type === 'single') {
|
|
30533
|
-
return step.call(null).pipe(tap$1((res) => {
|
|
30534
|
-
if (res?.failed) {
|
|
30535
|
-
stepStatus.status = 'error';
|
|
30536
|
-
stepStatus.error = res.message;
|
|
30537
|
-
}
|
|
30538
|
-
else {
|
|
30539
|
-
stepStatus.status = 'success';
|
|
30540
|
-
}
|
|
30541
|
-
}), catchError((err) => {
|
|
30542
|
-
stepStatus.status = 'error';
|
|
30543
|
-
stepStatus.error = err.message;
|
|
30544
|
-
return of(null); // 🔥 continue flow
|
|
30545
|
-
}));
|
|
30546
|
-
}
|
|
30547
|
-
if (step.type === 'batch') {
|
|
30548
|
-
const stepData = step.data();
|
|
30549
|
-
return step.call(stepData).pipe(tap$1((res) => {
|
|
30550
|
-
if (res?.failed) {
|
|
30551
|
-
stepStatus.status = 'error';
|
|
30552
|
-
stepStatus.error = res.message;
|
|
30553
|
-
}
|
|
30554
|
-
else {
|
|
30555
|
-
stepStatus.status = 'success';
|
|
30556
|
-
stepStatus.successCount = step.data?.length ?? 0;
|
|
30557
|
-
}
|
|
30558
|
-
}), catchError((err) => {
|
|
30559
|
-
stepStatus.status = 'error';
|
|
30560
|
-
stepStatus.error = err.message;
|
|
30561
|
-
return of(null);
|
|
30562
|
-
}));
|
|
30563
|
-
}
|
|
30564
|
-
// ✅ MULTIPLE LOOP (like 2 work experiences)
|
|
30565
|
-
const stepData = step.data(); // ✅ executed AFTER username set
|
|
30566
|
-
return from(stepData).pipe(concatMap((item) => step.call(item).pipe(tap$1((res) => {
|
|
30567
|
-
if (res?.failed) {
|
|
30568
|
-
stepStatus.failCount++;
|
|
30569
|
-
}
|
|
30570
|
-
else {
|
|
30571
|
-
stepStatus.successCount++;
|
|
30572
|
-
}
|
|
30573
|
-
}), catchError(() => {
|
|
30574
|
-
stepStatus.failCount++;
|
|
30575
|
-
return of(null); // 🔥 continue next item
|
|
30576
|
-
}))), tap$1(() => {
|
|
30577
|
-
// final step status
|
|
30578
|
-
if (stepStatus.failCount > 0) {
|
|
30579
|
-
stepStatus.status = 'partial'; // 🔥 mixed result
|
|
30580
|
-
}
|
|
30581
|
-
else {
|
|
30582
|
-
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;
|
|
30583
31042
|
}
|
|
30584
|
-
}), catchError(() => of(null)) // 🔥 safety
|
|
30585
|
-
);
|
|
30586
|
-
}))
|
|
30587
|
-
.subscribe({
|
|
30588
|
-
complete: () => {
|
|
30589
|
-
console.log('All steps completed (with possible errors)');
|
|
30590
31043
|
}
|
|
30591
|
-
}
|
|
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
|
+
}
|
|
30592
31190
|
}
|
|
30593
31191
|
async saveAWSFile(sectionFolder) {
|
|
30594
31192
|
if (!this.fileData)
|
|
@@ -30702,6 +31300,81 @@ class PreviewComponent {
|
|
|
30702
31300
|
}
|
|
30703
31301
|
};
|
|
30704
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
|
+
}
|
|
30705
31378
|
mapWorkExperience(data) {
|
|
30706
31379
|
return data.workExperience.map((exp) => {
|
|
30707
31380
|
return {
|
|
@@ -30866,12 +31539,54 @@ class PreviewComponent {
|
|
|
30866
31539
|
const d = new Date(date + "-01"); // add day
|
|
30867
31540
|
return d.toISOString();
|
|
30868
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
|
+
}
|
|
30869
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 });
|
|
30870
|
-
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"] }] });
|
|
30871
31586
|
}
|
|
30872
31587
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: PreviewComponent, decorators: [{
|
|
30873
31588
|
type: Component,
|
|
30874
|
-
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"] }]
|
|
30875
31590
|
}], ctorParameters: () => [{ type: CredentialingStore }, { type: FileService }, { type: UserSkillSetService }, { type: UserToolService }, { type: UserDocumentService }, { type: UserEducationService }, { type: UserDetailService }, { type: UserExperienceService }, { type: i6.TokenService }, { type: undefined, decorators: [{
|
|
30876
31591
|
type: Inject,
|
|
30877
31592
|
args: [LIBRARY_CONFIG]
|
|
@@ -30881,10 +31596,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
30881
31596
|
type: Input
|
|
30882
31597
|
}], roleData: [{
|
|
30883
31598
|
type: Input
|
|
30884
|
-
}], cloudfrontUrl: [{
|
|
30885
|
-
type: Input
|
|
30886
31599
|
}], backToParent: [{
|
|
30887
31600
|
type: Output
|
|
31601
|
+
}], cloudfrontUrl: [{
|
|
31602
|
+
type: Input
|
|
30888
31603
|
}] } });
|
|
30889
31604
|
|
|
30890
31605
|
class CredentialingComponent {
|
|
@@ -32012,7 +32727,7 @@ class InitialProcessComponent {
|
|
|
32012
32727
|
var userDetail = this.userForm.value;
|
|
32013
32728
|
var findType = this.userViewRoles.find((a) => { return a.selected; })?.code;
|
|
32014
32729
|
return {
|
|
32015
|
-
targetUserId: this.userId,
|
|
32730
|
+
// targetUserId: this.userId,
|
|
32016
32731
|
userName: this.users?.text,
|
|
32017
32732
|
providerType: findType ?? ProviderType.ServiceProvider,
|
|
32018
32733
|
providerName: provider.companyName,
|