UNPKG

@rangertechnologies/ngnxt

Version:

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

1 lines 2.25 MB
{"version":3,"file":"rangertechnologies-ngnxt.mjs","sources":["../../../projects/nxt-app/src/lib/nxt-app.service.ts","../../../projects/nxt-app/src/lib/nxt-app.component.ts","../../../projects/nxt-app/src/lib/model/errorWrapper.ts","../../../projects/nxt-app/src/lib/pipe/NxtDate.pipe.ts","../../../projects/nxt-app/src/lib/components/nxt-datepicker/custom-datepicker/custom-datepicker.component.ts","../../../projects/nxt-app/src/lib/components/nxt-datepicker/custom-datepicker/custom-datepicker.component.html","../../../projects/nxt-app/src/lib/services/data/data.service.ts","../../../projects/nxt-app/src/lib/components/nxt-datepicker/hijri-datepicker/hijri-datepicker.component.ts","../../../projects/nxt-app/src/lib/components/nxt-datepicker/hijri-datepicker/hijri-datepicker.component.html","../../../projects/nxt-app/src/lib/components/nxt-datepicker/nxt-datepicker.component.ts","../../../projects/nxt-app/src/lib/components/nxt-datepicker/nxt-datepicker.component.html","../../../projects/nxt-app/src/lib/services/translation/indexeddb-reader.service.ts","../../../projects/nxt-app/src/lib/services/translation/translation.service.ts","../../../projects/nxt-app/src/lib/pipe/custom-translate.pipe.ts","../../../projects/nxt-app/src/lib/pipe/amount-in-words/amount-in-words.pipe.ts","../../../projects/nxt-app/src/lib/services/GoogleMapsLoaderService/google-maps-loader-service.service.ts","../../../projects/nxt-app/src/lib/services/country/country.service.ts","../../../projects/nxt-app/src/lib/services/storage/storage.service.ts","../../../projects/nxt-app/src/lib/services/change/change.service.ts","../../../projects/nxt-app/src/lib/components/nxt-input/nxt-input.component.ts","../../../projects/nxt-app/src/lib/components/nxt-input/nxt-input.component.html","../../../projects/nxt-app/src/lib/components/nxt-dropdown/nxt-dropdown.component.ts","../../../projects/nxt-app/src/lib/components/nxt-dropdown/nxt-dropdown.component.html","../../../projects/nxt-app/src/lib/components/nxt-multi-select/nxt-multi-select.component.ts","../../../projects/nxt-app/src/lib/components/nxt-multi-select/nxt-multi-select.component.html","../../../projects/nxt-app/src/lib/pipe/button-styles.pipe.ts","../../../projects/nxt-app/src/lib/components/button/nxt-button.component.ts","../../../projects/nxt-app/src/lib/components/button/nxt-button.component.html","../../../projects/nxt-app/src/lib/components/pagination/pagination.component.ts","../../../projects/nxt-app/src/lib/components/pagination/pagination.component.html","../../../projects/nxt-app/src/lib/pipe/search-filter/search-filter.pipe.ts","../../../projects/nxt-app/src/lib/pipe/get-value.pipe.ts","../../../projects/nxt-app/src/lib/components/list-view-filter/list-view-filter.component.ts","../../../projects/nxt-app/src/lib/components/list-view-filter/list-view-filter.component.html","../../../projects/nxt-app/src/lib/components/icon-selector/icon-selector.component.ts","../../../projects/nxt-app/src/lib/components/icon-selector/icon-selector.component.html","../../../projects/nxt-app/src/lib/model/changeWrapper.ts","../../../projects/nxt-app/src/lib/components/search-box/search-box.component.ts","../../../projects/nxt-app/src/lib/components/search-box/search-box.component.html","../../../projects/nxt-app/src/lib/components/custom-radio/custom-radio.component.ts","../../../projects/nxt-app/src/lib/components/custom-radio/custom-radio.component.html","../../../projects/nxt-app/src/lib/services/shared/shared.service.ts","../../../projects/nxt-app/src/lib/components/file-upload/file-upload.component.ts","../../../projects/nxt-app/src/lib/components/file-upload/file-upload.component.html","../../../projects/nxt-app/src/lib/pipe/question-by-row.pipe.ts","../../../projects/nxt-app/src/lib/components/datatable/datatable.component.ts","../../../projects/nxt-app/src/lib/components/datatable/datatable.component.html","../../../projects/nxt-app/src/lib/services/salesforce/salesforce.service.ts","../../../projects/nxt-app/src/lib/pages/questionnaire/questionnaire.component.ts","../../../projects/nxt-app/src/lib/pages/questionnaire/questionnaire.component.html","../../../projects/nxt-app/src/lib/components/image-cropper/utils/cropper-position.utils.ts","../../../projects/nxt-app/src/lib/components/image-cropper/component/cropper.state.ts","../../../projects/nxt-app/src/lib/components/image-cropper/interfaces/move-start.interface.ts","../../../projects/nxt-app/src/lib/components/image-cropper/utils/resize.utils.ts","../../../projects/nxt-app/src/lib/components/image-cropper/utils/percentage.utils.ts","../../../projects/nxt-app/src/lib/components/image-cropper/services/crop.service.ts","../../../projects/nxt-app/src/lib/components/image-cropper/utils/exif.utils.ts","../../../projects/nxt-app/src/lib/components/image-cropper/services/load-image.service.ts","../../../projects/nxt-app/src/lib/components/image-cropper/utils/keyboard.utils.ts","../../../projects/nxt-app/src/lib/components/image-cropper/component/image-cropper.component.ts","../../../projects/nxt-app/src/lib/components/image-cropper/component/image-cropper.component.html","../../../projects/nxt-app/src/lib/components/custom-model/custom-model.component.ts","../../../projects/nxt-app/src/lib/components/custom-model/custom-model.component.html","../../../projects/nxt-app/src/lib/components/custom-calendar/custom-calendar.component.ts","../../../projects/nxt-app/src/lib/components/custom-calendar/custom-calendar.component.html","../../../projects/nxt-app/src/lib/pages/questionbook/questionbook.component.ts","../../../projects/nxt-app/src/lib/pages/questionbook/questionbook.component.html","../../../projects/nxt-app/src/lib/pages/booklet/booklet.component.ts","../../../projects/nxt-app/src/lib/pages/booklet/booklet.component.html","../../../projects/nxt-app/src/environments/version.ts","../../../projects/nxt-app/src/lib/components/formula-input/formula-input.component.ts","../../../projects/nxt-app/src/lib/components/formula-input/formula-input.component.html","../../../projects/nxt-app/src/lib/services/pdf-designer/pdf-designer.service.ts","../../../projects/nxt-app/src/lib/services/template/template.service.ts","../../../projects/nxt-app/src/lib/pages/pdfDesigner/pdf-properties/pdf-properties.component.ts","../../../projects/nxt-app/src/lib/pages/pdfDesigner/pdf-properties/pdf-properties.component.html","../../../projects/nxt-app/src/lib/pages/pdfDesigner/pdf-designer/pdf-designer.component.ts","../../../projects/nxt-app/src/lib/pages/pdfDesigner/pdf-designer/pdf-designer.component.html","../../../projects/nxt-app/src/lib/pages/builder/properties/common-fields.constants.ts","../../../projects/nxt-app/src/lib/services/form-builder/form-builder.service.ts","../../../projects/nxt-app/src/lib/pages/builder/properties/properties.component.ts","../../../projects/nxt-app/src/lib/pages/builder/properties/properties.component.html","../../../projects/nxt-app/src/lib/pages/builder/element/element.component.ts","../../../projects/nxt-app/src/lib/pages/builder/element/element.component.html","../../../projects/nxt-app/src/lib/pages/builder/form/form.component.ts","../../../projects/nxt-app/src/lib/pages/builder/form/form.component.html","../../../projects/nxt-app/src/lib/nxt-app.module.ts","../../../projects/nxt-app/src/public-api.ts","../../../projects/nxt-app/src/rangertechnologies-ngnxt.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class NxtAppService {\n\n constructor() { }\n}\n","import { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'lib-nxt-app',\n template: `\n <p>\n nxt-app works!\n </p>\n `,\n styles: [],\n standalone: false\n})\nexport class NxtAppComponent implements OnInit {\n\n constructor() { }\n\n ngOnInit() {\n }\n\n}\n","export class ErrorWrapper {\n errorCode: string;\n errorMsg: string;\n errorDetails: string;\n}\n","import { Injectable, Pipe, PipeTransform } from '@angular/core';\nimport moment from 'moment-hijri';\nimport 'moment/locale/ar-sa';\nimport 'moment/locale/en-gb';\n\n@Pipe({\n name: 'NxtDate',\n standalone: true\n})\n@Injectable({\n providedIn: 'root'\n})\nexport class NxtDatePipe implements PipeTransform {\n\n transform(\n value: any,\n typeOrFormat?: 'date' | 'datetime' | 'month' | 'time' | string,\n languageCode: string = 'en',\n dateFormatType: 'hijri' | 'gregorian' = 'gregorian'\n ): string {\n if (!value) return '';\n const isDate = moment(value);\n if (!isDate.isValid() && typeOrFormat !== 'time') {\n return '';\n }\n const locale = languageCode === 'ar' ? 'ar-SA' : 'en';\n\n // SKS29OCT25 Parse input date depending on Hijri or Gregorian\n let date: moment.Moment;\n // SKS29OCT25 Detect if first arg is a type or a format\n const isKnownType = ['date', 'datetime', 'month', 'time'].includes(typeOrFormat || '');\n const type = isKnownType ? typeOrFormat : undefined;\n const formatArg = !isKnownType ? typeOrFormat : undefined;\n if (type === 'time') {\n date = moment(value, ['HH:mm', 'HH:mm:ss'], true);\n if (!date.isValid()) return '';\n } else {\n date = dateFormatType === 'hijri'\n ? moment(value, 'YYYY-MM-DDTHH:mm', true).locale(locale)\n : moment(value).locale(locale);\n if (!date.isValid()) return '';\n }\n // SKS29OCT25 Step 1: Default format based on type\n let format = '';\n switch (type) {\n case 'date':\n format = dateFormatType === 'hijri' ? 'iDD/iMM/iYYYY' : 'DD/MM/YYYY';\n break;\n case 'datetime':\n format = dateFormatType === 'hijri' ? 'iDD iMMM iYYYY, HH:mm' : 'DD MMM YYYY, HH:mm';\n break;\n case 'month':\n format = dateFormatType === 'hijri' ? 'iMMMM iYYYY' : 'MMMM YYYY';\n break;\n case 'time':\n format = 'HH:mm';\n break;\n default:\n // SKS29OCT25 Step 2: Angular-like or custom format\n format = this.mapAngularFormat(formatArg || 'medium', dateFormatType);\n }\n let output = date.format(format);\n\n // SKS14AUG25 Convert to Arabic numerals if needed\n if (languageCode === 'ar') {\n output = this.toArabicNumbers(output);\n }\n\n return (output && output !== '') ? output : '';\n }\n\n /** SKS29OCT25 Map Angular-style date aliases to Moment formats */\n private mapAngularFormat(format: string, dateFormatType: 'hijri' | 'gregorian'): string {\n const isHijri = dateFormatType === 'hijri';\n switch (format) {\n case 'short':\n return isHijri ? 'iDD/iMM/iYYYY HH:mm' : 'DD/MM/YYYY HH:mm';\n case 'medium':\n return isHijri ? 'iMMM iDD, iYYYY, hh:mm:ss A' : 'MMM DD, YYYY, hh:mm:ss A';\n case 'long':\n return isHijri ? 'iMMMM iDD, iYYYY, hh:mm:ss A' : 'MMMM DD, YYYY, hh:mm:ss A';\n case 'full':\n return isHijri ? 'dddd, iMMMM iDD, iYYYY, hh:mm:ss A' : 'dddd, MMMM DD, YYYY, hh:mm:ss A';\n case 'shortDate':\n return isHijri ? 'iDD/iMM/iYYYY' : 'DD/MM/YYYY';\n case 'mediumDate':\n return isHijri ? 'iMMM iDD, iYYYY' : 'DD MMM YYYY';\n case 'longDate':\n return isHijri ? 'iMMMM iDD, iYYYY' : 'MMMM DD, YYYY';\n case 'fullDate':\n return isHijri ? 'dddd, iMMMM iDD, iYYYY' : 'dddd, MMMM DD, YYYY';\n case 'shortTime':\n return 'hh:mm A';\n case 'mediumTime':\n return 'hh:mm:ss A';\n case 'mm:ss':\n return 'mm:ss';\n default:\n return format; // Custom Moment-style format\n }\n }\n\n /** SKS29OCT25 Convert digits to Arabic numerals */\n private toArabicNumbers(input: string): string {\n const easternArabicNumerals = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];\n return input.replace(/\\d/g, d => easternArabicNumerals[+d]);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport {\n Component,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n Injectable,\n OnInit,\n OnChanges,\n SimpleChanges\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n NgbDateStruct,\n NgbDatepicker,\n NgbDatepickerModule,\n NgbDatepickerI18n\n} from '@ng-bootstrap/ng-bootstrap';\n\n@Injectable()\nexport class CustomDatepickerI18n extends NgbDatepickerI18n {\n constructor(private parent: CustomDatepickerComponent) { super(); }\n\n private toArabicNumber(num: number): string {\n return this.parent.languageCode === 'ar'\n ? num.toString().replace(/\\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d])\n : num.toString();\n }\n\n override getWeekdayLabel(weekday: number): string {\n return this.parent.languageCode === 'ar'\n ? ['الإث', 'الث', 'الأر', 'الخ', 'الجم', 'السب', 'الأح'][weekday - 1]\n : ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'][weekday - 1];\n }\n\n override getMonthShortName(month: number): string {\n return this.parent.languageCode === 'ar'\n ? ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',\n 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'][month - 1]\n : ['January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'][month - 1];\n }\n\n override getMonthFullName(month: number): string {\n return this.getMonthShortName(month);\n }\n\n override getDayNumerals(date: NgbDateStruct): string {\n return this.toArabicNumber(date.day);\n }\n\n override getYearNumerals(year: number): string {\n return this.toArabicNumber(year);\n }\n\n override getDayAriaLabel(date: NgbDateStruct): string {\n return `${this.toArabicNumber(date.day)}-${this.toArabicNumber(date.month)}-${this.toArabicNumber(date.year)}`;\n }\n}\n\n@Component({\n selector: 'app-custom-datepicker',\n standalone: true,\n imports: [NgbDatepickerModule, FormsModule, CommonModule],\n templateUrl: './custom-datepicker.component.html',\n styleUrls: ['./custom-datepicker.component.css'],\n providers: [{ provide: NgbDatepickerI18n, useClass: CustomDatepickerI18n }]\n})\nexport class CustomDatepickerComponent implements OnInit, OnChanges {\n @Input() type: 'date' | 'datetime' | 'month' | 'time' = 'date';\n @Input() languageCode: 'en' | 'ar' = 'en';\n @Input() value: any;\n @Input() min: any;\n @Input() max: any;\n @Output() modelChange = new EventEmitter<any>();\n\n @ViewChild('dp', { static: false }) datepicker?: NgbDatepicker;\n\n /** SKS14AUG25 Bound to the datepicker via [(ngModel)] */\n selectedDate: NgbDateStruct | null = null;\n\n /** SKS14AUG25 Simple time holder for datetime/time modes */\n time = { hour: 12, minute: 0 };\n hours: number[] = [];\n minutes: number[] = [];\n displayHours: string[] = [];\n displayMinutes: string[] = [];\n monthYear = { year: new Date().getFullYear(), month: new Date().getMonth() + 1 };\n months = Array.from({ length: 12 }, (_, i) => i + 1);\n years: number[] = [];\n monthNamesEn = ['January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'];\n monthNamesAr = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',\n 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];\n minDate!: NgbDateStruct;\n maxDate!: NgbDateStruct;\n today!: NgbDateStruct;\n\n ngOnInit() {\n const now = new Date();\n this.today = {\n year: now.getFullYear(),\n month: now.getMonth() + 1,\n day: now.getDate()\n };\n if (!this.value) {\n this.selectedDate = this.today;\n }\n const currentYear = new Date().getFullYear();\n this.minDate = { year: currentYear - 100, month: 1, day: 1 };\n this.maxDate = { year: currentYear + 100, month: 12, day: 31 };\n // APPLY INPUT RANGE IF PROVIDED\n const parsedMin = this.parseToDateStruct(this.min);\n const parsedMax = this.parseToDateStruct(this.max);\n \n if (parsedMin) this.minDate = parsedMin;\n if (parsedMax) this.maxDate = parsedMax;\n this.initYears();\n this.generateTimeArrays();\n this.applyValue(this.value);\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['languageCode'] && !changes['languageCode'].firstChange) {\n this.generateTimeArrays();\n }\n if (changes['value'] && !changes['value'].firstChange) {\n this.applyValue(changes['value'].currentValue);\n }\n if (changes['min'] || changes['max']) {\n const parsedMin = this.parseToDateStruct(this.min);\n const parsedMax = this.parseToDateStruct(this.max);\n \n if (parsedMin) this.minDate = parsedMin;\n if (parsedMax) this.maxDate = parsedMax;\n }\n }\n /** SKS14AUG25 Create hours/minutes arrays and their display labels */\n private generateTimeArrays() {\n this.hours = Array.from({ length: 24 }, (_, i) => i);\n this.minutes = Array.from({ length: 60 }, (_, i) => i);\n\n if (this.languageCode === 'ar') {\n this.displayHours = this.hours.map(h => this.convertToArabic(h));\n this.displayMinutes = this.minutes.map(m => this.convertToArabic(m));\n } else {\n this.displayHours = this.hours.map(h => String(h).padStart(2, '0'));\n this.displayMinutes = this.minutes.map(m => String(m).padStart(2, '0'));\n }\n }\n /** SKS14AUG25 Converts number to Arabic digits with leading zero */\n convertToArabic(num: number): string {\n return num.toString().padStart(2, '0').replace(/\\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d]);\n }\n private initYears() {\n const currentYear = new Date().getFullYear();\n this.years = Array.from({ length: 201 }, (_, i) => currentYear - 100 + i);\n }\n /** SKS14AUG25 Accepts:\n * - \"2023-10-26T20:00\"\n * - Date\n * - { year, month, day }\n * - { date: { year, month, day, time? } }\n */\n private applyValue(val: any) {\n if (!val) return;\n // SKS14AUG25 Normalize to { year, month, day, time? }\n let ymd: { year: number; month: number; day: number; time?: string } | null = null;\n\n if (typeof val === 'string' || val instanceof Date) {\n const d = new Date(val);\n if (!isNaN(d.getTime())) {\n ymd = {\n year: d.getFullYear(),\n month: d.getMonth() + 1,\n day: d.getDate(),\n time: this.type === 'datetime'\n ? `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`\n : undefined\n };\n }\n } else if ('year' in val && 'month' in val && 'day' in val) {\n ymd = val;\n } else if (val.date && 'year' in val.date && 'month' in val.date && 'day' in val.date) {\n ymd = val.date;\n }\n // Apply to picker\n if (ymd) {\n // For date & datetime: actually select the day\n if (this.type === 'date' || this.type === 'datetime') {\n this.selectedDate = { year: ymd.year, month: ymd.month, day: ymd.day };\n // Ensure correct month is visible (optional but nice)\n this.datepicker?.navigateTo({ year: ymd.year, month: ymd.month });\n\n // If datetime, set time too\n if (this.type === 'datetime' && ymd.time) {\n const [h, m] = ymd.time.split(':').map(n => +n);\n if (!isNaN(h) && !isNaN(m)) this.time = { hour: h, minute: m };\n }\n }\n // SKS14AUG25 For month: show correct month/year; (datepicker is day-based, we don't select a day)\n else if (this.type === 'month') {\n this.selectedDate = null;\n this.datepicker?.navigateTo({ year: ymd.year, month: ymd.month });\n }\n }\n // For time only: set time if provided\n if (this.type === 'time') {\n if (val) {\n const [h, m] = val.split(':').map(n => +n);\n if (!isNaN(h) && !isNaN(m)) this.time = { hour: h, minute: m };\n }\n }\n // SKS14AUG25 (existing logic for date/time)\n if (this.type === 'month') {\n if (typeof val === 'string') {\n const [year, month] = val.split('-').map(Number);\n this.monthYear = { year, month };\n } else if (val?.date) {\n this.monthYear = { year: val.date.year, month: val.date.month };\n }\n }\n }\n\n onDateSelect(date: NgbDateStruct) {\n this.selectedDate = date;\n\n if (this.type === 'date') {\n this.modelChange.emit({from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date });\n } else if (this.type === 'datetime') {\n this.modelChange.emit({from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian',\n date: { ...date, time: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` }\n });\n } else if (this.type === 'month') {\n // If you want month-only result:\n this.modelChange.emit({from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: { year: date.year, month: date.month } });\n }\n }\n\n onTimeChange() {\n if (this.type === 'time') {\n this.modelChange.emit({from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` });\n } else if (this.type === 'datetime' && this.selectedDate) {\n this.modelChange.emit({from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian',\n date: { ...this.selectedDate, time: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` }\n });\n }\n }\n\n toArabicNumber(num: number): string {\n return num.toString().replace(/\\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d]);\n }\n onMonthSelect() {\n this.modelChange.emit({\n from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian',\n date: { year: this.monthYear.year, month: (this.monthYear.month).toString().padStart(2, '0')}\n });\n }\n private parseToDateStruct(value: any): NgbDateStruct | null {\n if (!value) return null;\n \n if (value === 'today') {\n const today = new Date();\n return {\n year: today.getFullYear(),\n month: today.getMonth() + 1,\n day: today.getDate()\n };\n }\n \n if (typeof value === 'string') {\n const parts = value.split('-').map(Number);\n if (parts.length >= 3) {\n return {\n year: parts[0],\n month: parts[1],\n day: parts[2]\n };\n }\n }\n \n return null;\n }\n}\n","<div class=\"datepicker-popup\">\n <!-- DATE / DATETIME / MONTH -->\n <!-- SKS14AUG25 Show only when type uses a calendar -->\n <ngb-datepicker *ngIf=\"type === 'date' || type === 'datetime'\" #dp [(ngModel)]=\"selectedDate\"\n [ngModelOptions]=\"{ standalone: true }\" [startDate]=\"selectedDate || undefined\"\n [minDate]=\"minDate\" [maxDate]=\"maxDate\"\n (dateSelect)=\"onDateSelect($event)\">\n </ngb-datepicker>\n <!-- SKS14AUG25 Month Picker -->\n <ng-container *ngIf=\"type === 'month'\">\n <div class=\"month-picker\">\n <div class=\"select-group\">\n <label class=\"select-label\">{{languageCode === 'ar' ? 'السنة' : 'Year'}}</label>\n <!-- Year selector -->\n <select class=\"custom-select\" [(ngModel)]=\"monthYear.year\"\n [ngModelOptions]=\"{ standalone: true }\" (change)=\"onMonthSelect()\">\n <option *ngFor=\"let y of years\" [value]=\"y\">{{ languageCode === 'ar' ?\n convertToArabic(y) : y }}\n </option>\n </select>\n </div>\n <div class=\"select-group\">\n <label class=\"select-label\">{{languageCode === 'ar' ? 'الشهر' : 'Month'}}</label>\n <!-- Month selector -->\n <select class=\"custom-select\" [(ngModel)]=\"monthYear.month\"\n [ngModelOptions]=\"{ standalone: true }\" (change)=\"onMonthSelect()\">\n <option *ngFor=\"let m of months; let idx = index\" [value]=\"idx+1\">\n {{ languageCode === 'ar' ? monthNamesAr[idx] : monthNamesEn[idx] }}\n </option>\n </select>\n </div>\n </div>\n </ng-container>\n <!-- SKS14AUG25 Time Picker -->\n <ng-container *ngIf=\"type === 'time' || type === 'datetime'\">\n <div class=\"time-picker\">\n <div class=\"time-section\">\n <div class=\"time-controls\">\n <div class=\"time-group\">\n <select class=\"time-select\" [(ngModel)]=\"time.hour\"\n [ngModelOptions]=\"{ standalone: true }\" (change)=\"onTimeChange()\">\n <option *ngFor=\"let h of hours; let i = index\" [value]=\"h\">\n {{ displayHours[i] }}\n </option>\n </select>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-group\">\n <select class=\"time-select\" [(ngModel)]=\"time.minute\"\n [ngModelOptions]=\"{ standalone: true }\" (change)=\"onTimeChange()\">\n <option *ngFor=\"let m of minutes; let i = index\" [value]=\"m\">\n {{ displayMinutes[i] }}\n </option>\n </select>\n </div>\n </div>\n </div>\n </div>\n </ng-container>\n</div>","import { Injectable, Optional } from '@angular/core';\nimport { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport moment from 'moment-hijri';\n@Injectable({\n providedIn: 'root'\n})\nexport class DataService {\n apiUrl: any;\n // VD 23JAN24 optionally declared httpClient\n constructor(@Optional() private http: HttpClient) { }\n\n public getAPIData(tkn: string, \n params: string[],\n resolve,\n reject,\n config?: any) {\n const self = this;\n\n // Call the ShengelGo Server\n const headers = new HttpHeaders().set('Authorization', `${tkn}`);\n headers.append('Accept', 'application/json');\n headers.append('Access-Control-Allow-Origin', '*');\n\n // MR 23JAN24 - This should be removed, for now testing with dev-hse-api\n this.http.get('https://dev-hse-api.rangerfusion.com/nxt/api/process/?dataType=' + params[0] + '&operation=' + params[1] + '&param=' + params[2], { headers: headers , observe: 'response' })\n .subscribe(response => {\n if(response.status == 200) {\n resolve(response.body);\n } else {\n reject(response.body);\n }\n }, error => {\n reject(error);\n });\n }\n\n //calling the node api\n public getAPIFromNode(dataType: string, operation: string, param1: string,param2: string,api:string){\n const apiUrl = api ? api : 'http://localhost:3001/nxt';\n // let params = new HttpParams()\n // .set('dataType', dataType)\n // .set('operation', operation)\n // .set('param1', param1)\n // .set('param2',param2)\n\n let body = {\n \"dataType\" :dataType ,\n \"operation\" :operation ,\n \"param1\" :param1, \n \"param2\" : param2,\n }\n return this.http.post<any>(apiUrl,body );\n\n }\n\n \n// VD 22May24 -funtion to handling multiple child objects\npublic getValue(element: any, columns: any) {\n if (!element || !columns) return null; //AP-25MAR25 Ensure both element and column are valid\n// MSM27MAR25 check if columns is an array\n if(typeof columns === 'string' && !Array.isArray(columns) ) {\n columns = [columns];\n }\n columns.forEach((column) => {\n let flds = column?.split('.') || [];\n\n for (let i = 0; i < flds.length; i++) {\n if (element == null) return null; //AP-25MAR25 Prevent errors if element is null or undefined\n\n let splitFlds = flds[i].split('[');\n if (splitFlds.length === 1) {\n element = element[flds[i]] !== undefined ? element[flds[i]] : null;\n } else {\n let index = Number(splitFlds[1].split(']')[0]);\n element = element[splitFlds[0]] && Array.isArray(element[splitFlds[0]]) \n ? element[splitFlds[0]][index] !== undefined ? element[splitFlds[0]][index] : null\n : null;\n }\n }\n });\n return element;\n}\n// AP-16APR25 Sets the API URL with the provided apidata\napikey(apidata: any){\n this.apiUrl = apidata;\n}\n// VD 23JAN24 get callout \npublic apiResponse(endpoint: string): Observable<any> {\n if (!endpoint || !endpoint.trim()) {\n throw new Error('Invalid API endpoint');\n }\n\n const isAbsoluteUrl = endpoint.startsWith('http://') || endpoint.startsWith('https://');\n\n const url = isAbsoluteUrl\n ? endpoint\n : `${this.apiUrl}${endpoint}`;\n\n return this.http.get(url);\n}\n\n// public apiResponse(endpoint: any): Observable<any> {\n// // Build headers with token\n// const headers = new HttpHeaders({\n// 'Accept': 'application/json, text/plain, */*',\n// 'Cache-Control': 'no-cache',\n// 'Pragma': 'no-cache',\n// 'token': \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21wYW55SWQiOiJlcmFrZGV2IiwidXNlck5hbWUiOiJrdW1hcmVzYW5AZWxlY3Ryb3Jhay5mdXNpb24iLCJ1X2lkIjoiNjg5MDc5NmY2MGRlZGU3MTM2ZmVhMThiIiwicGVybWlzc2lvbnMiOlsiU3VwZXIgVXNlciIsIkVTUyBVc2VyIiwiSFJNIE1hbmFnZXIiLCJIUk0gVXNlciIsIkFwcCBVc2VyIiwiQ1JNIE1hbmFnZXIiXSwiZW1wbG95ZWVJZCI6IjEwNDAiLCJ1c2VybmFtZSI6Ikt1bWFyZXNhbiBLdW1hcmVzYW4iLCJjb21wYW55TmFtZSI6IkVsZWN0cm8gUkFLIEluZGlhIFByaXZhdGUgTGltaXRlZCIsImlhdCI6MTc1OTQ3ODgyMCwiZXhwIjoxNzY0NjYyODIwfQ.WyqeQnvPJIX1n40nr28T4MflivA-XFFyfbwLGnSXhIc\" // 👈 custom header exactly like your request\n// });\n// // Case 1: Full URL\n// if (endpoint && endpoint.startsWith('https://')) {\n// return this.http.get(endpoint, { headers });\n// }\n// // Case 2: Prepend apiUrl\n// if (this.apiUrl && this.apiUrl.trim() !== '' && endpoint !== undefined) {\n// const url = this.apiUrl + endpoint;\n// return this.http.get(url, { headers });\n// }\n// // Case 3: Fallback\n// return this.http.get(endpoint, { headers });\n// }\n // SKS7AUG25 eng ISO format to hijri eng numaral\n formatToHijriString(date: any, type?: 'year' | 'month' | 'date' | 'time'| any): any {\n if (!date) return null;\n \n let hijriMoment;\n \n if (typeof date === 'string') {\n //SKS7AUG25 Handle string input\n hijriMoment = moment(date, ['YYYY-MM-DD', 'YYYY-MM', 'YYYY'], true)?.locale('en');\n } else if (date instanceof Date) {\n //SKS7AUG25 Handle native Date object\n hijriMoment = moment(date).locale('en');\n } else {\n // Fallback (e.g., moment object passed directly)\n hijriMoment = moment(date).locale('en');\n }\n \n if (!hijriMoment?.isValid()) return null;\n \n const year = hijriMoment.iYear();\n const month = hijriMoment.iMonth() + 1; // 0-based\n const day = hijriMoment.iDate();\n const hour = hijriMoment.hour();\n const minute = hijriMoment.minute();\n \n const result: any = {};\n \n if (type === 'year') {\n result['year'] = year;\n } else if (type === 'month') {\n result['year'] = year;\n result['month'] = month;\n } else if (type === 'time') {\n result['time'] = `${hour}:${minute}`;\n } else {\n //SKS7AUG25 Auto-detect based on available data\n result['year'] = year;\n \n if (!isNaN(month)) result['month'] = month;\n if (!isNaN(day) && day > 0) result['day'] = day;\n if (hour || minute) result['time'] = `${hour}:${minute}`;\n }\n \n return result;\n }\n \n // SKS7AUG25 en numaral to hijri en numaral object\n formatToGregorianString(date: any, type?: any): string { \n const dateStr = `${date.year}-${date.month}-${date.day || 1}`; // Use day=1 if not provided\n const hijriMoment = moment(dateStr, 'iYYYY-iM-iD').locale('en'); // Force English locale\n if (date.time && type !== 'month') {\n const [hours, minutes] = date.time.split(':').map(Number);\n hijriMoment.set({ hour: hours, minute: minutes });\n }\n if (type === 'month') {\n return hijriMoment.format('YYYY-MM'); // Gregorian year-month\n }\n return hijriMoment.toDate().toISOString(); // Full ISO Gregorian datetime\n } \n nxtId(): string {\n const prefix = 'ngnxt'; \n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let core = '';\n for (let i = 0; i < 14; i++) {\n core += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return prefix + core; \n }\n}\n","import {\n Component,\n EventEmitter,\n Output,\n AfterViewInit,\n Injectable,\n ElementRef,\n ViewChild,\n Input,\n inject,\n SimpleChanges,\n OnInit,\n OnChanges\n} from '@angular/core';\nimport {\n NgbCalendar,\n NgbCalendarIslamicUmalqura,\n NgbDatepickerI18n,\n NgbDatepickerModule,\n NgbDateStruct\n} from '@ng-bootstrap/ng-bootstrap';\nimport { FormsModule } from '@angular/forms';\nimport { CommonModule } from '@angular/common';\nimport { DataService } from '../../../services/data/data.service';\n\nconst WEEKDAYS = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'];\nconst MONTHS = [\n 'محرم', 'صفر', 'ربيع الأول', 'ربيع الآخر',\n 'جمادى الأولى', 'جمادى الآخرة', 'رجب', 'شعبان',\n 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'\n];\n\n@Injectable()\nexport class IslamicI18n extends NgbDatepickerI18n {\n toArabicNumber(value: string | number): string {\n return value.toString().replace(/\\d/g, d => '٠١٢٣٤٥٦٧٨٩'[parseInt(d)]);\n }\n\n getMonthShortName(month: number): string {\n return MONTHS[month - 1];\n }\n\n getMonthFullName(month: number): string {\n return MONTHS[month - 1];\n }\n\n getWeekdayLabel(weekday: number): string {\n return WEEKDAYS[weekday - 1];\n }\n\n getDayAriaLabel(date: NgbDateStruct): string {\n return `${this.toArabicNumber(date.day)}-${this.toArabicNumber(date.month)}-${this.toArabicNumber(date.year)}`;\n }\n}\n\n@Component({\n selector: 'app-hijri-datepicker',\n standalone: true,\n imports: [NgbDatepickerModule, FormsModule, CommonModule],\n providers: [\n { provide: NgbCalendar, useClass: NgbCalendarIslamicUmalqura },\n { provide: NgbDatepickerI18n, useClass: IslamicI18n }\n ],\n templateUrl: './hijri-datepicker.component.html',\n styleUrl: './hijri-datepicker.component.css'\n})\nexport class HijriDatepickerComponent implements AfterViewInit, OnInit, OnChanges {\n calendar = inject(NgbCalendarIslamicUmalqura);\n model: NgbDateStruct = this.calendar.getToday();\n\n @Input() type: 'date' | 'month' | 'datetime' | 'time' = 'date';\n @Input() value: any;\n @Input() min: any;\n @Input() max: any;\n @Input() languageCode: any = 'en'\n @Output() dateChange = new EventEmitter<any>();\n @ViewChild('dp', { static: false }) datepickerRef?: any;\n\n months = MONTHS;\n years: number[] = [];\n\n hour: number = 12;\n minute: number = 0;\n hours = Array.from({ length: 24 }, (_, i) => i);\n minutes = Array.from({ length: 60 }, (_, i) => i);\n minDate!: NgbDateStruct;\n maxDate!: NgbDateStruct;\n today!: NgbDateStruct;\n\n constructor(private dataService: DataService) {}\n\n ngOnInit() {\n const todayHijri = this.calendar.getToday();\n this.today = todayHijri;\n \n if (!this.value) {\n this.model = todayHijri;\n }\n // Default range (Hijri ±100 years)\n const currentYear = todayHijri.year;\n this.minDate = { year: currentYear - 100, month: 1, day: 1 };\n this.maxDate = { year: currentYear + 100, month: 12, day: 30 };\n \n // Override if input provided\n const parsedMin = this.parseToHijriDate(this.min);\n const parsedMax = this.parseToHijriDate(this.max);\n \n if (parsedMin) this.minDate = parsedMin;\n if (parsedMax) this.maxDate = parsedMax;\n this.generateYears();\n this.setInitialDateFromValue();\n }\n\n ngAfterViewInit() {\n this.applyArabicNumerals();\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['value'] && !changes['value'].firstChange) {\n this.setInitialDateFromValue();\n }\n if (changes['min'] || changes['max']) {\n const parsedMin = this.parseToHijriDate(this.min);\n const parsedMax = this.parseToHijriDate(this.max);\n \n if (parsedMin) this.minDate = parsedMin;\n if (parsedMax) this.maxDate = parsedMax;\n }\n }\n\n setInitialDateFromValue() {\n if (this.value) {\n this.model = this.dataService.formatToHijriString(this.value);\n // Navigate & select the correct date in UI\n setTimeout(() => {\n this.datepickerRef?.navigateTo({\n year: this.model.year,\n month: this.model.month\n });\n });\n }\n }\n\n // SKS14AUG25 Generate year range ±15 years\n generateYears() {\n const currentYear = this.model.year;\n const startYear = currentYear - 100;\n const endYear = currentYear + 100;\n this.years = Array.from(\n { length: endYear - startYear + 1 },\n (_, i) => startYear + i\n );\n }\n\n onDateChange(date: NgbDateStruct) {\n this.model = date;\n this.emitDateTime();\n this.applyArabicNumerals();\n }\n\n onMonthChange() {\n this.emitDateTime();\n }\n\n onTimeChange() {\n this.emitDateTime();\n }\n\n emitDateTime() {\n if (this.type === 'datetime') {\n this.dateChange.emit({\n ...this.model,\n time: `${this.hour}:${this.minute}`\n });\n } else if (this.type === 'time') {\n this.dateChange.emit(`${this.hour}:${this.minute}`);\n } else if (this.type === 'month') {\n let temp = { ...this.model };\n delete temp.day;\n this.dateChange.emit(temp);\n } else {\n this.dateChange.emit(this.model);\n }\n }\n\n toArabicNumber(value: string | number): string {\n return value.toString().replace(/\\d/g, d => '٠١٢٣٤٥٦٧٨٩'[parseInt(d)]);\n }\n\n applyArabicNumerals() {\n setTimeout(() => {\n const dayCells = document.querySelectorAll('.ngb-dp-day .btn-light, .ngb-dp-day .btn-primary');\n dayCells.forEach((btn: Element) => {\n const value = btn.textContent?.trim();\n if (value && /^\\d+$/.test(value)) {\n btn.textContent = this.toArabicNumber(value);\n }\n });\n\n const yearOptions = document.querySelectorAll('ngb-datepicker select:nth-child(2) option');\n yearOptions.forEach((option: HTMLOptionElement) => {\n const value = option.textContent?.trim();\n if (value && /^\\d+$/.test(value)) {\n option.textContent = this.toArabicNumber(value);\n }\n });\n });\n }\n\n onNavigate() {\n setTimeout(() => {\n this.applyArabicNumerals();\n }, 50);\n }\n private parseToHijriDate(value: any): NgbDateStruct | null {\n if (!value) return null;\n \n let gregorianDate: Date | null = null;\n \n if (value === 'today') {\n gregorianDate = new Date();\n } \n else if (typeof value === 'string') {\n const parsed = new Date(value);\n if (!isNaN(parsed.getTime())) {\n gregorianDate = parsed;\n }\n }\n \n if (gregorianDate) {\n return this.calendar.fromGregorian(gregorianDate);\n }\n \n return null;\n }\n}\n","<!-- SKS14AUG25 Date Picker -->\n<ng-container *ngIf=\"type === 'date' || type === 'datetime'\">\n <div class=\"date-picker-wrapper\">\n <ngb-datepicker #dp [ngModel]=\"model\" (ngModelChange)=\"onDateChange($event)\"\n [minDate]=\"minDate\" [maxDate]=\"maxDate\"\n (navigate)=\"onNavigate()\" [firstDayOfWeek]=\"7\" [showWeekNumbers]=\"false\">\n </ngb-datepicker>\n </div>\n</ng-container>\n\n<!-- SKS14AUG25 Month Picker -->\n<ng-container *ngIf=\"type === 'month'\">\n <div class=\"month-picker\">\n <div class=\"select-group\">\n <label class=\"select-label\">{{languageCode === 'ar' ? 'السنة' : 'Year'}}</label>\n <select class=\"custom-select\" [(ngModel)]=\"model.year\" (change)=\"onMonthChange()\">\n <option *ngFor=\"let year of years\" [value]=\"year\">\n {{ toArabicNumber(year) }}\n </option>\n </select>\n </div>\n <div class=\"select-group\">\n <label class=\"select-label\">{{languageCode === 'ar' ? 'الشهر' : 'Month'}}</label>\n <select class=\"custom-select\" [(ngModel)]=\"model.month\" (change)=\"onMonthChange()\">\n <option *ngFor=\"let month of months; let i = index\" [value]=\"i + 1\">\n {{ month }}\n </option>\n </select>\n </div>\n </div>\n</ng-container>\n\n<!-- SKS14AUG25 Time Picker -->\n<ng-container *ngIf=\"type === 'datetime'\">\n <div class=\"time-picker\">\n <div class=\"time-section\">\n <div class=\"time-controls\">\n <div class=\"time-group\">\n <select class=\"time-select\" [(ngModel)]=\"hour\" (change)=\"onTimeChange()\">\n <option *ngFor=\"let h of hours\" [value]=\"h\">\n {{ toArabicNumber(h.toString().padStart(2, '0')) }}\n </option>\n </select>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-group\">\n <select class=\"time-select\" [(ngModel)]=\"minute\" (change)=\"onTimeChange()\">\n <option *ngFor=\"let m of minutes\" [value]=\"m\">\n {{ toArabicNumber(m.toString().padStart(2, '0')) }}\n </option>\n </select>\n </div>\n </div>\n </div>\n </div>\n</ng-container>","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { CustomDatepickerComponent } from './custom-datepicker/custom-datepicker.component';\nimport { HijriDatepickerComponent } from './hijri-datepicker/hijri-datepicker.component';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { DataService } from '../../services/data/data.service';\n\n@Component({\n selector: 'app-nxt-datepicker',\n standalone: true,\n imports: [CustomDatepickerComponent, HijriDatepickerComponent, CommonModule, FormsModule],\n templateUrl: './nxt-datepicker.component.html',\n styleUrls: ['./nxt-datepicker.component.css']\n})\nexport class NxtDatepickerComponent {\n isHijriCalendar = true; //SKS14AUG25 true = Hijri, false = Gregorian\n @Input() type: string = 'datetime';\n @Input() languageCode: any = 'en'\n @Input() value: any\n @Input() min: any\n @Input() max: any\n @Output() dateChange = new EventEmitter<any>();\n constructor(private dataService: DataService){}\n ngOnInit() { \n this.isHijriCalendar = this.languageCode === 'ar' ? true : false\n }\n ngOnChange(){\n }\n toggleCalendarType() {\n this.isHijriCalendar = !this.isHijriCalendar;\n }\n\n onDatePicked(date: any) {\n let output: any;\n if(date.from === \"hijri\"){\n output = this.type === 'time' ? date.data : this.dataService.formatToGregorianString(date.data, this.type)\n } else {\n if (this.type === 'datetime' || this.type === 'date') {\n const [hour, minute] = this.type === 'datetime' && date.data.date.time\n ? date.data.date.time.split(':').map(Number)\n : [0, 0];\n const isoDate = new Date(\n date.data.date.year,\n date.data.date.month - 1, // JS months start at 0\n date.data.date.day,\n hour,\n minute\n );\n output = isoDate.toISOString(); //SKS14AUG25 Always ISO 8601 datetime\n } \n else if (this.type === 'time') {\n output = date.data.date;\n } \n else if (this.type === 'month') {\n output = `${date.data.date.year}-${date.data.date.month}` ;\n } \n else {\n const isoDate = new Date(\n date.data.date.year,\n date.data.date.month - 1,\n date.data.date.day\n );\n output = isoDate.toISOString();\n }\n }\n this.dateChange.emit({from: date.from,date: output})\n }\n}\n","<div class=\"datepicker-container\">\n <!-- SKS14AUG25 Calendar Type Switch -->\n <div *ngIf=\"languageCode === 'ar' && type !== 'time'\" class=\"calendar-switch\">\n <div class=\"switch-container\">\n <div (click)=\"toggleCalendarType()\" class=\"calendar-type-label\">\n {{ isHijriCalendar ? 'تقويم هجري' : 'تقويم ميلادي' }}\n </div>\n </div>\n </div>\n\n <!-- SKS14AUG25 Hijri Calendar -->\n <app-hijri-datepicker\n *ngIf=\"isHijriCalendar && type !== 'time'\"\n [type]=\"type\"\n [min]=\"min\"\n [max]=\"max\"\n [value]=\"value\"\n [languageCode]=\"languageCode\"\n (dateChange)=\"onDatePicked({from: 'hijri',data: $event})\">\n </app-hijri-datepicker>\n\n <!-- SKS14AUG25 Gregorian Calendar -->\n <app-custom-datepicker\n *ngIf=\"!isHijriCalendar || type === 'time'\"\n [type]=\"type\"\n [value]=\"value\"\n [min]=\"min\"\n [max]=\"max\"\n [languageCode]=\"languageCode\"\n (modelChange)=\"onDatePicked({from: $event.from,data: $event})\">\n </app-custom-datepicker>\n</div>\n","import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class IndexedDbReaderService {\n\n private dbName = 'ranger'; //SKS3MAR25 MUST match main app\n private storeName = 'appStore';\n private path = 'translations';\n private dbPromise: Promise<IDBDatabase | null>;\n\n constructor() {\n this.dbPromise = this.initDB();\n }\n\n private initDB(): Promise<IDBDatabase | null> {\n return new Promise((resolve) => {\n const request = indexedDB.open(this.dbName);\n request.onsuccess = () => {\n const db = request.result;\n // SKS7MAR26 Skip if store doesn't exist\n if (!db.objectStoreNames.contains(this.storeName)) {\n resolve(null);\n } else {\n resolve(db);\n }\n };\n\n request.onerror = () => resolve(null); // SKS7MAR26 skip DB if error\n request.onupgradeneeded = (event: any) => {\n // SKS7MAR26 optional: create store only if needed\n const db = event.target.result;\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName);\n }\n };\n });\n }\n\n async getTranslation(lang: string): Promise<any> {\n const db = await this.dbPromise;\n if (!db) {\n // SKS7MAR26 DB or store not ready → skip gracefully\n return {};\n }\n return this.readTranslation(db, lang);\n }\n\n private readTranslation(db: IDBDatabase, lang: string): Promise<any> {\n return new Promise((resolve) => {\n try {\n const tx = db.transaction(this.storeName, 'readonly');\n const request = tx.objectStore(this.storeName).get(this.path);\n request.onsuccess = () => {\n const translations = request.result;\n resolve(translations ? translations[lang] ?? {} : {});\n };\n request.onerror = () => resolve({});\n } catch (e) {\n // SKS7MAR26 transaction fails → skip gracefully\n resolve({});\n }\n });\n }\n}","import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { DataService } from '../data/data.service';\nimport { IndexedDbReaderService } from './indexeddb-reader.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class TranslationService {\n private translations: any = {};\n private currentLang: string = 'en'; // Default language\n private translationsLoaded = new BehaviorSubject<void>(null); // Signal when translations are loaded\n public translationsLoaded$ = this.translationsLoaded.asObservable(); // Observable for components to subscribe to\n private languageChange = new BehaviorSubject<any>(null); // Signal when translations are loaded\n public languageChange$ = this.languageChange.asObservable(); // Observable for components to subscribe to\n\n private formBuilderTranslations: any;\n private formBuilderCurrentLang: string = 'en'; // Default language\n private formBuilderTranslationsLoaded = new BehaviorSubject<string>(''); // Emit language string\n public formBuilderTranslationsLoaded$ = this.formBuilderTranslationsLoaded.asObservable();\n constructor(private dataService: DataService, private indexedDbReader: IndexedDbReaderService) { }\n\n // SKS3MAR25 Load translations from localStorage\n async load(): Promise<void> {\n const stored = await this.indexedDbReader.getTranslation(this.currentLang);\n if (stored) {\n const flattened = this.flattenAppTranslations({\n [this.currentLang]: stored\n });\n this.updateTranslations(flattened);\n } else {\n if(!this.translations) this.translations = {}\n this.translations[this.currentLang] = {};\n }\n this.translationsLoaded.next();\n }\n // SKS17DEC25 Helpers \n private safeParse<T>(value: string | null, fallback: T): T {\n try {\n return value ? JSON.parse(value) : fallback;\n } catch {\n return fallback;\n }\n }\n // SKS17DEC25 Translation Logic \n flattenAppTranslations(translations: any): any {\n const newTranslations: any = {};\n for (const lang of Object.keys(translations)) {\n const { APP = {}, ...rest } = translations[lang] || {};\n newTranslations[lang] = {\n ...rest, // original keys\n ...APP // flattened APP keys\n