@rangertechnologies/ngnxt
Version:
This library was used for creating dymanic UI based on the input JSON/data
784 lines (776 loc) • 3.08 MB
JavaScript
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] + '¶m=' + 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);