UNPKG

@rangertechnologies/ngnxt

Version:

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

784 lines (776 loc) 3.08 MB
import * as i0 from '@angular/core'; import { Injectable, Component, Pipe, EventEmitter, ViewChild, Output, Input, Optional, inject, forwardRef, HostListener, ViewChildren, signal, ChangeDetectionStrategy, ViewEncapsulation, HostBinding, computed, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import * as i6 from '@angular/forms'; import { FormsModule, FormControl, FormArray, Validators, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import * as i5 from '@angular/common'; import { CommonModule, NgIf, NgClass, NgFor } from '@angular/common'; import { QuillEditorComponent } from 'ngx-quill'; import Quill from 'quill'; import { Mention } from 'quill-mention'; import ImageResizor from 'quill-image-resizor'; import moment from 'moment-hijri'; import 'moment/locale/ar-sa'; import 'moment/locale/en-gb'; import * as i1 from '@ng-bootstrap/ng-bootstrap'; import { NgbDatepickerI18n, NgbDatepickerModule, NgbCalendarIslamicUmalqura, NgbCalendar } from '@ng-bootstrap/ng-bootstrap'; import * as i1$1 from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http'; import * as i5$1 from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip'; import * as i9 from '@angular/google-maps'; import { GoogleMapsModule } from '@angular/google-maps'; import { BehaviorSubject, Subject, takeUntil, debounceTime, distinctUntilChanged, catchError, of, merge, fromEvent, auditTime, firstValueFrom, forkJoin } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatSort } from '@angular/material/sort'; import { SelectionModel } from '@angular/cdk/collections'; import * as i1$2 from '@angular/platform-browser'; import { takeUntil as takeUntil$1, first } from 'rxjs/operators'; import * as i9$1 from '@angular/cdk/bidi'; import * as i1$3 from '@angular/router'; import * as i8 from 'ngx-device-detector'; import _ from 'lodash'; class NxtAppService { constructor() { } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtAppService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtAppService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtAppService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class NxtAppComponent { constructor() { } ngOnInit() { } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtAppComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: NxtAppComponent, isStandalone: false, selector: "lib-nxt-app", ngImport: i0, template: ` <p> nxt-app works! </p> `, isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtAppComponent, decorators: [{ type: Component, args: [{ selector: 'lib-nxt-app', template: ` <p> nxt-app works! </p> `, standalone: false }] }], ctorParameters: () => [] }); class ErrorWrapper { errorCode; errorMsg; errorDetails; } class NxtDatePipe { transform(value, typeOrFormat, languageCode = 'en', dateFormatType = 'gregorian') { if (!value) return ''; const isDate = moment(value); if (!isDate.isValid() && typeOrFormat !== 'time') { return ''; } const locale = languageCode === 'ar' ? 'ar-SA' : 'en'; // SKS29OCT25 Parse input date depending on Hijri or Gregorian let date; // SKS29OCT25 Detect if first arg is a type or a format const isKnownType = ['date', 'datetime', 'month', 'time'].includes(typeOrFormat || ''); const type = isKnownType ? typeOrFormat : undefined; const formatArg = !isKnownType ? typeOrFormat : undefined; if (type === 'time') { date = moment(value, ['HH:mm', 'HH:mm:ss'], true); if (!date.isValid()) return ''; } else { date = dateFormatType === 'hijri' ? moment(value, 'YYYY-MM-DDTHH:mm', true).locale(locale) : moment(value).locale(locale); if (!date.isValid()) return ''; } // SKS29OCT25 Step 1: Default format based on type let format = ''; switch (type) { case 'date': format = dateFormatType === 'hijri' ? 'iDD/iMM/iYYYY' : 'DD/MM/YYYY'; break; case 'datetime': format = dateFormatType === 'hijri' ? 'iDD iMMM iYYYY, HH:mm' : 'DD MMM YYYY, HH:mm'; break; case 'month': format = dateFormatType === 'hijri' ? 'iMMMM iYYYY' : 'MMMM YYYY'; break; case 'time': format = 'HH:mm'; break; default: // SKS29OCT25 Step 2: Angular-like or custom format format = this.mapAngularFormat(formatArg || 'medium', dateFormatType); } let output = date.format(format); // SKS14AUG25 Convert to Arabic numerals if needed if (languageCode === 'ar') { output = this.toArabicNumbers(output); } return (output && output !== '') ? output : ''; } /** SKS29OCT25 Map Angular-style date aliases to Moment formats */ mapAngularFormat(format, dateFormatType) { const isHijri = dateFormatType === 'hijri'; switch (format) { case 'short': return isHijri ? 'iDD/iMM/iYYYY HH:mm' : 'DD/MM/YYYY HH:mm'; case 'medium': return isHijri ? 'iMMM iDD, iYYYY, hh:mm:ss A' : 'MMM DD, YYYY, hh:mm:ss A'; case 'long': return isHijri ? 'iMMMM iDD, iYYYY, hh:mm:ss A' : 'MMMM DD, YYYY, hh:mm:ss A'; case 'full': return isHijri ? 'dddd, iMMMM iDD, iYYYY, hh:mm:ss A' : 'dddd, MMMM DD, YYYY, hh:mm:ss A'; case 'shortDate': return isHijri ? 'iDD/iMM/iYYYY' : 'DD/MM/YYYY'; case 'mediumDate': return isHijri ? 'iMMM iDD, iYYYY' : 'DD MMM YYYY'; case 'longDate': return isHijri ? 'iMMMM iDD, iYYYY' : 'MMMM DD, YYYY'; case 'fullDate': return isHijri ? 'dddd, iMMMM iDD, iYYYY' : 'dddd, MMMM DD, YYYY'; case 'shortTime': return 'hh:mm A'; case 'mediumTime': return 'hh:mm:ss A'; case 'mm:ss': return 'mm:ss'; default: return format; // Custom Moment-style format } } /** SKS29OCT25 Convert digits to Arabic numerals */ toArabicNumbers(input) { const easternArabicNumerals = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; return input.replace(/\d/g, d => easternArabicNumerals[+d]); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtDatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: NxtDatePipe, isStandalone: true, name: "NxtDate" }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtDatePipe, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NxtDatePipe, decorators: [{ type: Pipe, args: [{ name: 'NxtDate', standalone: true }] }, { type: Injectable, args: [{ providedIn: 'root' }] }] }); class CustomDatepickerI18n extends NgbDatepickerI18n { parent; constructor(parent) { super(); this.parent = parent; } toArabicNumber(num) { return this.parent.languageCode === 'ar' ? num.toString().replace(/\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d]) : num.toString(); } getWeekdayLabel(weekday) { return this.parent.languageCode === 'ar' ? ['الإث', 'الث', 'الأر', 'الخ', 'الجم', 'السب', 'الأح'][weekday - 1] : ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'][weekday - 1]; } getMonthShortName(month) { return this.parent.languageCode === 'ar' ? ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'][month - 1] : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][month - 1]; } getMonthFullName(month) { return this.getMonthShortName(month); } getDayNumerals(date) { return this.toArabicNumber(date.day); } getYearNumerals(year) { return this.toArabicNumber(year); } getDayAriaLabel(date) { return `${this.toArabicNumber(date.day)}-${this.toArabicNumber(date.month)}-${this.toArabicNumber(date.year)}`; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CustomDatepickerI18n, deps: [{ token: CustomDatepickerComponent }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CustomDatepickerI18n }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CustomDatepickerI18n, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: CustomDatepickerComponent }] }); class CustomDatepickerComponent { type = 'date'; languageCode = 'en'; value; min; max; modelChange = new EventEmitter(); datepicker; /** SKS14AUG25 Bound to the datepicker via [(ngModel)] */ selectedDate = null; /** SKS14AUG25 Simple time holder for datetime/time modes */ time = { hour: 12, minute: 0 }; hours = []; minutes = []; displayHours = []; displayMinutes = []; monthYear = { year: new Date().getFullYear(), month: new Date().getMonth() + 1 }; months = Array.from({ length: 12 }, (_, i) => i + 1); years = []; monthNamesEn = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; monthNamesAr = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; minDate; maxDate; today; ngOnInit() { const now = new Date(); this.today = { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() }; if (!this.value) { this.selectedDate = this.today; } const currentYear = new Date().getFullYear(); this.minDate = { year: currentYear - 100, month: 1, day: 1 }; this.maxDate = { year: currentYear + 100, month: 12, day: 31 }; // APPLY INPUT RANGE IF PROVIDED const parsedMin = this.parseToDateStruct(this.min); const parsedMax = this.parseToDateStruct(this.max); if (parsedMin) this.minDate = parsedMin; if (parsedMax) this.maxDate = parsedMax; this.initYears(); this.generateTimeArrays(); this.applyValue(this.value); } ngOnChanges(changes) { if (changes['languageCode'] && !changes['languageCode'].firstChange) { this.generateTimeArrays(); } if (changes['value'] && !changes['value'].firstChange) { this.applyValue(changes['value'].currentValue); } if (changes['min'] || changes['max']) { const parsedMin = this.parseToDateStruct(this.min); const parsedMax = this.parseToDateStruct(this.max); if (parsedMin) this.minDate = parsedMin; if (parsedMax) this.maxDate = parsedMax; } } /** SKS14AUG25 Create hours/minutes arrays and their display labels */ generateTimeArrays() { this.hours = Array.from({ length: 24 }, (_, i) => i); this.minutes = Array.from({ length: 60 }, (_, i) => i); if (this.languageCode === 'ar') { this.displayHours = this.hours.map(h => this.convertToArabic(h)); this.displayMinutes = this.minutes.map(m => this.convertToArabic(m)); } else { this.displayHours = this.hours.map(h => String(h).padStart(2, '0')); this.displayMinutes = this.minutes.map(m => String(m).padStart(2, '0')); } } /** SKS14AUG25 Converts number to Arabic digits with leading zero */ convertToArabic(num) { return num.toString().padStart(2, '0').replace(/\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d]); } initYears() { const currentYear = new Date().getFullYear(); this.years = Array.from({ length: 201 }, (_, i) => currentYear - 100 + i); } /** SKS14AUG25 Accepts: * - "2023-10-26T20:00" * - Date * - { year, month, day } * - { date: { year, month, day, time? } } */ applyValue(val) { if (!val) return; // SKS14AUG25 Normalize to { year, month, day, time? } let ymd = null; if (typeof val === 'string' || val instanceof Date) { const d = new Date(val); if (!isNaN(d.getTime())) { ymd = { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate(), time: this.type === 'datetime' ? `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` : undefined }; } } else if ('year' in val && 'month' in val && 'day' in val) { ymd = val; } else if (val.date && 'year' in val.date && 'month' in val.date && 'day' in val.date) { ymd = val.date; } // Apply to picker if (ymd) { // For date & datetime: actually select the day if (this.type === 'date' || this.type === 'datetime') { this.selectedDate = { year: ymd.year, month: ymd.month, day: ymd.day }; // Ensure correct month is visible (optional but nice) this.datepicker?.navigateTo({ year: ymd.year, month: ymd.month }); // If datetime, set time too if (this.type === 'datetime' && ymd.time) { const [h, m] = ymd.time.split(':').map(n => +n); if (!isNaN(h) && !isNaN(m)) this.time = { hour: h, minute: m }; } } // SKS14AUG25 For month: show correct month/year; (datepicker is day-based, we don't select a day) else if (this.type === 'month') { this.selectedDate = null; this.datepicker?.navigateTo({ year: ymd.year, month: ymd.month }); } } // For time only: set time if provided if (this.type === 'time') { if (val) { const [h, m] = val.split(':').map(n => +n); if (!isNaN(h) && !isNaN(m)) this.time = { hour: h, minute: m }; } } // SKS14AUG25 (existing logic for date/time) if (this.type === 'month') { if (typeof val === 'string') { const [year, month] = val.split('-').map(Number); this.monthYear = { year, month }; } else if (val?.date) { this.monthYear = { year: val.date.year, month: val.date.month }; } } } onDateSelect(date) { this.selectedDate = date; if (this.type === 'date') { this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date }); } else if (this.type === 'datetime') { this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: { ...date, time: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` } }); } else if (this.type === 'month') { // If you want month-only result: this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: { year: date.year, month: date.month } }); } } onTimeChange() { if (this.type === 'time') { this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` }); } else if (this.type === 'datetime' && this.selectedDate) { this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: { ...this.selectedDate, time: `${(this.time.hour).toString().padStart(2, '0')}:${(this.time.minute).toString().padStart(2, '0')}` } }); } } toArabicNumber(num) { return num.toString().replace(/\d/g, d => '٠١٢٣٤٥٦٧٨٩'[+d]); } onMonthSelect() { this.modelChange.emit({ from: this.languageCode === 'ar' ? 'gregorianAr' : 'gregorian', date: { year: this.monthYear.year, month: (this.monthYear.month).toString().padStart(2, '0') } }); } parseToDateStruct(value) { if (!value) return null; if (value === 'today') { const today = new Date(); return { year: today.getFullYear(), month: today.getMonth() + 1, day: today.getDate() }; } if (typeof value === 'string') { const parts = value.split('-').map(Number); if (parts.length >= 3) { return { year: parts[0], month: parts[1], day: parts[2] }; } } return null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CustomDatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: CustomDatepickerComponent, isStandalone: true, selector: "app-custom-datepicker", inputs: { type: "type", languageCode: "languageCode", value: "value", min: "min", max: "max" }, outputs: { modelChange: "modelChange" }, providers: [{ provide: NgbDatepickerI18n, useClass: CustomDatepickerI18n }], viewQueries: [{ propertyName: "datepicker", first: true, predicate: ["dp"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<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' ? '\u0627\u0644\u0633\u0646\u0629' : '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' ? '\u0627\u0644\u0634\u0647\u0631' : '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>", styles: [".datepicker-container{background:#fff;border-radius:4px;max-width:400px}.switch-container{display:flex;align-items:center;gap:.5rem;justify-content:end}.calendar-type-label{cursor:pointer;padding:8px 12px;border-radius:6px;background-color:#f5f5f5;display:inline-block;transition:transform .15s ease,background-color .2s ease;-webkit-user-select:none;user-select:none;margin-bottom:5px}.calendar-type-label.click-effect:active{transform:scale(.95);background-color:#e0e0e0}.date-picker-wrapper{display:flex;justify-content:center;margin-bottom:1rem}.month-picker{display:flex;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}.select-group{flex:1;min-width:120px}.select-label{display:block;font-size:.875rem;font-weight:500;color:#374151;margin-bottom:.25rem}.custom-select{width:100%;padding:.5rem;border:1px solid #d1d5db;border-radius:8px;background-color:#fff;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.custom-select:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.time-section{text-align:center}.time-label{display:block;font-size:.875rem;font-weight:500;color:#374151;margin-bottom:.5rem}.time-controls{display:flex;align-items:center;justify-content:center;gap:.5rem}.time-group{display:flex;flex-direction:column;align-items:center;gap:.25rem}.time-unit-label{font-size:.75rem;color:#6b7280;font-weight:500}.time-select{width:70px;padding:.375rem .25rem;border:1px solid #d1d5db;border-radius:6px;background-color:#fff;font-size:.875rem;text-align:center;transition:border-color .2s,box-shadow .2s}.time-select:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.time-separator{font-size:1.25rem;font-weight:700;color:#374151;margin:0 .25rem}:host ::ng-deep ngb-datepicker{border:solid 1px #c9c6c6!important;border-radius:5px;box-shadow:none!important}:host ::ng-deep .ngb-dp-header{padding:5px;background-color:#fff!important;border-bottom:none!important;border-radius:5px!important}:host ::ng-deep .ngb-dp-content{padding:0 5px 5px}:host ::ng-deep .ngb-dp-day{border-radius:6px;margin:1px}:host ::ng-deep .ngb-dp-day .btn-light{border:none;background:transparent}:host ::ng-deep .ngb-dp-day .btn-light:hover{background-color:#f3f4f6}:host ::ng-deep .ngb-dp-day .btn-primary{background-color:#3b82f6;border-color:#3b82f6}:host ::ng-deep .ngb-dp-navigation-select select{border:1px solid #d1d5db;border-radius:6px;padding:.25rem .3rem;background-color:#fff}:host ::ng-deep .ngb-dp-navigation-select{gap:.2rem}:host ::ng-deep .ngb-dp-arrow-btn{margin:0!important}:host ::ng-deep .ngb-dp-arrow{flex:unset!important;width:auto!important;height:auto!important}:host ::ng-deep ngb-datepicker-navigation-select select{font-size:12px!important}:host ::ng-deep ngb-datepicker-month{font-size:12px!important}:host ::ng-deep ngb-datepicker-month div.small{font-size:100%!important}:host ::ng-deep .ngb-dp-weekday{margin-right:2.2px!important}:host ::ng-deep .ngb-dp-navigation-chevron{border-width:.1em .1em 0 0!important;color:gray!important}.ngb-dp-arrow-btn:focus,.ngb-dp-arrow-btn:focus-visible,.ngb-dp-arrow button:focus{outline:none!important;box-shadow:none!important}@media(max-width:480px){.datepicker-container{padding:.75rem}.month-picker{flex-direction:column}.time-controls{flex-wrap:wrap}}.datepicker-popup{display:flex;flex-direction:column;gap:10px}.time-dropdowns{display:flex;align-items:center;gap:5px;justify-content:center}.time-dropdowns select{padding:4px;border-radius:4px;border:1px solid #ccc}.month-picker{display:flex;gap:8px;justify-content:center;align-items:center}.month-picker select{padding:4px 8px;border-radius:5px}:host ::ng-deep :focus-visible{outline:none}\n"], dependencies: [{ kind: "ngmodule", type: NgbDatepickerModule }, { kind: "component", type: i1.NgbDatepicker, selector: "ngb-datepicker", inputs: ["contentTemplate", "dayTemplate", "dayTemplateData", "displayMonths", "firstDayOfWeek", "footerTemplate", "markDisabled", "maxDate", "minDate", "navigation", "outsideDays", "showWeekNumbers", "startDate", "weekdays"], outputs: ["navigate", "dateSelect"], exportAs: ["ngbDatepicker"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i6.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i6.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CustomDatepickerComponent, decorators: [{ type: Component, args: [{ selector: 'app-custom-datepicker', standalone: true, imports: [NgbDatepickerModule, FormsModule, CommonModule], providers: [{ provide: NgbDatepickerI18n, useClass: CustomDatepickerI18n }], template: "<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' ? '\u0627\u0644\u0633\u0646\u0629' : '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' ? '\u0627\u0644\u0634\u0647\u0631' : '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>", styles: [".datepicker-container{background:#fff;border-radius:4px;max-width:400px}.switch-container{display:flex;align-items:center;gap:.5rem;justify-content:end}.calendar-type-label{cursor:pointer;padding:8px 12px;border-radius:6px;background-color:#f5f5f5;display:inline-block;transition:transform .15s ease,background-color .2s ease;-webkit-user-select:none;user-select:none;margin-bottom:5px}.calendar-type-label.click-effect:active{transform:scale(.95);background-color:#e0e0e0}.date-picker-wrapper{display:flex;justify-content:center;margin-bottom:1rem}.month-picker{display:flex;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}.select-group{flex:1;min-width:120px}.select-label{display:block;font-size:.875rem;font-weight:500;color:#374151;margin-bottom:.25rem}.custom-select{width:100%;padding:.5rem;border:1px solid #d1d5db;border-radius:8px;background-color:#fff;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.custom-select:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.time-section{text-align:center}.time-label{display:block;font-size:.875rem;font-weight:500;color:#374151;margin-bottom:.5rem}.time-controls{display:flex;align-items:center;justify-content:center;gap:.5rem}.time-group{display:flex;flex-direction:column;align-items:center;gap:.25rem}.time-unit-label{font-size:.75rem;color:#6b7280;font-weight:500}.time-select{width:70px;padding:.375rem .25rem;border:1px solid #d1d5db;border-radius:6px;background-color:#fff;font-size:.875rem;text-align:center;transition:border-color .2s,box-shadow .2s}.time-select:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.time-separator{font-size:1.25rem;font-weight:700;color:#374151;margin:0 .25rem}:host ::ng-deep ngb-datepicker{border:solid 1px #c9c6c6!important;border-radius:5px;box-shadow:none!important}:host ::ng-deep .ngb-dp-header{padding:5px;background-color:#fff!important;border-bottom:none!important;border-radius:5px!important}:host ::ng-deep .ngb-dp-content{padding:0 5px 5px}:host ::ng-deep .ngb-dp-day{border-radius:6px;margin:1px}:host ::ng-deep .ngb-dp-day .btn-light{border:none;background:transparent}:host ::ng-deep .ngb-dp-day .btn-light:hover{background-color:#f3f4f6}:host ::ng-deep .ngb-dp-day .btn-primary{background-color:#3b82f6;border-color:#3b82f6}:host ::ng-deep .ngb-dp-navigation-select select{border:1px solid #d1d5db;border-radius:6px;padding:.25rem .3rem;background-color:#fff}:host ::ng-deep .ngb-dp-navigation-select{gap:.2rem}:host ::ng-deep .ngb-dp-arrow-btn{margin:0!important}:host ::ng-deep .ngb-dp-arrow{flex:unset!important;width:auto!important;height:auto!important}:host ::ng-deep ngb-datepicker-navigation-select select{font-size:12px!important}:host ::ng-deep ngb-datepicker-month{font-size:12px!important}:host ::ng-deep ngb-datepicker-month div.small{font-size:100%!important}:host ::ng-deep .ngb-dp-weekday{margin-right:2.2px!important}:host ::ng-deep .ngb-dp-navigation-chevron{border-width:.1em .1em 0 0!important;color:gray!important}.ngb-dp-arrow-btn:focus,.ngb-dp-arrow-btn:focus-visible,.ngb-dp-arrow button:focus{outline:none!important;box-shadow:none!important}@media(max-width:480px){.datepicker-container{padding:.75rem}.month-picker{flex-direction:column}.time-controls{flex-wrap:wrap}}.datepicker-popup{display:flex;flex-direction:column;gap:10px}.time-dropdowns{display:flex;align-items:center;gap:5px;justify-content:center}.time-dropdowns select{padding:4px;border-radius:4px;border:1px solid #ccc}.month-picker{display:flex;gap:8px;justify-content:center;align-items:center}.month-picker select{padding:4px 8px;border-radius:5px}:host ::ng-deep :focus-visible{outline:none}\n"] }] }], propDecorators: { type: [{ type: Input }], languageCode: [{ type: Input }], value: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], modelChange: [{ type: Output }], datepicker: [{ type: ViewChild, args: ['dp', { static: false }] }] } }); class DataService { http; apiUrl; // VD 23JAN24 optionally declared httpClient constructor(http) { this.http = http; } getAPIData(tkn, params, resolve, reject, config) { const self = this; // Call the ShengelGo Server const headers = new HttpHeaders().set('Authorization', `${tkn}`); headers.append('Accept', 'application/json'); headers.append('Access-Control-Allow-Origin', '*'); // MR 23JAN24 - This should be removed, for now testing with dev-hse-api 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' }) .subscribe(response => { if (response.status == 200) { resolve(response.body); } else { reject(response.body); } }, error => { reject(error); }); } //calling the node api getAPIFromNode(dataType, operation, param1, param2, api) { const apiUrl = api ? api : 'http://localhost:3001/nxt'; // let params = new HttpParams() // .set('dataType', dataType) // .set('operation', operation) // .set('param1', param1) // .set('param2',param2) let body = { "dataType": dataType, "operation": operation, "param1": param1, "param2": param2, }; return this.http.post(apiUrl, body); } // VD 22May24 -funtion to handling multiple child objects getValue(element, columns) { if (!element || !columns) return null; //AP-25MAR25 Ensure both element and column are valid // MSM27MAR25 check if columns is an array if (typeof columns === 'string' && !Array.isArray(columns)) { columns = [columns]; } columns.forEach((column) => { let flds = column?.split('.') || []; for (let i = 0; i < flds.length; i++) { if (element == null) return null; //AP-25MAR25 Prevent errors if element is null or undefined let splitFlds = flds[i].split('['); if (splitFlds.length === 1) { element = element[flds[i]] !== undefined ? element[flds[i]] : null; } else { let index = Number(splitFlds[1].split(']')[0]); element = element[splitFlds[0]] && Array.isArray(element[splitFlds[0]]) ? element[splitFlds[0]][index] !== undefined ? element[splitFlds[0]][index] : null : null; } } }); return element; } // AP-16APR25 Sets the API URL with the provided apidata apikey(apidata) { this.apiUrl = apidata; } // VD 23JAN24 get callout apiResponse(endpoint) { if (!endpoint || !endpoint.trim()) { throw new Error('Invalid API endpoint'); } const isAbsoluteUrl = endpoint.startsWith('http://') || endpoint.startsWith('https://'); const url = isAbsoluteUrl ? endpoint : `${this.apiUrl}${endpoint}`; return this.http.get(url); } // public apiResponse(endpoint: any): Observable<any> { // // Build headers with token // const headers = new HttpHeaders({ // 'Accept': 'application/json, text/plain, */*', // 'Cache-Control': 'no-cache', // 'Pragma': 'no-cache', // 'token': "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21wYW55SWQiOiJlcmFrZGV2IiwidXNlck5hbWUiOiJrdW1hcmVzYW5AZWxlY3Ryb3Jhay5mdXNpb24iLCJ1X2lkIjoiNjg5MDc5NmY2MGRlZGU3MTM2ZmVhMThiIiwicGVybWlzc2lvbnMiOlsiU3VwZXIgVXNlciIsIkVTUyBVc2VyIiwiSFJNIE1hbmFnZXIiLCJIUk0gVXNlciIsIkFwcCBVc2VyIiwiQ1JNIE1hbmFnZXIiXSwiZW1wbG95ZWVJZCI6IjEwNDAiLCJ1c2VybmFtZSI6Ikt1bWFyZXNhbiBLdW1hcmVzYW4iLCJjb21wYW55TmFtZSI6IkVsZWN0cm8gUkFLIEluZGlhIFByaXZhdGUgTGltaXRlZCIsImlhdCI6MTc1OTQ3ODgyMCwiZXhwIjoxNzY0NjYyODIwfQ.WyqeQnvPJIX1n40nr28T4MflivA-XFFyfbwLGnSXhIc" // 👈 custom header exactly like your request // }); // // Case 1: Full URL // if (endpoint && endpoint.startsWith('https://')) { // return this.http.get(endpoint, { headers }); // } // // Case 2: Prepend apiUrl // if (this.apiUrl && this.apiUrl.trim() !== '' && endpoint !== undefined) { // const url = this.apiUrl + endpoint; // return this.http.get(url, { headers }); // } // // Case 3: Fallback // return this.http.get(endpoint, { headers }); // } // SKS7AUG25 eng ISO format to hijri eng numaral formatToHijriString(date, type) { if (!date) return null; let hijriMoment; if (typeof date === 'string') { //SKS7AUG25 Handle string input hijriMoment = moment(date, ['YYYY-MM-DD', 'YYYY-MM', 'YYYY'], true)?.locale('en'); } else if (date instanceof Date) { //SKS7AUG25 Handle native Date object hijriMoment = moment(date).locale('en'); } else { // Fallback (e.g., moment object passed directly) hijriMoment = moment(date).locale('en'); } if (!hijriMoment?.isValid()) return null; const year = hijriMoment.iYear(); const month = hijriMoment.iMonth() + 1; // 0-based const day = hijriMoment.iDate(); const hour = hijriMoment.hour(); const minute = hijriMoment.minute(); const result = {}; if (type === 'year') { result['year'] = year; } else if (type === 'month') { result['year'] = year; result['month'] = month; } else if (type === 'time') { result['time'] = `${hour}:${minute}`; } else { //SKS7AUG25 Auto-detect based on available data result['year'] = year; if (!isNaN(month)) result['month'] = month; if (!isNaN(day) && day > 0) result['day'] = day; if (hour || minute) result['time'] = `${hour}:${minute}`; } return result; } // SKS7AUG25 en numaral to hijri en numaral object formatToGregorianString(date, type) { const dateStr = `${date.year}-${date.month}-${date.day || 1}`; // Use day=1 if not provided const hijriMoment = moment(dateStr, 'iYYYY-iM-iD').locale('en'); // Force English locale if (date.time && type !== 'month') { const [hours, minutes] = date.time.split(':').map(Number); hijriMoment.set({ hour: hours, minute: minutes }); } if (type === 'month') { return hijriMoment.format('YYYY-MM'); // Gregorian year-month } return hijriMoment.toDate().toISOString(); // Full ISO Gregorian datetime } nxtId() { const prefix = 'ngnxt'; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let core = ''; for (let i = 0; i < 14; i++) { core += chars.charAt(Math.floor(Math.random() * chars.length)); } return prefix + core; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataService, deps: [{ token: i1$1.HttpClient, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1$1.HttpClient, decorators: [{ type: Optional }] }] }); const WEEKDAYS = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س']; const MONTHS = [ 'محرم', 'صفر', 'ربيع الأول', 'ربيع الآخر', 'جمادى الأولى', 'جمادى الآخرة', 'رجب', 'شعبان', 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة' ]; class IslamicI18n extends NgbDatepickerI18n { toArabicNumber(value) { return value.toString().replace(/\d/g, d => '٠١٢٣٤٥٦٧٨٩'[parseInt(d)]); } getMonthShortName(month) { return MONTHS[month - 1]; } getMonthFullName(month) { return MONTHS[month - 1]; } getWeekdayLabel(weekday) { return WEEKDAYS[weekday - 1]; } getDayAriaLabel(date) { return `${this.toArabicNumber(date.day)}-${this.toArabicNumber(date.month)}-${this.toArabicNumber(date.year)}`; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: IslamicI18n, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: IslamicI18n }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: IslamicI18n, decorators: [{ type: Injectable }] }); class HijriDatepickerComponent { dataService; calendar = inject(NgbCalendarIslamicUmalqura); model = this.calendar.getToday(); type = 'date'; value; min; max; languageCode = 'en'; dateChange = new EventEmitter(); datepickerRef; months = MONTHS; years = []; hour = 12; minute = 0; hours = Array.from({ length: 24 }, (_, i) => i); minutes = Array.from({ length: 60 }, (_, i) => i); minDate; maxDate; today; constructor(dataService) { this.dataService = dataService; } ngOnInit() { const todayHijri = this.calendar.getToday(); this.today = todayHijri; if (!this.value) { this.model = todayHijri; } // Default range (Hijri ±100 years) const currentYear = todayHijri.year; this.minDate = { year: currentYear - 100, month: 1, day: 1 }; this.maxDate = { year: currentYear + 100, month: 12, day: 30 }; // Override if input provided const parsedMin = this.parseToHijriDate(this.min); const parsedMax = this.parseToHijriDate(this.max); if (parsedMin) this.minDate = parsedMin; if (parsedMax) this.maxDate = parsedMax; this.generateYears(); this.setInitialDateFromValue(); } ngAfterViewInit() { this.applyArabicNumerals(); } ngOnChanges(changes) { if (changes['value'] && !changes['value'].firstChange) { this.setInitialDateFromValue(); } if (changes['min'] || changes['max']) { const parsedMin = this.parseToHijriDate(this.min); const parsedMax = this.parseToHijriDate(this.max); if (parsedMin) this.minDate = parsedMin; if (parsedMax) this.maxDate = parsedMax; } } setInitialDateFromValue() { if (this.value) { this.model = this.dataService.formatToHijriString(this.value); // Navigate & select the correct date in UI setTimeout(() => { this.datepickerRef?.navigateTo({ year: this.model.year, month: this.model.month }); }); } } // SKS14AUG25 Generate year range ±15 years generateYears() { const currentYear = this.model.year; const startYear = currentYear - 100; const endYear = currentYear + 100; this.years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i); } onDateChange(date) { this.model = date; this.emitDateTime(); this.applyArabicNumerals(); } onMonthChange() { this.emitDateTime(); } onTimeChange() { this.emitDateTime(); } emitDateTime() { if (this.type === 'datetime') { this.dateChange.emit({ ...this.model, time: `${this.hour}:${this.minute}` }); } else if (this.type === 'time') { this.dateChange.emit(`${this.hour}:${this.minute}`); } else if (this.type === 'month') { let temp = { ...this.model }; delete temp.day; this.dateChange.emit(temp); } else { this.dateChange.emit(this.model); } } toArabicNumber(value) { return value.toString().replace(/\d/g, d => '٠١٢٣٤٥٦٧٨٩'[parseInt(d)]); } applyArabicNumerals() { setTimeout(() => { const dayCells = document.querySelectorAll('.ngb-dp-day .btn-light, .ngb-dp-day .btn-primary'); dayCells.forEach((btn) => { const value = btn.textContent?.trim(); if (value && /^\d+$/.test(value)) { btn.textContent = this.toArabicNumber(value);