@corp-products/ui-components 3.5.8 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.storybook/main.ts +12 -0
  2. package/debug-storybook.log +53 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +25 -36
  5. package/src/enums/date-formatter.ts +22 -0
  6. package/src/helper/date-handler.ts +142 -0
  7. package/src/lib/alert-dialog/alert-dialog.component.html +22 -0
  8. package/src/lib/alert-dialog/alert-dialog.component.scss +0 -0
  9. package/src/lib/alert-dialog/alert-dialog.component.spec.ts +22 -0
  10. package/src/lib/alert-dialog/alert-dialog.component.ts +44 -0
  11. package/src/lib/alert-dialog/alert-dialog.interface.ts +6 -0
  12. package/src/lib/alert-dialog/alert-dialog.service.ts +33 -0
  13. package/src/lib/app-accordion/app-accordion.component.html +15 -0
  14. package/src/lib/app-accordion/app-accordion.component.scss +0 -0
  15. package/src/lib/app-accordion/app-accordion.component.spec.ts +21 -0
  16. package/src/lib/app-accordion/app-accordion.component.ts +21 -0
  17. package/src/lib/app-accordion/index.ts +2 -0
  18. package/src/lib/app-breadcrumb/app-breadcrumb.component.html +7 -0
  19. package/src/lib/app-breadcrumb/app-breadcrumb.component.scss +25 -0
  20. package/src/lib/app-breadcrumb/app-breadcrumb.component.ts +140 -0
  21. package/src/lib/app-breadcrumb/app-breadcrumb.interface.ts +15 -0
  22. package/src/lib/app-button/app-button.component.html +7 -0
  23. package/src/lib/app-button/app-button.component.scss +0 -0
  24. package/src/lib/app-button/app-button.component.ts +14 -0
  25. package/src/lib/app-button/app-button.ts +15 -0
  26. package/src/lib/app-button/index.ts +2 -0
  27. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.html +22 -0
  28. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.scss +39 -0
  29. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.spec.ts +21 -0
  30. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.ts +43 -0
  31. package/src/lib/app-dropdown-menu/app-dropdown-menu.ts +17 -0
  32. package/src/lib/app-dropdown-menu/index.ts +2 -0
  33. package/src/lib/app-dropdown-menu/menu-popup.pipe.ts +18 -0
  34. package/src/lib/app-header/app-header.component.html +26 -0
  35. package/src/lib/app-header/app-header.component.scss +0 -0
  36. package/src/lib/app-header/app-header.component.ts +43 -0
  37. package/src/lib/app-side-menu/app-side-menu.component.html +20 -0
  38. package/src/lib/app-side-menu/app-side-menu.component.ts +28 -0
  39. package/src/lib/app-side-menu/routes-names.ts +28 -0
  40. package/src/lib/app-side-menu/side-menu-items.ts +45 -0
  41. package/src/lib/app-side-menu/side-menu.ts +12 -0
  42. package/src/lib/app-tabs/app-tab.interface.ts +27 -0
  43. package/src/lib/app-tabs/app-tabs.component.html +37 -0
  44. package/src/lib/app-tabs/app-tabs.component.scss +103 -0
  45. package/src/lib/app-tabs/app-tabs.component.spec.ts +21 -0
  46. package/src/lib/app-tabs/app-tabs.component.ts +67 -0
  47. package/src/lib/app-tabs/index.ts +2 -0
  48. package/src/lib/bottom-sheet/bottom-sheet.component.html +18 -0
  49. package/src/lib/bottom-sheet/bottom-sheet.component.scss +31 -0
  50. package/src/lib/bottom-sheet/bottom-sheet.component.ts +26 -0
  51. package/src/lib/confirmation-dialog/confirmation-dialog.component.html +37 -0
  52. package/src/lib/confirmation-dialog/confirmation-dialog.component.scss +0 -0
  53. package/src/lib/confirmation-dialog/confirmation-dialog.component.spec.ts +22 -0
  54. package/src/lib/confirmation-dialog/confirmation-dialog.component.ts +64 -0
  55. package/src/lib/confirmation-dialog/confirmation-dialog.interface.ts +13 -0
  56. package/src/lib/confirmation-dialog/confirmation-dialog.service.ts +34 -0
  57. package/src/lib/dual-calender/date-picker-switcher/date-picker-switcher.component.html +27 -0
  58. package/src/lib/dual-calender/date-picker-switcher/date-picker-switcher.component.scss +22 -0
  59. package/src/lib/dual-calender/date-picker-switcher/date-picker-switcher.component.ts +64 -0
  60. package/src/lib/dual-calender/dual-calendar.component.html +30 -0
  61. package/src/lib/dual-calender/dual-calendar.component.scss +265 -0
  62. package/src/lib/dual-calender/dual-calendar.component.ts +157 -0
  63. package/src/lib/dual-calender/gregorian-calendar/gregorian-calendar.component.html +21 -0
  64. package/src/lib/dual-calender/gregorian-calendar/gregorian-calendar.component.scss +0 -0
  65. package/src/lib/dual-calender/gregorian-calendar/gregorian-calendar.component.spec.ts +21 -0
  66. package/src/lib/dual-calender/gregorian-calendar/gregorian-calendar.component.ts +65 -0
  67. package/src/lib/dual-calender/hijri-calendar/hijri-calendar.component.html +10 -0
  68. package/src/lib/dual-calender/hijri-calendar/hijri-calendar.component.scss +0 -0
  69. package/src/lib/dual-calender/hijri-calendar/hijri-calendar.component.spec.ts +21 -0
  70. package/src/lib/dual-calender/hijri-calendar/hijri-calendar.component.ts +66 -0
  71. package/src/lib/dual-calender/services/gregorian-i18n.service.ts +123 -0
  72. package/src/lib/dual-calender/services/islamic-i18n.service.ts +119 -0
  73. package/src/lib/dual-calender/utils/date-i18n.utils.ts +58 -0
  74. package/src/lib/dynamic-form/dynamic-form.component.html +84 -0
  75. package/src/lib/dynamic-form/dynamic-form.component.scss +0 -0
  76. package/src/lib/dynamic-form/dynamic-form.component.spec.ts +21 -0
  77. package/src/lib/dynamic-form/dynamic-form.component.ts +58 -0
  78. package/src/lib/dynamic-form/dynamic-form.interface.ts +96 -0
  79. package/src/lib/dynamic-side-bar-v2/dynamic-sidebar.component.html +32 -0
  80. package/src/lib/dynamic-side-bar-v2/dynamic-sidebar.component.scss +3 -0
  81. package/src/lib/dynamic-side-bar-v2/dynamic-sidebar.component.ts +82 -0
  82. package/src/lib/dynamic-side-bar-v2/dynamic-sidebar.config.ts +31 -0
  83. package/src/lib/dynamic-side-bar-v2/dynamic-sidebar.service.ts +41 -0
  84. package/src/lib/form-components/@utils/form-utils.ts +12 -0
  85. package/src/lib/form-components/@utils/validations/error-keys.enum.ts +25 -0
  86. package/src/lib/form-components/@utils/validations/form-validation.service.ts +68 -0
  87. package/src/lib/form-components/@utils/validations/index.ts +4 -0
  88. package/src/lib/form-components/@utils/validations/numbers-only.validator.ts +10 -0
  89. package/src/lib/form-components/@utils/validations/validation-message.pipe.ts +24 -0
  90. package/src/lib/form-components/components/auto-complete/auto-complete.component.html +35 -0
  91. package/src/lib/form-components/components/auto-complete/auto-complete.component.scss +12 -0
  92. package/src/lib/form-components/components/auto-complete/auto-complete.component.spec.ts +21 -0
  93. package/src/lib/form-components/components/auto-complete/auto-complete.component.ts +82 -0
  94. package/src/lib/form-components/components/base-input.component.ts +35 -0
  95. package/src/lib/form-components/components/date-picker/date-picker.component.html +40 -0
  96. package/src/lib/form-components/components/date-picker/date-picker.component.scss +32 -0
  97. package/src/lib/form-components/components/date-picker/date-picker.component.spec.ts +21 -0
  98. package/src/lib/form-components/components/date-picker/date-picker.component.ts +81 -0
  99. package/src/lib/form-components/components/date-picker/date-picker.validator.ts +38 -0
  100. package/src/lib/form-components/components/input/input.component.html +80 -0
  101. package/src/lib/form-components/components/input/input.component.scss +46 -0
  102. package/src/lib/form-components/components/input/input.component.spec.ts +21 -0
  103. package/src/lib/form-components/components/input/input.component.ts +56 -0
  104. package/src/lib/form-components/components/select/select.component.html +123 -0
  105. package/src/lib/form-components/components/select/select.component.scss +53 -0
  106. package/src/lib/form-components/components/select/select.component.spec.ts +21 -0
  107. package/src/lib/form-components/components/select/select.component.ts +87 -0
  108. package/src/lib/form-components/components/select-button/select-button.component.html +21 -0
  109. package/src/lib/form-components/components/select-button/select-button.component.scss +0 -0
  110. package/src/lib/form-components/components/select-button/select-button.component.spec.ts +21 -0
  111. package/src/lib/form-components/components/select-button/select-button.component.ts +22 -0
  112. package/src/lib/form-components/components/switcher/switch.component.html +5 -0
  113. package/src/lib/form-components/components/switcher/switch.component.scss +0 -0
  114. package/src/lib/form-components/components/switcher/switch.component.spec.ts +21 -0
  115. package/src/lib/form-components/components/switcher/switch.component.ts +25 -0
  116. package/src/lib/form-components/index.ts +9 -0
  117. package/src/lib/form-components/interfaces/index.ts +1 -0
  118. package/src/lib/form-components/interfaces/label-value.ts +4 -0
  119. package/src/lib/ico-moon-icon/ico-moon-icon.component.ts +23 -0
  120. package/src/lib/read-more/read-more.component.html +17 -0
  121. package/src/lib/read-more/read-more.component.scss +0 -0
  122. package/src/lib/read-more/read-more.component.spec.ts +21 -0
  123. package/src/lib/read-more/read-more.component.ts +21 -0
  124. package/src/lib/side-bar/side-bar.component.html +24 -0
  125. package/src/lib/side-bar/side-bar.component.scss +22 -0
  126. package/src/lib/side-bar/side-bar.component.spec.ts +21 -0
  127. package/src/lib/side-bar/side-bar.component.ts +33 -0
  128. package/src/lib/side-bar-dynamic/data-injector.pipe.ts +15 -0
  129. package/src/lib/side-bar-dynamic/dynamic-sidebar.service.ts +116 -0
  130. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.html +42 -0
  131. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.scss +5 -0
  132. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.spec.ts +21 -0
  133. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.ts +37 -0
  134. package/src/lib/side-bar-dynamic/side-bar-utils.ts +30 -0
  135. package/src/lib/side-bar-dynamic/sidebar-config.ts +48 -0
  136. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.html +20 -0
  137. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.scss +0 -0
  138. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.spec.ts +21 -0
  139. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.ts +21 -0
  140. package/src/lib/user-info/user-info.component.html +10 -0
  141. package/src/lib/user-info/user-info.component.ts +11 -0
  142. package/src/public-api.ts +29 -0
  143. package/tsconfig.lib.json +18 -0
  144. package/tsconfig.lib.prod.json +11 -0
  145. package/tsconfig.spec.json +14 -0
  146. package/fesm2022/corp-products-ui-components.mjs +0 -2252
  147. package/fesm2022/corp-products-ui-components.mjs.map +0 -1
  148. package/index.d.ts +0 -720
@@ -0,0 +1,31 @@
1
+ import { Type, ViewContainerRef } from "@angular/core";
2
+ import { AppButtonIconPos, AppButtonSeverity, AppButtonSize, AppButtonVariant } from "../app-button";
3
+
4
+ export interface DynamicSidebarV2Actions {
5
+ submit: () => void;
6
+ close: () => void;
7
+ }
8
+ export interface DynamicSidebarV2Config<T = any> {
9
+ title?: string;
10
+ component: Type<T>;
11
+ data?: Partial<T>;
12
+ size?: 'sm' | 'md' | 'lg';
13
+ position?: 'left' | 'right';
14
+ styleClass?: string;
15
+ viewContainerRef?: ViewContainerRef;
16
+ actions?: SidebarV2Actions
17
+ }
18
+
19
+ export interface SidebarActionV2Config {
20
+ title?: string;
21
+ icon?: string;
22
+ size?: AppButtonSize;
23
+ style?: AppButtonSeverity;
24
+ iconPos?: AppButtonIconPos;
25
+ variant?: AppButtonVariant;
26
+ }
27
+
28
+ export interface SidebarV2Actions {
29
+ cancel?: SidebarActionV2Config;
30
+ save?: SidebarActionV2Config;
31
+ }
@@ -0,0 +1,41 @@
1
+ import { ApplicationRef, ComponentRef, EnvironmentInjector, Injectable, Injector, createComponent, inject } from '@angular/core';
2
+ import { DynamicSidebarComponent } from './dynamic-sidebar.component';
3
+ import { DynamicSidebarV2Config } from './dynamic-sidebar.config';
4
+
5
+ @Injectable({ providedIn: 'root' })
6
+ export class DynamicSidebarV2Service {
7
+ private sidebarRef?: ComponentRef<DynamicSidebarComponent>;
8
+ private readonly appRef = inject(ApplicationRef);
9
+ private readonly injector = inject(Injector);
10
+ private readonly envInjector = inject(EnvironmentInjector);
11
+
12
+ open<T>(options: DynamicSidebarV2Config<T>) {
13
+ this.close();
14
+
15
+ this.sidebarRef = createComponent(DynamicSidebarComponent, {
16
+ environmentInjector: this.envInjector,
17
+ elementInjector: this.injector,
18
+ });
19
+
20
+ const instance = this.sidebarRef.instance;
21
+ instance.title = options.title ?? '';
22
+ instance.sidebarSize = options.size ?? 'md';
23
+ instance.position = options.position ?? 'left';
24
+ instance.styleClass = options.styleClass ?? '';
25
+ instance.component = options.component;
26
+ instance.data = options.data ?? {};
27
+ instance.actions = options.actions;
28
+ instance.closed.subscribe(() => this.close());
29
+
30
+ this.appRef.attachView(this.sidebarRef.hostView);
31
+ const domElem = (this.sidebarRef.hostView as any).rootNodes[0] as HTMLElement;
32
+ document.body.appendChild(domElem);
33
+ }
34
+
35
+ close() {
36
+ if (this.sidebarRef) {
37
+ this.sidebarRef.destroy();
38
+ this.sidebarRef = undefined;
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,12 @@
1
+ import {FormControl, FormGroup} from "@angular/forms";
2
+
3
+ export class FormUtils {
4
+ static getFormControl(controlName: string, form: FormGroup): FormControl {
5
+ if (!form) throw new Error(`Form is not initialized.`);
6
+ const formControl = form.get(controlName) as FormControl;
7
+
8
+ if (!formControl) throw new Error(`There's no form control with given name. '${controlName}'`);
9
+
10
+ return formControl;
11
+ }
12
+ }
@@ -0,0 +1,25 @@
1
+ export enum BasicErrorKeysEnum {
2
+ required = 'REQUIRED',
3
+ email = 'EMAIL',
4
+ pattern = 'PATTERN',
5
+ invalidArFormat = 'INVALID_AR_FORMAT',
6
+ invalidLink = 'INVALID_LINK',
7
+ endDateBeforeStartDate = 'END_DATE_BEFORE_START_DATE',
8
+ startDateEqualsEndDate = 'START_DATE_EQUALS_END_DATE',
9
+ endTimeBeforeStartTime = 'END_TIME_BEFORE_START_TIME',
10
+ startTimeEqualsEndTime = 'START_TIME_EQUALS_END_TIME',
11
+ integer = 'INTEGER',
12
+ positiveNumber = 'POSITIVE_NUMBER',
13
+ fileSelected = 'FILE_SELECTED',
14
+ default = 'DEFAULT',
15
+ numbersOnly = 'NUMBERS_ONLY',
16
+ }
17
+ export enum ErrorsWithValuesKeysEnum {
18
+ minlength = 'MIN_LENGTH',
19
+ maxlength = 'MAX_LENGTH',
20
+ min = 'MIN',
21
+ max = 'MAX',
22
+ maxSize = 'MAX_SIZE',
23
+ maxFiles = 'MAX_FILES',
24
+ allowedTypes = 'ALLOWED_TYPES',
25
+ }
@@ -0,0 +1,68 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import { InterpolationParameters } from '@ngx-translate/core';
4
+ import { BasicErrorKeysEnum, ErrorsWithValuesKeysEnum } from './error-keys.enum';
5
+
6
+ @Injectable({
7
+ providedIn: 'root',
8
+ })
9
+ export class FormValidationService {
10
+ private translate = inject(TranslateService);
11
+
12
+ private getTranslation(key: string, interpolateParams?: InterpolationParameters): string {
13
+ return this.translate.instant(`VALIDATION.${key}`, interpolateParams);
14
+ }
15
+
16
+ getErrorMessage(errorKey: string, errorValue: any): string {
17
+ if (this.isBasicErrorKey(errorKey)) {
18
+ return this.getTranslation(BasicErrorKeysEnum[errorKey as keyof typeof BasicErrorKeysEnum]);
19
+ }
20
+
21
+ if (this.isErrorWithValueKey(errorKey)) {
22
+ return this.getErrorWithValueMessage(
23
+ errorKey as keyof typeof ErrorsWithValuesKeysEnum,
24
+ errorValue
25
+ );
26
+ }
27
+
28
+ return this.getTranslation(BasicErrorKeysEnum.default);
29
+ }
30
+
31
+ // Basic error keys are the keys that don't have any values to interpolate. like required, email, etc.
32
+ private isBasicErrorKey(key: string): key is keyof typeof BasicErrorKeysEnum {
33
+ return Object.keys(BasicErrorKeysEnum).includes(key as BasicErrorKeysEnum);
34
+ }
35
+
36
+ // Error keys with values are the keys that have values to interpolate. like minlength, maxlength, etc.
37
+ private isErrorWithValueKey(key: string): key is keyof typeof ErrorsWithValuesKeysEnum {
38
+ return Object.keys(ErrorsWithValuesKeysEnum).includes(key as ErrorsWithValuesKeysEnum);
39
+ }
40
+
41
+ private getErrorWithValueMessage(
42
+ errorKey: keyof typeof ErrorsWithValuesKeysEnum,
43
+ errorValue: any
44
+ ): string {
45
+ const messages: Record<keyof typeof ErrorsWithValuesKeysEnum, (value: any) => string> = {
46
+ minlength: (val) =>
47
+ this.getTranslation(ErrorsWithValuesKeysEnum.minlength, {
48
+ requiredLength: val?.requiredLength,
49
+ actualLength: val?.actualLength,
50
+ }),
51
+ maxlength: (val) =>
52
+ this.getTranslation(ErrorsWithValuesKeysEnum.maxlength, {
53
+ requiredLength: val?.requiredLength,
54
+ actualLength: val?.actualLength,
55
+ }),
56
+ min: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.min, { min: val?.min }),
57
+ max: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.max, { max: val?.max }),
58
+ maxSize: (val) =>
59
+ this.getTranslation(ErrorsWithValuesKeysEnum.maxSize, { size: val?.requiredLength }),
60
+ maxFiles: (val) =>
61
+ this.getTranslation(ErrorsWithValuesKeysEnum.maxFiles, { size: val?.requiredLength }),
62
+ allowedTypes: (val) =>
63
+ this.getTranslation(ErrorsWithValuesKeysEnum.allowedTypes, { types: val?.join(', ') }),
64
+ };
65
+
66
+ return messages[errorKey](errorValue);
67
+ }
68
+ }
@@ -0,0 +1,4 @@
1
+ export * from './error-keys.enum';
2
+ export * from './validation-message.pipe';
3
+ export * from './form-validation.service';
4
+ export * from './numbers-only.validator';
@@ -0,0 +1,10 @@
1
+ import { AbstractControl, ValidationErrors } from '@angular/forms';
2
+
3
+ export function numbersOnlyValidator(control: AbstractControl): ValidationErrors | null {
4
+ const value = control.value;
5
+ if (value === null || value === undefined || value === '') {
6
+ return null;
7
+ }
8
+ const isNumbersOnly = /^[0-9]+$/.test(value);
9
+ return isNumbersOnly ? null : { numbersOnly: true };
10
+ }
@@ -0,0 +1,24 @@
1
+ import { inject, Pipe, PipeTransform } from "@angular/core";
2
+ import { ValidationErrors } from "@angular/forms";
3
+ import { FormValidationService } from "./form-validation.service";
4
+
5
+ @Pipe({
6
+ name: "validationErrors",
7
+ standalone: true,
8
+ pure: true
9
+ })
10
+ export class ValidationErrorsPipe implements PipeTransform {
11
+ private formValidationService = inject(FormValidationService);
12
+
13
+ // allowed keys here to handle errors in case of cross-validators like startDate and endDate validators,
14
+ // we pass this custom key to handle the error messages only for the allowed keys
15
+ transform(errors: ValidationErrors | null, allowedKeys?: string[]): string[] {
16
+ if (!errors) return [];
17
+
18
+ return Object.keys(errors)
19
+ .filter((errorKey) => !allowedKeys || allowedKeys.includes(errorKey)) // Filter errors if allowedKeys are provided
20
+ .map((errorKey) => {
21
+ return this.formValidationService.getErrorMessage(errorKey, errors[errorKey]);
22
+ });
23
+ }
24
+ }
@@ -0,0 +1,35 @@
1
+ <div class="field flex flex-col gap-2 my-3 relative">
2
+ @if (!required) {
3
+ <span class="absolute top-[6px] left-0 text-[10px] text-gray-400">{{'forms.config.optional' | translate}}</span>
4
+ }
5
+ <p-floatlabel [variant]="variant" class="autoComplete">
6
+ <p-auto-complete (keydown)="onKeyDown($event)" (onBlur)="onBlur($event)" (completeMethod)="search($event)" (onSelect)="onSelect($event)" [delay]="delay"
7
+ [disabled]="disabled" [formControl]="control" [id]="inputId" fluid [typeahead]="typeAhead"
8
+ [inputStyleClass]="'reset-default-styles w-full' + (basicInput ? ' basic-style': ' ')"
9
+ [minLength]="minLengthToSearch" [name]="name" [ngClass]="{ 'p-invalid ng-dirty ng-invalid': isInvalid}"
10
+ [placeholder]="placeholder" [styleClass]="'w-full'" multiple [suggestions]="items">
11
+ <ng-template let-item pTemplate="item">
12
+ @if (selectedItemTemplate) {
13
+ <ng-container [ngTemplateOutletContext]="{ $implicit: item }" [ngTemplateOutlet]="selectedItemTemplate" />
14
+ }
15
+ </ng-template>
16
+ </p-auto-complete>
17
+ <label [for]="inputId">
18
+ {{ label }}
19
+ @if (required) {
20
+ <span [class.text-required]="isInvalid">*</span>
21
+ }
22
+ </label>
23
+ </p-floatlabel>
24
+ @if (hint) {
25
+ <small class="p-mt-1">{{ hint }}</small>
26
+ }
27
+ @if (isInvalid && (control.dirty || control.touched)) {
28
+ <small class="p-error">
29
+ @for (error of control.errors | validationErrors; track error) {
30
+ {{ error }}<br>
31
+ }
32
+ </small>
33
+ }
34
+
35
+ </div>
@@ -0,0 +1,12 @@
1
+ .text-required {
2
+ color: red;
3
+ }
4
+
5
+ .p-error {
6
+ color: #dc2626;
7
+ }
8
+ .autoComplete{
9
+ .p-floatlabel-in {
10
+ height: auto !important;
11
+ }
12
+ }
@@ -0,0 +1,21 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { AutoCompleteComponent } from './auto-complete.component';
3
+
4
+ describe('AutoCompleteComponent', () => {
5
+ let component: AutoCompleteComponent;
6
+ let fixture: ComponentFixture<AutoCompleteComponent>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [AutoCompleteComponent],
11
+ }).compileComponents();
12
+
13
+ fixture = TestBed.createComponent(AutoCompleteComponent);
14
+ component = fixture.componentInstance;
15
+ fixture.detectChanges();
16
+ });
17
+
18
+ it('should create', () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,82 @@
1
+ import { JsonPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
2
+ import { Component, EventEmitter, Input, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
3
+ import { ReactiveFormsModule } from '@angular/forms';
4
+ import { TranslatePipe } from '@ngx-translate/core';
5
+ import { PrimeTemplate } from 'primeng/api';
6
+ import {
7
+ AutoComplete,
8
+ AutoCompleteCompleteEvent,
9
+ AutoCompleteSelectEvent,
10
+ } from 'primeng/autocomplete';
11
+ import { FloatLabel } from 'primeng/floatlabel';
12
+ import { ValidationErrorsPipe } from '../../@utils/validations/validation-message.pipe';
13
+ import { BaseInputComponent } from '../base-input.component';
14
+
15
+ @Component({
16
+ selector: 'stc-auto-complete',
17
+ standalone: true,
18
+ imports: [
19
+ ReactiveFormsModule,
20
+ AutoComplete,
21
+ PrimeTemplate,
22
+ NgIf,
23
+ NgTemplateOutlet,
24
+ NgClass,
25
+ JsonPipe,
26
+ ValidationErrorsPipe,
27
+ TranslatePipe,
28
+ FloatLabel,
29
+ ],
30
+ templateUrl: './auto-complete.component.html',
31
+ styleUrl: './auto-complete.component.scss',
32
+ encapsulation: ViewEncapsulation.None
33
+ })
34
+ export class AutoCompleteComponent extends BaseInputComponent {
35
+ @Input() selectedItemTemplate: TemplateRef<unknown> | null = null;
36
+ // eslint-disable-next-line @angular-eslint/no-output-on-prefix
37
+ @Output() onSearch: EventEmitter<string> = new EventEmitter<string>();
38
+ @Output() selectOption: EventEmitter<AutoCompleteSelectEvent> =
39
+ new EventEmitter<AutoCompleteSelectEvent>();
40
+ @Input() items: any[] = [];
41
+ @Input() minLengthToSearch = 3;
42
+ @Input() delay = 300; // default value
43
+ @Input() basicInput!: boolean;
44
+ @Input() typeAhead: boolean = false;
45
+ @Input() variant: 'in' | 'over' | 'on' = 'over';
46
+
47
+ constructor() {
48
+ super();
49
+ }
50
+
51
+ search(event: AutoCompleteCompleteEvent) {
52
+ this.onSearch.emit(event.query);
53
+ }
54
+
55
+ onSelect(event: AutoCompleteSelectEvent) {
56
+ this.selectOption.emit(event);
57
+ }
58
+
59
+ onKeyDown(event: KeyboardEvent) {
60
+ if (!['Enter', 'Tab', ' '].includes(event.key)) return;
61
+ event.preventDefault();
62
+ const input = event.target as HTMLInputElement;
63
+ this.addValueFromInput(input);
64
+ }
65
+
66
+ onBlur(event: Event) {
67
+ const input = event.target as HTMLInputElement;
68
+ this.addValueFromInput(input);
69
+ }
70
+
71
+ private addValueFromInput(input: HTMLInputElement) {
72
+ const value = input.value?.trim();
73
+ if (!value) return;
74
+ const current = this.control.value ?? [];
75
+ if (!current.includes(value)) {
76
+ this.control.setValue([...current, value]);
77
+ this.control.markAsDirty();
78
+ }
79
+ input.value = '';
80
+ }
81
+
82
+ }
@@ -0,0 +1,35 @@
1
+ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
2
+ import { FormControl, Validators } from '@angular/forms';
3
+ import { Subject, takeUntil } from 'rxjs';
4
+ @Component({
5
+ template: '',
6
+ })
7
+ export abstract class BaseInputComponent implements OnInit, OnDestroy {
8
+ @Input({ required: true }) control!: FormControl;
9
+ @Input() name: string = '';
10
+ @Input() label?: string;
11
+ @Input() placeholder: string = '';
12
+ @Input() inputId!: string;
13
+ @Input() readonly: boolean = false;
14
+ @Input() disabled: boolean = false;
15
+ @Input() hint?: string;
16
+
17
+ protected destroy$ = new Subject<void>();
18
+
19
+ get required(): boolean {
20
+ return this.control.hasValidator(Validators.required);
21
+ }
22
+
23
+ get isInvalid(): boolean {
24
+ return this.control.invalid && this.control.touched;
25
+ }
26
+
27
+ ngOnInit() {
28
+
29
+ }
30
+
31
+ ngOnDestroy(): void {
32
+ this.destroy$.next();
33
+ this.destroy$.complete();
34
+ }
35
+ }
@@ -0,0 +1,40 @@
1
+ <div class="field flex flex-col gap-2 my-3 relative">
2
+ <p-floatlabel [variant]="variant">
3
+ <p-datepicker [selectionMode]="selectionMode" [disabled]="disabled" [formControl]="innerControl"
4
+ (onSelect)="onDateChange($event)" [iconDisplay]="'input'" [showClear]="showClear" (onClear)="afterClearDate()"
5
+ [inputId]="inputId" [inputStyleClass]="'reset-default-styles ' + (basicInput ? 'basic-style' : '')"
6
+ [ngClass]="{ 'p-invalid ng-dirty ng-invalid': isInvalid }" [placeholder]="placeholder" [showIcon]="showIcon"
7
+ [class]="'w-full'" appendTo="body" [timeOnly]="isTimeOnly" [hourFormat]="hourFormat" [minDate]="minDate"
8
+ [maxDate]="maxDate" panelStyleClass="d-ltr">
9
+ @if (isTimeOnly) {
10
+ <ng-template #inputicon let-clickCallBack="clickCallBack">
11
+ <i class="text-[18px] font-icon-time-clock" (click)="clickCallBack($event)"></i>
12
+ </ng-template>
13
+ <ng-template pTemplate="footer">
14
+ <div class="p-datepicker-buttonbar">
15
+ <button pButton type="button" class="p-button-text" (click)="selectCurrentTime($event)">الان
16
+ </button>
17
+ <button pButton type="button" class="p-button-text" (click)="clearButtonClick($event)"> الغاء</button>
18
+ </div>
19
+ </ng-template>
20
+ }
21
+ </p-datepicker>
22
+
23
+ <label [for]="inputId">
24
+ {{ label }}
25
+ @if (required) {
26
+ <span [class.text-required]="required">*</span>
27
+ }
28
+ </label>
29
+ </p-floatlabel>
30
+ @if (hint) {
31
+ <small class="p-mt-1">{{ hint }}</small>
32
+ }
33
+ @if (isInvalid && (control.dirty || control.touched)) {
34
+ <small class="p-error">
35
+ @for (error of control.errors | validationErrors; track error) {
36
+ {{ error }}<br>
37
+ }
38
+ </small>
39
+ }
40
+ </div>
@@ -0,0 +1,32 @@
1
+ .p-datepicker {
2
+ @apply relative;
3
+ }
4
+
5
+ .p-datepicker-clear-icon {
6
+ @apply cursor-pointer absolute top-[50%] left-[13px] text-[var(--p-datepicker-input-icon-color)] ml-[calc(var(--p-icon-size))] leading-none;
7
+ }
8
+
9
+ .d-ltr {
10
+ direction: ltr;
11
+ }
12
+
13
+
14
+ .text-required {
15
+ color: red;
16
+ }
17
+
18
+ .p-error {
19
+ color: #dc2626;
20
+ }
21
+
22
+ stc-date-picker.w-full,
23
+ stc-date-picker.w-full .field,
24
+ stc-date-picker.w-full .p-floatlabel,
25
+ stc-date-picker.w-full .p-datepicker,
26
+ stc-date-picker.w-full .p-inputtext {
27
+ @apply w-full;
28
+ }
29
+
30
+ stc-date-picker.w-full .p-datepicker {
31
+ @apply block;
32
+ }
@@ -0,0 +1,21 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { DatePickerComponent } from 'nx-BOD-workspace/libs/ui-components/src/lib/form-components/components/date-picker/date-picker.component';
3
+
4
+ describe('DatePickerComponent', () => {
5
+ let component: DatePickerComponent;
6
+ let fixture: ComponentFixture<DatePickerComponent>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [DatePickerComponent],
11
+ }).compileComponents();
12
+
13
+ fixture = TestBed.createComponent(DatePickerComponent);
14
+ component = fixture.componentInstance;
15
+ fixture.detectChanges();
16
+ });
17
+
18
+ it('should create', () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,81 @@
1
+ import { NgClass } from '@angular/common';
2
+ import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
3
+ import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
4
+ import { ValidationErrorsPipe } from '../../@utils/validations';
5
+ import { DatePicker, DatePickerModule } from 'primeng/datepicker';
6
+ import { FloatLabelModule } from 'primeng/floatlabel';
7
+ import { BaseInputComponent } from '../base-input.component';
8
+ import { formatDate } from '@angular/common';
9
+
10
+ @Component({
11
+ selector: 'stc-date-picker',
12
+ standalone: true,
13
+ imports: [
14
+ FormsModule,
15
+ DatePicker,
16
+ ReactiveFormsModule,
17
+ NgClass,
18
+ DatePickerModule,
19
+ ValidationErrorsPipe,
20
+ FloatLabelModule,
21
+ ],
22
+ templateUrl: './date-picker.component.html',
23
+ styleUrl: './date-picker.component.scss',
24
+ encapsulation: ViewEncapsulation.None,
25
+ })
26
+ export class DatePickerComponent extends BaseInputComponent {
27
+ @Input() showIcon: boolean = false;
28
+ @Input() showClear: boolean = false;
29
+ @Input() basicInput!: boolean;
30
+ @Input() isTimeOnly: boolean = false;
31
+ @Input() minDate: Date | undefined | null;
32
+ @Input() maxDate: Date | undefined | null;
33
+ @Input() hourFormat: '12' | '24' = '12';
34
+ nowTime = new Date();
35
+ @Input() selectionMode: 'single' | 'range' = 'single';
36
+ @Output() onAfterClearDate = new EventEmitter<void>();
37
+ @Input() variant: 'in' | 'over' | 'on' = 'over';
38
+ @Input() withoutTime: boolean = false;
39
+ innerControl = new FormControl<Date | null>(null);
40
+
41
+ constructor() {
42
+ super();
43
+ }
44
+
45
+ override ngOnInit() {
46
+ if (typeof this.control?.value === 'string') {
47
+ const date = new Date(this.control.value);
48
+ if (date) {
49
+ this.innerControl.setValue(date, { emitEvent: false });
50
+ }
51
+ }
52
+
53
+ this.control.valueChanges.subscribe((value) => {
54
+ if (!value) this.innerControl.reset();
55
+ });
56
+ }
57
+
58
+ selectCurrentTime(e: any) {
59
+ if (this.withoutTime) {
60
+ const d = new Date();
61
+ this.control.setValue(d.toISOString().split('T')[0]);
62
+ return;
63
+ }
64
+ this.control.setValue(this.nowTime);
65
+ }
66
+
67
+ clearButtonClick(e: any) {
68
+ this.control.setValue(null);
69
+ }
70
+
71
+ afterClearDate() {
72
+ this.control.reset();
73
+ this.onAfterClearDate.emit();
74
+ }
75
+
76
+ onDateChange(value: any) {
77
+ if (!this.withoutTime || !value) return;
78
+ const dateOnly = value instanceof Date ? formatDate(value, 'yyyy-MM-dd', 'en-US') : value;
79
+ this.control.setValue(dateOnly, { emitEvent: true });
80
+ }
81
+ }
@@ -0,0 +1,38 @@
1
+ import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
2
+
3
+ export const dateRangeValidator =
4
+ (fromKey: string, toKey: string): ValidatorFn =>
5
+ (control: AbstractControl): ValidationErrors | null => {
6
+ const fromControl = control.get?.(fromKey);
7
+ const toControl = control.get?.(toKey);
8
+
9
+ if (!fromControl?.value || !toControl?.value) {
10
+ return null;
11
+ }
12
+
13
+ const from = new Date(fromControl.value);
14
+ const to = new Date(toControl.value);
15
+
16
+ from.setHours(0, 0, 0, 0);
17
+ to.setHours(0, 0, 0, 0);
18
+
19
+ return from > to ? { invalidDateRange: true } : null;
20
+ };
21
+
22
+ export const notFutureDateValidator =
23
+ (dateKey: string): ValidatorFn =>
24
+ (control: AbstractControl): ValidationErrors | null => {
25
+ const dateControl = control.get?.(dateKey);
26
+
27
+ if (!dateControl?.value) {
28
+ return null;
29
+ }
30
+
31
+ const valueDate = new Date(dateControl.value);
32
+ valueDate.setHours(0, 0, 0, 0);
33
+
34
+ const today = new Date();
35
+ today.setHours(0, 0, 0, 0);
36
+
37
+ return valueDate > today ? { futureDate: true } : null;
38
+ };