UNPKG

@rangertechnologies/ngnxt

Version:

This library was used for creating dymanic UI based on the input JSON/data

664 lines 187 kB
import { Component, Input, Output, EventEmitter, Inject } from '@angular/core'; import { ChangeService } from '../../services/change.service'; import { ErrorWrapper } from '../../model/errorWrapper'; import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import * as i0 from "@angular/core"; import * as i1 from "../../services/salesforce.service"; import * as i2 from "../../services/data.service"; import * as i3 from "../../services/change.service"; import * as i4 from "../../services/storage.service"; import * as i5 from "../../i18n.service"; import * as i6 from "../../components/custom-rich-text/custom-rich-text.component"; import * as i7 from "@angular/common"; import * as i8 from "@angular/forms"; import * as i9 from "../../components/datatable/datatable.component"; import * as i10 from "@angular/material/tooltip"; import * as i11 from "@angular/cdk/bidi"; import * as i12 from "../../components/image-cropper/component/image-cropper.component"; import * as i13 from "../../components/pick-location/pick-location.component"; import * as i14 from "../../components/custom-input/custom-input.component"; import * as i15 from "../../components/custom-text-area/custom-text-area.component"; import * as i16 from "../../components/custom-calendar/custom-calendar.component"; import * as i17 from "../../components/custom-date-picker/custom-date-picker.component"; import * as i18 from "../../components/custom-dropdown/custom-dropdown.component"; import * as i19 from "../../components/search-box/search-box.component"; import * as i20 from "../../components/file-upload/file-upload.component"; import * as i21 from "../../components/dependent-table/dependent-table.component"; import * as i22 from "../../components/custom-label/custom-label.component"; import * as i23 from "../../components/table-appendix/table-appendix.component"; import * as i24 from "../../components/custom-date/custom-date.component"; import * as i25 from "../../components/custom-time/custom-time.component"; import * as i26 from "../../components/custom-button/custom-button.component"; import * as i27 from "../../components/custom-model/custom-model.component"; import * as i28 from "../../components/custom-radio/custom-radio.component"; import * as i29 from "../../i18n.pipe"; export class QuestionbookComponent { sfService; dataService; changeService; storageService; i18nService; cdr; document; qbItem; questionItem; translatedQuestions = []; // VD 11Jun24 - translation changes questions; errorFieldId; labelValue; token; // VD 19JAN24 - getting token as input handleDropDown = new EventEmitter(); handleQuestion = new EventEmitter(); // VD 19Jul01 - get updated question dropDownData; //@ViewChild(ComponenthostDirective, { static: true }) dynamicHost: ComponenthostDirective; subQuestions; selectedFileData = []; loadComponent$ = new BehaviorSubject(false); // HA 28DEC23 Added styling and logics to load the book type questions qbRef; // HA 19DEC23 Declaration for Salesforce Styling style; langDirection = 'ltr'; bookStyle; subscription; isImageEdit = false; selectedImageElement; transform = { translateUnit: 'px', scale: 1, rotate: 0, flipH: false, flipV: false, translateH: 0, translateV: 0 }; canvasRotation = 0; cropper; loading = false; cropperMaxHeight = 0; cropperMaxWidth = 0; cropperMinHeight = 0; cropperMinWidth = 0; cropperStaticWidth = 0; cropperStaticHeight = 0; aspectRatio = 4 / 3; roundCropper = false; imageChangedEvent = null; alignImage = 'center'; isImageHover = false; // RS 17JAN2025 //Formats and returns file upload requirements from a JSON string getFileRequirements(fieldsMeta) { try { const metaData = typeof fieldsMeta === 'object' ? fieldsMeta : JSON.parse(fieldsMeta); const fileConfig = metaData[0]; if (!fileConfig) return ''; const requirements = []; if (fileConfig.allowedTypes?.length) { requirements.push(`Accepted formats: ${fileConfig.allowedTypes.join(', ')}`); } if (fileConfig.maxFileSize) { requirements.push(`Maximum size per file: ${fileConfig.maxFileSize / 1048576}MB`); } if (fileConfig.maxFiles) { requirements.push(`Maximum number of files: ${fileConfig.maxFiles}`); } return requirements.map(req => req.trim()).join('\n'); } catch (error) { console.error('Error parsing fieldsMeta:', error); return ''; } } hadleDropDownDependent = new EventEmitter(); // VD 06Sep24 calendar changes handleCalendarDate = new EventEmitter(); handleCalendarEvent = new EventEmitter(); bookQuestionsMap = new Map(); constructor(sfService, dataService, changeService, storageService, i18nService, cdr, document) { this.sfService = sfService; this.dataService = dataService; this.changeService = changeService; this.storageService = storageService; this.i18nService = i18nService; this.cdr = cdr; this.document = document; } ngOnInit() { // VD 13Sep24 changes this.processData(); // VD sep1324 changes - process data when databind value change this.subscription = this.changeService.customChange$.subscribe((changeValue) => { if (changeValue == 'dataBind') { this.processData(); } }); } ngAfterViewInit() { // SKS20MAR25 Ensure the child component is available before calling the method // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string // AP-28MAR25 Add Boolean setTimeout(() => { this.questions.forEach(ques => { let input; if (ques.type == 'Dropdown' || ques.type == 'Radio' || ques.type == 'Location') { input = {}; input['valueObj'] = ques.input || ques.defaultValue || null; input['referenceField'] = ques.referenceField || null; input['fromQuestionId'] = ques.id || null; } else if (ques.type == 'Date' || ques.type == 'DateTime') { input = {}; input['value'] = ques.input || ques.defaultValue || null; } else if (ques.type == 'List') { input = {}; ques['subText'] = typeof ques?.subText === 'object' ? ques?.subText : JSON.parse(ques['subText'] || {}); let apiObj = ques.subText; input['valueObj'] = ques.input || ques.defaultValue || null; input['field'] = apiObj.defaultField || Array.isArray(apiObj.field) ? apiObj.field[0] : apiObj.field || null; } else if (ques.type == 'Image') { input = ques.orgImageData; } else if (ques.type === 'Boolean') { ques.input = ques.input || ques.defaultValue || false; } else { input = ques.input || ques.defaultValue || null; } this.childEventCapture(input, ques); }); }); } // VD 13Sep24 changes // RS 09DEC24 Changed keys processData() { // HA 19DEC23 Logic for Question Book Styling(Slaesforce) // HA 09FEB24 Added ternary if (this.qbItem?.style) { // HA 28DEC23 Added styling and logics to load the book type questions this.qbItem.style = typeof this.qbItem.style === 'string' ? JSON.parse('' + this.qbItem?.style) : this.qbItem.style; this.qbItem.style.labelClass = this.qbItem.style?.labelClass ? this.qbItem.style?.labelClass : ""; this.qbItem.style.labelStyle = this.qbItem.style?.labelStyle ? this.qbItem.style?.labelStyle : ""; this.qbItem.style.labelValueStyle = this.qbItem.style?.labelValueStyle ? this.qbItem.style?.labelValueStyle : ""; this.qbItem.style.inputClass = this.qbItem.style?.inputClass ? this.qbItem.style?.inputClass : ""; this.qbItem.style.inputStyle = this.qbItem.style.inputStyle ? this.qbItem.style?.inputStyle : ""; this.qbItem.style.bookStyle = this.qbItem.style?.bookStyle ? this.qbItem.style?.bookStyle : ""; this.qbItem.style.showLabel = this.qbItem.style?.showLabel ? this.qbItem.style?.showLabel : true; } else { if (this.qbItem) { this.qbItem.style = { "labelClass": "", "labelStyle": "", "labelValueStyle": "", "inputClass": "", "inputStyle": "", "bookStyle": "", "showLabel": true, "direction": "ltr", "questionStyle": "" }; } } this.qbItem.style.questionStyle = this.qbItem?.style?.questionStyle ? this.qbItem?.style.questionStyle : ""; this.langDirection = this.qbItem.style.direction = this.qbItem?.style?.direction ? this.qbItem?.style?.direction : 'ltr'; this.bookStyle = this.qbItem?.style?.bookStyle; this.questions.forEach(element => { var htmlElement = element?.questionText; var textOnly = htmlElement?.replace(/<[^>]*>/g, ''); element.questionText = textOnly; // HA 19DEC23 Logic for Question Styling(Slaesforce) // HA 28DEC23 Added styling and logics to load the book type questions if (element.type === 'Book') { if (element['qbReferenceQuestions']) { var qb = typeof element['qbReferenceQuestions'] === 'object' ? element['qbReferenceQuestions'] : JSON.parse(element['qbReferenceQuestions']); if (qb['questionbook'].style) { qb['questionbook'].style = typeof qb['questionbook'].style === 'string' ? JSON.parse('' + qb['questionbook'].style) : qb['questionbook'].style; } else { qb['questionbook'].style = this.qbItem.style; } // Book type question styling qb['questionbook'].style.labelClass = qb['questionbook'].style?.labelClass ? qb['questionbook'].style?.labelClass : ""; qb['questionbook'].style.labelStyle = qb['questionbook'].style?.labelStyle ? qb['questionbook'].style?.labelStyle : ""; qb['questionbook'].style.labelValueStyle = qb['questionbook'].Style__?.labelValueStyle ? qb['questionbook'].style.labelValueStyle : ""; qb['questionbook'].style.inputClass = qb['questionbook'].style?.inputClass ? qb['questionbook'].style?.inputClass : ""; qb['questionbook'].style.inputStyle = qb['questionbook'].style?.inputStyle ? qb['questionbook'].style?.inputStyle : ""; qb['questionbook'].style.bookStyle = qb['questionbook'].style?.bookStyle ? qb['questionbook'].style?.bookStyle : ""; qb['questionbook'].style.showLabel = qb['questionbook'].style?.showLabel ? qb['questionbook'].style?.showLabel : true; qb['questionbook'].style.questionStyle = qb['questionbook'].style?.questionStyle ? qb['questionbook'].style?.questionStyle : ""; qb['langDirection'] = qb['questionbook'].style.direction ? qb['questionbook'].style?.direction : this.langDirection; // Loop for each question in the book qb['questionbook']?.subQuestions?.forEach(questionFromBook => { questionFromBook.style = questionFromBook.style ? questionFromBook.style : qb['questionbook'].style; questionFromBook.style.labelClass = questionFromBook.style?.labelClass ? questionFromBook.style?.labelClass : ""; questionFromBook.style.labelStyle = questionFromBook.style?.labelStyle ? questionFromBook.style?.labelStyle : ""; questionFromBook.style.labelValueStyle = questionFromBook.style?.labelValueStyle ? questionFromBook.style?.labelValueStyle : ""; questionFromBook.style.inputClass = questionFromBook.style?.inputClass ? questionFromBook.style?.inputClass : ""; questionFromBook.style.inputStyle = questionFromBook.style?.inputStyle ? questionFromBook.style?.inputStyle : ""; questionFromBook.style.bookStyle = questionFromBook.style?.bookStyle ? questionFromBook.style?.bookStyle : ""; questionFromBook.style.showLabel = questionFromBook.style?.showLabel ? questionFromBook.style?.showLabel : true; questionFromBook.style.questionStyle = questionFromBook.style?.questionStyle ? questionFromBook.style?.questionStyle : ""; questionFromBook['langDirection'] = questionFromBook.style?.direction ? questionFromBook.style?.direction : this.langDirection; }); element['qbItem'] = qb['questionbook']; } } element.style = element.style ? typeof element.style === 'string' ? JSON.parse('' + element?.style) : element?.style : this.qbItem?.style; element.style.labelClass = element.style?.labelClass ? element.style?.labelClass : ""; element.style.labelStyle = element.style?.labelStyle ? element.style?.labelStyle : ""; element.style.labelValueStyle = element.style?.labelValueStyle ? element.style.labelValueStyle : ""; element.style.inputClass = element.style?.inputClass ? element.style?.inputClass : ""; element.style.inputStyle = element.style.inputStyle ? element.style?.inputStyle : ""; element.style.bookStyle = element.style?.bookStyle ? element.style?.bookStyle : ""; element.style.showLabel = element.style?.showLabel ? element.style?.showLabel : true; element.style.questionStyle = element.style?.questionStyle ? element.style?.questionStyle : ""; element['langDirection'] = element.style?.direction ? element.style?.direction : this.langDirection; //VD 02Aug24 dependent field show/hide changes // RS 09DEC24 Changed keys // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string if (element.referenceField) { element.dependentRef = element.referenceField; } if (element.subText) { element['subText'] = typeof element?.subText === 'object' ? element?.subText : JSON.parse(element['subText'] || {}); let dependencyObj = element.subText; if (dependencyObj.isDependentField) { element.isHidden = true; } // VD 07Aug24 - isDependentField change this.subscription = this.changeService.changeAnnounced$.subscribe((changeValue) => { // VD 20Aug24 - isDependentField change if (changeValue != undefined && dependencyObj.isDependentField) { if (changeValue.valueObj != undefined && changeValue.fromQuestionId == dependencyObj.sourceQuestionId) { // element.isHiddenField = true; if (dependencyObj.dependentValue == changeValue.valueObj) { element.isHidden = false; element.input = ''; element.selectedValue = undefined; if (element.dependentRef == changeValue.referenceField) { element.referenceField = changeValue.referenceField; } } else { element.isHidden = true; element.input = ''; element.selectedValue = undefined; this.hideDependentElements(element.id); // if there is no dependent selection remove the reference if (element.dependentRef == changeValue.referenceField) { element.referenceField = ''; } } } } }); this.storageService.update(element); } // sunday comment // if (element.type === 'Calendar') { // this.openCalendarComponent(element); // } }); this.subQuestions = []; this.setSubQuestions(this.questions); this.processTranslatedQuestions(); } // VD 07Aug24 - isDependentField change // AP-26MAR25 Ensure ques.subText is always an object by parsing it if it's a string hideDependentElements(elementId) { let elementsToHide = this.questions.filter(el => { if (el.subText) { el['subText'] = typeof el?.subText === 'object' ? el?.subText : JSON.parse(el['subText'] || {}); let depObj = el.subText; return depObj.sourceQuestionId == elementId; } return false; }); elementsToHide.forEach(el => { el.isHidden = true; el.input = ''; el.selectedValue = undefined; this.storageService.update(el); // Recursively hide elements dependent on this one this.hideDependentElements(el.id); }); } clearSQError(quesId) { var sqList = this.subQuestions?.filter((item) => item.id == quesId); for (var sq of sqList) { sq.error = null; } } // VD 11Jun24 - translation changes processTranslatedQuestions() { if (this.translatedQuestions && this.translatedQuestions.length > 0) { this.questions.forEach((orgQuestion) => { this.translatedQuestions.forEach((transQuestion) => { if (orgQuestion.uniqueIdentifier == transQuestion.identifier) { orgQuestion.questionText = transQuestion.label; orgQuestion.question = transQuestion.placeHolder; } }); }); // console.log('originalQuestion' + this.questions); // console.log(this.questions); } } setSubQuestions(records) { this.subQuestions.push(records); } // Capture and Process Child Event childEventCapture(event, ques) { let dropdownData = {}; this.changeService.announceChange(event); if (ques) { if (ques.type == 'Dropdown' || ques.type == 'Radio') { // HA 19JAN24 Correction of dropdown values ques.selectedObj = event.valueObj ? event.valueObj : null; ques.input = event.selectedObj ? event.selectedObj : event.valueObj; ques.selectedValue = event.valueObj ? event.valueObj : null; // emit the dropdown data to parent dropdownData['event'] = event.valueObj; dropdownData['ques'] = ques; if (dropdownData['event']) { this.handleDropDown.emit(dropdownData); } // VD 21DEC23 - dependent field change } else if (ques.type == 'Location') { ques.input = event.valueObj; ques.selectedValue = event.valueObj ? event.valueObj.address : null; } else if (ques.type == 'File') { ques.input = this.selectedFileData = event; // Handle file validation errors if (Array.isArray(event) && event.length === 0) { // No files uploaded when required ques.error = ques.Error_Message__c ? new ErrorWrapper() : null; } else if (Array.isArray(event) && event.length > 0) { // Files were successfully uploaded ques.error = null; } // If there were validation errors from the file upload component if (event?.validationErrors && event.validationErrors.length > 0) { ques.error = new ErrorWrapper(); ques.error.message = event.validationErrors[0]; // Use the first validation error } } else if (ques.type == 'Date' || ques.type == 'DateTime') { // HA 24JAN24 Converting Date and DateTime to UTC if (event.value) { let d = new Date(event.value); let utcString = d?.toISOString(); ques.input = utcString; } else { ques.input = ''; } } else if (ques.type == 'List') { // VD 20Aug24 handling the list type handle multiple object values ques.input = this.dataService.getValue(event.valueObj, event.field); } //RS 06JAN25 else if (ques.type === 'RichTextArea') { ques.input = event || null; } // AP-28MAR25 Add Boolean else if (ques.type === 'Boolean') { ques.input = event; } else { ques.input = event; } //RS 06JAN25 // Validate error message - modified to handle rich text const hasError = ques.Error_Message__c && ((ques.type === 'File' && ques.input.length === 0) || (ques.type === 'RichTextArea' && (!ques.input || ques.input.trim() === '')) || (ques.type !== 'File' && ques.Type__c !== 'RichTextArea' && !ques.input)); ques.error = hasError ? new ErrorWrapper() : null; ques.error = hasError ? new ErrorWrapper() : null; // Once the right value is stored in ques.input store the ques (with input) in storageService this.storageService.update(ques); // VD 19Jul01 - get updated question this.handleQuestion.emit(ques); } } deleteFile(fileData) { console.log('fileDATA', fileData); } getDropDown(event) { this.hadleDropDownDependent.emit(event); } // VD 06Sep24 calendar changes getCurrentCalendar(event) { this.handleCalendarDate.emit(event); } getCalendarEvent(event) { if (event) { this.handleCalendarEvent.emit(event); } } // VD 06Sep24 remove the special charecters and tags // from the questionText removeCharacters(questionText) { let updatedText = questionText?.replace(/<[^>]*>/g, ''); return updatedText; } // sunday comment // openCalendarComponent(ques) { // const viewContainerRef = this.dynamicHost.viewContainerRef; // if(viewContainerRef){ // viewContainerRef.clear(); // const componentRef = viewContainerRef.createComponent(CustomCalendarComponent); // componentRef.instance.question = ques; // componentRef.instance.dateSelected.subscribe((event: any) => this.getCurrentCalendar(event)); // componentRef.instance.eventSelected.subscribe((event:any) => this.getCalendarEvent(event)) // } // } isCalendarModalOpen = false; calendarModalTitle; calendarModalSize; calendarSaveButtonValue; referenceQuestions = []; qbRefrenceBook; modalCalendarModalFooter; openCalendarModal(event) { this.isCalendarModalOpen = true; this.qbRefrenceBook = event.qbRefrenceBook; this.referenceQuestions = event.referenceQuestions; this.calendarModalTitle = event.modalTitle; this.calendarModalSize = event.modalSize; this.calendarSaveButtonValue = event.saveButtonValue; this.modalCalendarModalFooter = event.modalFooter; } onCalendarModalSave() { this.changeService.dataChanges('calendar-modal-save'); } handleQuestionEvent(event) { console.log('handle question here'); } closeCalendarModal(event) { this.isCalendarModalOpen = false; } // SKS25MAR25 image add async fileChangeEvent(ques, event) { const file = event.target.files[0]; if (file) { try { const imageData = await this.readFileAsDataURL(file); ques.imageData = imageData; ques.orgImageData = imageData; // Initialize logo size if not already set if (!ques.imageSize) { ques.imageSize = { width: 150, height: 150 }; } await this.childEventCapture(imageData, ques); } catch (error) { console.error("Error reading file:", error); } } } readFileAsDataURL(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); reader.readAsDataURL(file); }); } async onImageEdit(ques) { this.isImageEdit = true; this.selectedImageElement = ques; console.log("onImageEdit"); } async onImageDelete(ques) { try { ques.imageData = ''; ques.orgImageData = ''; // Initialize logo size if not already set if (!ques.imageSize) { ques.imageSize = { width: 150, height: 150 }; } await this.childEventCapture(ques.imageData, ques); } catch (error) { console.error("Error reading file:", error); } } // SKS25MAR25 image edit functions flipHorizontal() { this.transform = { ...this.transform, flipH: !this.transform.flipH }; } flipVertical() { this.transform = { ...this.transform, flipV: !this.transform.flipV }; } resetImage() { this.canvasRotation = 0; this.cropper = undefined; this.transform = { translateUnit: 'px', scale: 1, rotate: 0, flipH: false, flipV: false, translateH: 0, translateV: 0 }; } zoomOut() { this.transform = { ...this.transform, scale: this.transform.scale - .1 }; } zoomIn() { this.transform = { ...this.transform, scale: this.transform.scale + .1 }; } rotateLeft() { this.loading = true; setTimeout(() => { this.canvasRotation--; this.flipAfterRotate(); }); } rotateRight() { this.loading = true; setTimeout(() => { this.canvasRotation++; this.flipAfterRotate(); }); } moveLeft() { this.transform = { ...this.transform, translateH: this.transform.translateH - 1 }; } moveRight() { this.transform = { ...this.transform, translateH: this.transform.translateH + 1 }; } moveDown() { this.transform = { ...this.transform, translateV: this.transform.translateV + 1 }; } moveUp() { this.transform = { ...this.transform, translateV: this.transform.translateV - 1 }; } flipAfterRotate() { const flippedH = this.transform.flipH; const flippedV = this.transform.flipV; this.transform = { ...this.transform, flipH: flippedV, flipV: flippedH, translateH: 0, translateV: 0 }; } async imageCropped(event) { try { const base64 = await this.convertBlobToBase64(event.objectUrl); this.selectedImageElement.imageData = base64; await this.childEventCapture(base64, this.selectedImageElement); // console.log('CROPPED', event); } catch (error) { console.error("Error in imageCropped:", error); } } cropperReady(sourceImageDimensions) { // console.log('Cropper ready', sourceImageDimensions); this.loading = false; } // SKS25MAR25 blob to base 64 converter convertBlobToBase64(objectUrl) { return fetch(objectUrl) .then(response => response.blob()) .then(blob => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result); reader.onerror = error => reject(error); }); }); } closeModal() { this.isImageEdit = false; } // SKS28MAR25 for nested book read readQuestions(qbId, ques) { let questions = []; // Added ternary to avoid undefined for (var sq in ques.bookQuestionsMap[qbId].subQuestions) { let q = ques.bookQuestionsMap[qbId].subQuestions[sq]; // if question is there process if (q) { questions.push(q); } } return questions; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: QuestionbookComponent, deps: [{ token: i1.SalesforceService }, { token: i2.DataService }, { token: i3.ChangeService }, { token: i4.StorageService }, { token: i5.I18nService }, { token: i0.ChangeDetectorRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: QuestionbookComponent, selector: "lib-questionbook", inputs: { qbItem: "qbItem", questionItem: "questionItem", translatedQuestions: "translatedQuestions", questions: "questions", errorFieldId: "errorFieldId", labelValue: "labelValue", token: "token", dropDownData: "dropDownData" }, outputs: { handleDropDown: "handleDropDown", handleQuestion: "handleQuestion", hadleDropDownDependent: "hadleDropDownDependent", handleCalendarDate: "handleCalendarDate", handleCalendarEvent: "handleCalendarEvent" }, providers: [ChangeService], ngImport: i0, template: "<!-- HA 20DEC23 Book Style from salesforce -->\n<!-- HA 28DEC23 Removed IsShengel(removal of shengel values applies for this reason) and direct styling of books to avoid styling issues-->\n<!-- HA 18JAN24 Added class for styling -->\n<div [style]=\"bookStyle\" class=\"content-box form-group\">\n <div class=\"form-row\">\n <!-- HA 20DEC23 Directive and Question Style from salesforce -->\n <!-- RA09DEC24 Changed keys-->\n <div [class]=\"'col-lg-' + ques.size + ' paddingnone'\" *ngFor=\"let ques of questions;let i = index\"\n [id]=\"ques.id\" [dir]=\"ques.langDirection\" [style]=\"ques?.style?.questionStyle\">\n <!-- Sub Question Label -->\n <!-- HA 20DEC23 Label Style from salesforce -->\n <!-- VD 09May24 is hide field change-->\n <div *ngIf=\"!ques.isHidden\" class=\"nxtInputContainer\">\n <div *ngIf=\"ques.type === 'DateTime'\">\n\n </div>\n <!-- VD 20JUN24 - help text changes-->\n <!-- VD 01Aug24 - validation change-->\n <!-- // VD 02Aug24 - label value style-->\n <!-- AP-28MAR25 Show label if enabled and not a Boolean type -->\n <div [ngClass]=\"{ down2: qbItem?.progressBar }\"\n *ngIf=\"ques.style?.showLabel ? (ques.style?.showLabel && ques.type !== 'Boolean'): (ques.style?.showLabel !== false && ques.type !== 'Boolean')\"\n [style]=\"ques.style?.labelStyle\">\n <span [class]=\"'dis-flex shengel-myt-font3 myt-font7 '\" [style]=\"ques.style?.labelValueStyle\">{{\n removeCharacters(ques?.questionText) }}\n <div *ngIf=\"ques.isOptional\" style=\"color: red;\">*</div>\n <!-- SKS13MAR25 only show on file type -->\n <!-- RS 17JAN2025 -->\n <!-- Displays icons with tooltips help text -->\n <div *ngIf=\"ques.questionText && ques?.helpText && ques.type === 'File'\" class=\"icon\"\n [matTooltip]=\"ques?.helpText\" matTooltipClass=\"white-tooltip\">i</div>\n <!-- RS 17JAN2025 -->\n <!-- Displays icons with tooltips for file requirements -->\n <div class=\"icon\" *ngIf=\"ques.fieldsMeta && ques.type === 'File'\"\n [matTooltip]=\"getFileRequirements(ques.fieldsMeta)\" matTooltipClass=\"white-tooltip\"\n style=\"margin-left: 4px;\">i</div>\n </span>\n </div>\n <!-- // VD 12Jun24 - readonly change-->\n <!-- DateTime -->\n <div *ngIf=\"ques.type === 'DateTime'\">\n <app-custom-date-picker [minDate]=\"ques.minDate\" [error]=\"ques.error\"\n [errorMessage]=\"ques.errorMessage\" [readOnly]=\"ques.isReadOnly\" [date]=\"ques.input\"\n (dateChange)=\"childEventCapture($event, ques)\">\n </app-custom-date-picker>\n </div>\n\n <!-- Date-->\n <div *ngIf=\"ques.type === 'Date'\">\n <app-custom-date [date]=\"ques.input\" [error]=\"ques.error\" [errorMessage]=\"ques.errorMessage\"\n [readOnly]=\"ques.isReadOnly\" (dateChange)=\"childEventCapture($event, ques)\">\n </app-custom-date>\n </div>\n\n <!-- Time-->\n <div *ngIf=\"ques.type === 'Time'\">\n <app-custom-time [time]=\"ques.input\" [error]=\"ques.error\" [errorMessage]=\"ques.errorMessage\"\n [readOnly]=\"ques.isReadOnly\" (timeChange)=\"childEventCapture($event, ques)\">\n </app-custom-time>\n </div>\n <!-- calendar -->\n <div *ngIf=\"ques.type === 'Calendar'\">\n <app-custom-calendar [question]=\"ques\" (eventSelected)=\"getCalendarEvent($event)\"\n (dateSelected)=\"getCurrentCalendar($event)\" (openModal)=\"openCalendarModal($event)\"\n (closeModal)=\"closeCalendarModal($event)\"></app-custom-calendar>\n <!-- model used in calendar component -->\n <app-custom-model *ngIf=\"isCalendarModalOpen\" [modalTitle]=\"calendarModalTitle\"\n [isModalOpen]=\"isCalendarModalOpen\" [modalSize]=\"calendarModalSize\"\n [saveButtonValue]=\"calendarSaveButtonValue\" [modalFooter]=\"modalCalendarModalFooter\"\n (saveButtonEmit)=\"onCalendarModalSave()\" (cancelButtonEmit)=\"closeCalendarModal($event)\">\n <lib-questionbook [qbItem]=\"qbRefrenceBook\" [questions]=\"referenceQuestions\"\n (handleQuestion)=\"handleQuestionEvent($event)\"></lib-questionbook>\n </app-custom-model>\n </div>\n <!-- Text -->\n <div *ngIf=\"ques.type === 'Text' || ques.type === 'Link'\">\n <app-custom-input [value]=\"ques.input\" [ngClassValue]=\"{\n 'dis-flex dt-line date-line bookText boxoutline myt-font1': qbItem.progressBar,\n textBox: !qbItem.progressBar\n }\" [question]=\"ques\" [readOnly]=\"ques.isReadOnly\" [idValue]=\"ques.trackingId\"\n [focusEvent]=\"clearSQError(ques.id)\" [error]=\"ques.error\" [placeholder]=\"ques.question\"\n (inputValue)=\"childEventCapture($event, ques)\">\n </app-custom-input>\n </div>\n\n <!-- for pick location -->\n <!-- VD 21DEC23 - dependent field change -->\n <div *ngIf=\"ques.type === 'Location'\">\n <!-- HA10012024 Added Api key as input -->\n <app-pick-location [apiKey]=\"qbItem['apiKey']\" [address]=\"ques.selectedValue\" [question]=\"ques\"\n (locationSelected)=\"childEventCapture($event, ques)\">\n </app-pick-location>\n </div>\n\n <!-- for text area -->\n <!-- AP-02APR25 - default placeholder empty string -->\n <div *ngIf=\"ques.type === 'TextArea'\">\n <app-custom-text-area [question]=\"ques\" [readOnly]=\"ques.isReadOnly\" [value]=\"ques.input\" [rows]=\"3\"\n [error]=\"ques.error\" [placeholder]=\"ques.question || ''\"\n (textareaValueChange)=\"childEventCapture($event, ques)\"></app-custom-text-area>\n </div>\n <!-- RS 06JAN25 -->\n <!-- for rich text editor -->\n <div *ngIf=\"ques.type === 'RichTextArea'\">\n <app-custom-rich-text [question]=\"ques\" [readOnly]=\"ques.isReadOnly\" [value]=\"ques.input || ''\"\n [error]=\"ques.error\" [placeholder]=\"ques.question\"\n (textValueChange)=\"childEventCapture($event, ques)\">\n </app-custom-rich-text>\n </div>\n\n <!-- SKS25MAR25 Image -->\n <div *ngIf=\"ques.type === 'Image'\" (mouseenter)=\"isImageHover = true;\"\n (mouseleave)=\"isImageHover = false;\">\n <div *ngIf=\"isImageHover\" style=\"display: flex; justify-content: end;\">\n <svg *ngIf=\"!ques.isReadOnly\" (click)=\"onImageEdit(ques)\" width=\"16\" height=\"16\"\n viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M11.1067 6.07174L9.92833 4.8934L2.16667 12.6551V13.8334H3.345L11.1067 6.07174ZM12.285 4.8934L13.4633 3.71507L12.285 2.53674L11.1067 3.71507L12.285 4.8934ZM4.035 15.5001H0.5V11.9642L11.6958 0.768403C11.8521 0.612177 12.064 0.524414 12.285 0.524414C12.506 0.524414 12.7179 0.612177 12.8742 0.768403L15.2317 3.1259C15.3879 3.28218 15.4757 3.4941 15.4757 3.71507C15.4757 3.93604 15.3879 4.14796 15.2317 4.30424L4.03583 15.5001H4.035Z\"\n fill=\"#6C757D\" />\n </svg>\n <svg *ngIf=\"!ques.isReadOnly\" (click)=\"onImageDelete(ques)\" width=\"16\" height=\"16\"\n viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14 3.98726C11.78 3.76726 9.54667 3.65393 7.32 3.65393C6 3.65393 4.68 3.7206 3.36 3.85393L2 3.98726\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M5.6665 3.31362L5.81317 2.44028C5.91984 1.80695 5.99984 1.33362 7.1265 1.33362H8.87317C9.99984 1.33362 10.0865 1.83362 10.1865 2.44695L10.3332 3.31362\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path\n d=\"M12.5667 6.09375L12.1334 12.8071C12.06 13.8537 12 14.6671 10.14 14.6671H5.86002C4.00002 14.6671 3.94002 13.8537 3.86668 12.8071L3.43335 6.09375\"\n stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M6.88647 11.0004H9.10647\" stroke=\"#FF2C10\" stroke-width=\"1.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M6.3335 8.33325H9.66683\" stroke=\"#FF2C10\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" />\n </svg>\n </div>\n <img *ngIf=\"ques.isReadOnly\" [src]=\"ques.imageData\" />\n <div *ngIf=\"!ques.isReadOnly\" class=\"logo-container\">\n <!-- Logo preview area -->\n <div class=\"logo-preview\" *ngIf=\"ques.input\">\n <img [src]=\"ques.imageData\" />\n </div>\n\n <!-- Upload button -->\n <div *ngIf=\"!ques.imageData\" class=\"logo-upload-placeholder\">\n <label for=\"logo-upload-{{i}}\" class=\"logo-upload-label\">\n <img src=\"../assets/icons/Image.svg\" alt=\"Upload\" />\n <span>Upload Image</span>\n </label>\n <input type=\"file\" id=\"logo-upload-{{i}}\" accept=\"image/*\"\n (change)=\"fileChangeEvent(ques, $event)\" style=\"display: none;\" />\n </div>\n </div>\n </div>\n\n <!-- Email -->\n <div *ngIf=\"ques.type === 'Email'\">\n <input type=\"email\" readOnly=\"ques.isReadOnly\" [(ngModel)]=\"ques.input\" [id]=\"ques.id\" required=\"\"\n (focus)=\"clearSQError(ques.id)\" style.border-color=\"{{ ques.error ? 'red' : '' }}\"\n placeholder=\"{{ ques.question }}\" />\n </div>\n\n <!--AP-24MAR25 Number -->\n <!-- AP-02APR25 - default placeholder empty string -->\n <div *ngIf=\"ques.type === 'Number'\">\n <input type=\"number\" [attr.readonly]=\"ques.isReadOnly ? true : null\" [value]=\"ques.input\"\n [id]=\"ques.id\" required (focus)=\"clearSQError(ques.id)\"\n [style.borderColor]=\"ques.error ? 'red' : ''\" [placeholder]=\"ques.question || ''\"\n (inputValue)=\"childEventCapture($event, ques)\" />\n </div>\n <!-- AP-28MAR25 Boolean -->\n <div *ngIf=\"ques.type === 'Boolean'\" style=\"display: flex; align-items:center; gap:10px;\">\n <div>{{ ques.questionText }}</div>\n <input type=\"checkbox\" [attr.disabled]=\"ques.isReadOnly ? true : null\" [checked]=\"ques.input\"\n [id]=\"ques.id\" required (focus)=\"clearSQError(ques.id)\"\n [style.borderColor]=\"ques.error ? 'red' : ''\"\n (change)=\"childEventCapture($event.target.checked, ques)\" /> \n </div> \n\n <!-- SKS21MAR25 line -->\n <div *ngIf=\"ques.type === 'Line'\">\n <hr class=\"custom-line\" style=\"display: inline-flex\" />\n </div>\n <!-- Table -->\n <!-- RS 03FEB2025 -->\n <!-- Added handleTableSave to handle table data persistence, enabling saving table contents to local storage when save button is clicked -->\n <div *ngIf=\"ques.type === 'Table'\" class=\"\">\n <!-- <app-custom-table [question]=\"ques\" [apiMeta]=\"ques.subText\"\n (valueChange)=\"childEventCapture($event, ques); clearSQError(ques.id)\">\n </app-custom-table> -->\n <!-- SKS13MAR25 data table change -->\n <nxt-datatable isEditRow isDeleteRow actionButton isButtons [question]=\"ques\" from=\"formBuilder\"\n (valueChange)=\"childEventCapture($event, ques); clearSQError(ques.id)\" [apiMeta]=\"ques.subText\" [tableConfig]=\"ques.tableConfig\"\n tableId=\"\" direction=\"ltr\" tableWidth=\"auto\">\n </nxt-datatable>\n </div>\n\n <!-- Table Appendix -->\n <div *ngIf=\"ques.type === 'TableAppendix'\" class=\"\">\n <app-table-appendix [question]=\"ques\"\n (valueChange)=\"childEventCapture($event, ques); clearSQError(ques.id)\">\n </app-table-appendix>\n </div>\n <!-- list -->\n <!-- VD 20Aug24 used correct attribute -->\n <!-- AP-02APR25 - default placeholder empty string -->\n <div *ngIf=\"ques.type === 'List'\" class=\"\">\n <lib-search-box [question]=\"ques\" [readOnly]=\"ques.isReadOnly\" [apiMeta]=\"ques.subText\"\n [id]=\"ques.id\" [placeHolderText]=\"ques.question || ''\" [filterName]=\"ques.input\"\n (searchValueChange)=\"childEventCapture($event, ques)\">\n </lib-search-box>\n </div>\n\n <!-- Dropdown -->\n <!-- HA 09FEB24 Added condition of sqOption to the dropdown -->\n <div *ngIf=\"ques?.type === 'Dropdown' && ques?.options\" class=\"\">\n <!-- HA 20DEC23 For Translation --> <!-- VD 19JAN24 - getting token as input -->\n <!-- AP 10FEB25 - Dynamically binding selectedValue based on isShengel condition -->\n <app-custom-dropdown [options]=\"ques.options\" [token]=\"token\" [apiMeta]=\"ques.subText\"\n [id]=\"ques.id\" [selectedValue]=\"qbItem.isShengel ? ques.input : ques.selectedValue\"\n placeholder=\"---{{'select' | i18n:i18nService.currentLanguage}}---\"\n [errorMessage]=\"ques.errorMessage\" [error]=\"ques.error\" [referenceField]=\"ques.referenceField\"\n [readOnly]=\"ques.isReadOnly\" [question]=\"ques\"\n (valueChange)=\"childEventCapture($event, ques); clearSQError(ques.id)\">\n </app-custom-dropdown>\n <i class=\"fa fa-check \" aria-hidden=\"true\" *ngIf=\"ques?.input?.length > 0\"></i>\n </div>\n <!-- // VD 02Aug24 custom-radio component -->\n <div *ngIf=\"ques.type === 'Radio' && ques?.options\" class=\"\">\n <app-custom-radio [options]=\"ques.options\" [token]=\"token\" [apiMeta]=\"ques.subText\" [id]=\"ques.id\"\n [selectedValue]=\"ques.selectedValue\" [errorMessage]=\"ques.errorMessage\" [error]=\"ques.error\"\n [referenceField]=\"ques.referenceField\" [readOnly]=\"ques.isReadOnly\"\n (valueChange)=\"childEventCapture($event, ques); clearSQError(ques.id)\">\n </app-custom-radio>\n </div>\n\n <!-- Attachment / Files -->\n <div *ngIf=\"ques.type === 'File'\" class=\"\">\n <app-file-upload [limitFileUploading]=\"5\" [error]=\"ques.error\" [question]=\"ques\"\n [allFiles]=\"ques.input\" [tableFile]=\"false\" (selectedFileData)=\"childEventCapture($event, ques)\"\n (deletedFileData)=\"deleteFile($event)\" [isDeleteFileButtonVisible]=\"true\"></app-file-upload>\n </div>\n <div *ngIf=\"ques.type === 'PopUpMessage'\" class=\"\">\n <app-dependent-table [alertMessage]=\"ques.errorMessage\">\n </app-dependent-table>\n </div>\n <div *ngIf=\"ques.type === 'Label'\" class=\"\">\n <app-custom-label [labelStyle]=\"ques.title\" [labelValue]=\"ques.question\">\n </app-custom-label>\n </div>\n <!-- // VD 02Aug24 image component -->\n <!-- <div *ngIf=\"ques.type === 'Image'\" class=\"\">\n <app-custom-image [question]=\"ques\">\n </app-custom-image>\n </div> -->\n <!-- 08NOV23 - button type question added -->\n <!-- Button -->\n <div *ngIf=\"ques.type === 'Button'\" class=\"\">\n <app-custom-button [height]=\"'50px'\" [width]=\"'150px'\" [buttonText]=\"ques?.question\"\n [value]=\"ques?.question\" (buttonValue)=\"childEventCapture($event, ques)\">\n </app-custom-button>\n </div>\n <!-- HA 20DEC23 This is to load book type questions-->\n <div *ngIf=\"ques.type === 'Book'\">\n <!-- HA 09FEB24 Added ternary operator -->\n <lib-questionbook [qbItem]=\"ques.qbItem\" [labelValue]=\"labelValue\"\n [questions]=\"readQuestions(ques.qbReference, ques.qbReferenceQuestions)\"\n (handleDropDown)=\"getDr