@corp-products/ui-components 0.0.1

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 (97) hide show
  1. package/README.md +3 -0
  2. package/eslint.config.js +34 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +19 -0
  5. package/project.json +29 -0
  6. package/src/index.ts +13 -0
  7. package/src/lib/app-accordion/app-accordion.component.html +15 -0
  8. package/src/lib/app-accordion/app-accordion.component.scss +0 -0
  9. package/src/lib/app-accordion/app-accordion.component.spec.ts +21 -0
  10. package/src/lib/app-accordion/app-accordion.component.ts +21 -0
  11. package/src/lib/app-accordion/index.ts +2 -0
  12. package/src/lib/app-button/app-button.component.html +13 -0
  13. package/src/lib/app-button/app-button.component.scss +0 -0
  14. package/src/lib/app-button/app-button.component.ts +28 -0
  15. package/src/lib/app-button/app-button.ts +15 -0
  16. package/src/lib/app-button/index.ts +2 -0
  17. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.html +25 -0
  18. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.scss +39 -0
  19. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.spec.ts +21 -0
  20. package/src/lib/app-dropdown-menu/app-dropdown-menu.component.ts +43 -0
  21. package/src/lib/app-dropdown-menu/app-dropdown-menu.ts +17 -0
  22. package/src/lib/app-dropdown-menu/index.ts +2 -0
  23. package/src/lib/app-dropdown-menu/menu-popup.pipe.ts +18 -0
  24. package/src/lib/app-tabs/app-tab.interface.ts +26 -0
  25. package/src/lib/app-tabs/app-tabs.component.html +35 -0
  26. package/src/lib/app-tabs/app-tabs.component.scss +103 -0
  27. package/src/lib/app-tabs/app-tabs.component.spec.ts +21 -0
  28. package/src/lib/app-tabs/app-tabs.component.ts +48 -0
  29. package/src/lib/app-tabs/index.ts +2 -0
  30. package/src/lib/confirmation-dialog/confirmation-dialog.component.html +54 -0
  31. package/src/lib/confirmation-dialog/confirmation-dialog.component.scss +0 -0
  32. package/src/lib/confirmation-dialog/confirmation-dialog.component.spec.ts +22 -0
  33. package/src/lib/confirmation-dialog/confirmation-dialog.component.ts +51 -0
  34. package/src/lib/dynamic-form/dynamic-form.component.html +39 -0
  35. package/src/lib/dynamic-form/dynamic-form.component.scss +0 -0
  36. package/src/lib/dynamic-form/dynamic-form.component.spec.ts +21 -0
  37. package/src/lib/dynamic-form/dynamic-form.component.ts +29 -0
  38. package/src/lib/dynamic-form/dynamic-form.interface.ts +56 -0
  39. package/src/lib/form-components/@utils/form-utils.ts +12 -0
  40. package/src/lib/form-components/@utils/validations/error-keys.enum.ts +24 -0
  41. package/src/lib/form-components/@utils/validations/form-validation.service.ts +53 -0
  42. package/src/lib/form-components/@utils/validations/index.ts +3 -0
  43. package/src/lib/form-components/@utils/validations/validation-message.pipe.ts +24 -0
  44. package/src/lib/form-components/components/auto-complete/auto-complete.component.html +48 -0
  45. package/src/lib/form-components/components/auto-complete/auto-complete.component.scss +0 -0
  46. package/src/lib/form-components/components/auto-complete/auto-complete.component.spec.ts +21 -0
  47. package/src/lib/form-components/components/auto-complete/auto-complete.component.ts +48 -0
  48. package/src/lib/form-components/components/base-input.component.ts +41 -0
  49. package/src/lib/form-components/components/date-picker/date-picker.component.html +53 -0
  50. package/src/lib/form-components/components/date-picker/date-picker.component.scss +4 -0
  51. package/src/lib/form-components/components/date-picker/date-picker.component.spec.ts +23 -0
  52. package/src/lib/form-components/components/date-picker/date-picker.component.ts +45 -0
  53. package/src/lib/form-components/components/input/input.component.html +59 -0
  54. package/src/lib/form-components/components/input/input.component.scss +3 -0
  55. package/src/lib/form-components/components/input/input.component.spec.ts +21 -0
  56. package/src/lib/form-components/components/input/input.component.ts +32 -0
  57. package/src/lib/form-components/components/select/select.component.html +90 -0
  58. package/src/lib/form-components/components/select/select.component.scss +0 -0
  59. package/src/lib/form-components/components/select/select.component.spec.ts +21 -0
  60. package/src/lib/form-components/components/select/select.component.ts +51 -0
  61. package/src/lib/form-components/components/select-button/select-button.component.html +21 -0
  62. package/src/lib/form-components/components/select-button/select-button.component.scss +0 -0
  63. package/src/lib/form-components/components/select-button/select-button.component.spec.ts +21 -0
  64. package/src/lib/form-components/components/select-button/select-button.component.ts +22 -0
  65. package/src/lib/form-components/components/switcher/switch.component.html +5 -0
  66. package/src/lib/form-components/components/switcher/switch.component.scss +0 -0
  67. package/src/lib/form-components/components/switcher/switch.component.spec.ts +21 -0
  68. package/src/lib/form-components/components/switcher/switch.component.ts +25 -0
  69. package/src/lib/form-components/index.ts +9 -0
  70. package/src/lib/form-components/interfaces/index.ts +1 -0
  71. package/src/lib/form-components/interfaces/label-value.ts +4 -0
  72. package/src/lib/ico-moon-icon/ico-moon-icon.component.ts +23 -0
  73. package/src/lib/read-more/read-more.component.html +17 -0
  74. package/src/lib/read-more/read-more.component.scss +0 -0
  75. package/src/lib/read-more/read-more.component.spec.ts +21 -0
  76. package/src/lib/read-more/read-more.component.ts +21 -0
  77. package/src/lib/side-bar/side-bar.component.html +25 -0
  78. package/src/lib/side-bar/side-bar.component.scss +5 -0
  79. package/src/lib/side-bar/side-bar.component.spec.ts +21 -0
  80. package/src/lib/side-bar/side-bar.component.ts +32 -0
  81. package/src/lib/side-bar-dynamic/data-injector.pipe.ts +15 -0
  82. package/src/lib/side-bar-dynamic/dynamic-sidebar.service.ts +116 -0
  83. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.html +51 -0
  84. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.scss +5 -0
  85. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.spec.ts +21 -0
  86. package/src/lib/side-bar-dynamic/side-bar-dynamic.component.ts +37 -0
  87. package/src/lib/side-bar-dynamic/side-bar-utils.ts +30 -0
  88. package/src/lib/side-bar-dynamic/sidebar-config.ts +48 -0
  89. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.html +20 -0
  90. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.scss +0 -0
  91. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.spec.ts +21 -0
  92. package/src/lib/user-autocomplete-card/user-autocomplete-card.component.ts +21 -0
  93. package/src/lib/user-info/user-info.component.html +10 -0
  94. package/src/lib/user-info/user-info.component.ts +11 -0
  95. package/tsconfig.json +25 -0
  96. package/tsconfig.lib.json +12 -0
  97. package/tsconfig.lib.prod.json +9 -0
@@ -0,0 +1,54 @@
1
+ @if (dynamicDialogConfig.data) {
2
+ <div>
3
+ <div class="bg-gray-150 p-4 flex items-center">
4
+ <div class="flex-grow flex gap-2">
5
+ @if (dynamicDialogConfig.data.headerIcon) {
6
+ <i [class]="dynamicDialogConfig.data.headerIcon"></i>
7
+ }
8
+
9
+ <h3 class="m-0 font-bold text-[16px]">
10
+ {{ dynamicDialogConfig.data.header }}
11
+ </h3>
12
+ </div>
13
+
14
+ <app-button (click)="close()" icon="font-icon-close" [style]="'secondary'" variant="text"/>
15
+ </div>
16
+
17
+ @if (dynamicDialogConfig.data) {
18
+ <div class="p-4">
19
+ @if (dynamicDialogConfig.data.dialogIcon) {
20
+ <em [class]="dynamicDialogConfig.data.dialogIcon"></em>
21
+ }
22
+ <div>
23
+ {{ dynamicDialogConfig.data.message }}
24
+ </div>
25
+ @if (dynamicDialogConfig.data.hint) {
26
+ <div class="font-bold text-[14px] mt-2">
27
+ {{ dynamicDialogConfig.data.hint }}
28
+ </div>
29
+ }
30
+ </div>
31
+ }
32
+
33
+ @if (dynamicDialogConfig.data.inputForm) {
34
+ <app-dynamic-form [dynamicFormData]="dialogFormData"></app-dynamic-form>
35
+ }
36
+
37
+ <div class="p-4 flex gap-3">
38
+ <app-button
39
+ [title]="dynamicDialogConfig.data?.confirmLabel || ('actions.confirm' | translate)" class="w-[50%]"
40
+ [disabled]="!!(dialogFormData && dialogFormData.formGroup?.invalid)"
41
+ [icon]="dynamicDialogConfig.data.confirmBtnIcon || ''"
42
+ [iconPos]="dynamicDialogConfig.data.confirmBtnPosition || 'left'"
43
+ [style]="dynamicDialogConfig.data.confirmBtnStyle ||'primary'"
44
+ (click)="submit()"/>
45
+
46
+ <app-button
47
+ [title]="dynamicDialogConfig.data?.closeLabel || ('actions.cancel' | translate)"
48
+ class="w-[50%]"
49
+ [style]="'secondary'"
50
+ [variant]="'outlined'"
51
+ (click)="close()"/>
52
+ </div>
53
+ </div>
54
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+
3
+ import { ConfirmationDialogComponent } from "./confirmation-dialog.component";
4
+
5
+ describe("SharedConfirmDialogComponent", () => {
6
+ let component: ConfirmationDialogComponent;
7
+ let fixture: ComponentFixture<ConfirmationDialogComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [ConfirmationDialogComponent]
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(ConfirmationDialogComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it("should create", () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,51 @@
1
+ import { Component, OnDestroy, OnInit, inject } from "@angular/core";
2
+ import { NavigationStart, Router } from "@angular/router";
3
+ import { AvatarModule } from "primeng/avatar";
4
+ import { filter, Subscription } from "rxjs";
5
+ import { AppButtonComponent } from "../app-button/app-button.component";
6
+ import { DialogService, DynamicDialog, DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog";
7
+ import { DynamicFormComponent } from "../dynamic-form/dynamic-form.component";
8
+ import { DynamicFormData } from "../dynamic-form/dynamic-form.interface";
9
+ import {TranslatePipe} from "@ngx-translate/core";
10
+
11
+ @Component({
12
+ selector: "app-confirm-dialog",
13
+ templateUrl: "./confirmation-dialog.component.html",
14
+ styleUrls: ["./confirmation-dialog.component.scss"],
15
+ standalone: true,
16
+ imports: [AppButtonComponent, AvatarModule, DynamicDialog, DynamicFormComponent, TranslatePipe],
17
+ providers: [DialogService]
18
+ })
19
+ export class ConfirmationDialogComponent implements OnInit, OnDestroy {
20
+ router = inject(Router);
21
+ dialogService = inject(DialogService);
22
+ dynamicDialogConfig = inject(DynamicDialogConfig);
23
+ private readonly _ref = inject(DynamicDialogRef);
24
+ private readonly _subscription = new Subscription();
25
+ dialogFormData: DynamicFormData;
26
+
27
+ ngOnInit() {
28
+ // closing when navigating back from the browser
29
+ this._subscription.add(
30
+ this.router.events.pipe(filter((event) => event instanceof NavigationStart)).subscribe(() => {
31
+ if (this.dynamicDialogConfig) {
32
+ this._ref.close();
33
+ }
34
+ })
35
+ );
36
+ this.dialogFormData = this.dynamicDialogConfig.data?.inputForm;
37
+ }
38
+
39
+ ngOnDestroy(): void {
40
+ this._subscription.unsubscribe();
41
+ }
42
+
43
+ submit() {
44
+ const submitData = { submitted: true, data: this.dialogFormData?.formGroup?.value };
45
+ this._ref.close(this.dynamicDialogConfig.data.inputForm ? submitData : true);
46
+ }
47
+
48
+ close() {
49
+ this._ref.close();
50
+ }
51
+ }
@@ -0,0 +1,39 @@
1
+ <div class="border-t mx-4 py-2">
2
+ <form [formGroup]="formGroup">
3
+ <div class="grid grid-cols-12 gap-x-2">
4
+ @for (inputName of inputsNames; track $index) {
5
+ <div
6
+ [ngClass]="inputsMap[inputName].rowSize === 'half' ? 'col-span-6 md:col-span-6': 'col-span-12 md:col-span-12'">
7
+ @switch (inputsMap[inputName].fieldType) {
8
+ @case (fieldType.DATE_PICKER) {
9
+ <stc-date-picker
10
+ [minDate]="inputsMap[inputName]?.dateRange?.min"
11
+ [id]="inputsMap[inputName].inputId"
12
+ [control]="getFormControl(inputName, formGroup)"
13
+ [name]="inputName"
14
+ [label]="inputsMap[inputName].label"
15
+ [showIcon]="inputsMap[inputName].showIcon || true"
16
+ [isTimeOnly]="inputsMap[inputName].isTimeOnly || false"/>
17
+ }
18
+ @case (fieldType.SELECT_BUTTON) {
19
+ <stc-select-button
20
+ [control]="getFormControl(inputName, formGroup)"
21
+ [id]="inputsMap[inputName].inputId"
22
+ [name]="inputName"
23
+ [label]="inputsMap[inputName].label"
24
+ [options]="inputsMap[inputName].selectButtonOptions || []"/>
25
+ }
26
+ }
27
+ </div>
28
+ }
29
+ </div>
30
+ <div class="col-span-12">
31
+ <small class="p-error text-red-700">
32
+ @for (error of formGroup.errors | validationErrors: dynamicFormData.formValidationErrorsKeys;
33
+ track error) {
34
+ {{ error }}
35
+ }
36
+ </small>
37
+ </div>
38
+ </form>
39
+ </div>
@@ -0,0 +1,21 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+ import { DynamicFormComponent } from "./dynamic-form.component";
3
+
4
+ describe("DynamicFormComponent", () => {
5
+ let component: DynamicFormComponent;
6
+ let fixture: ComponentFixture<DynamicFormComponent>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [DynamicFormComponent]
11
+ }).compileComponents();
12
+
13
+ fixture = TestBed.createComponent(DynamicFormComponent);
14
+ component = fixture.componentInstance;
15
+ fixture.detectChanges();
16
+ });
17
+
18
+ it("should create", () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,29 @@
1
+ import { Component, Input, OnInit } from "@angular/core";
2
+ import { CommonModule } from "@angular/common";
3
+ import { FormGroup, ReactiveFormsModule } from "@angular/forms";
4
+ import { DynamicFormData, FormFieldTypeEnum, InputsMap } from "./dynamic-form.interface";
5
+ import {DatePickerComponent, FormUtils, SelectButtonComponent, ValidationErrorsPipe} from "../form-components";
6
+ import { TranslateModule } from "@ngx-translate/core";
7
+
8
+ @Component({
9
+ selector: "app-dynamic-form",
10
+ standalone: true,
11
+ imports: [CommonModule, ReactiveFormsModule, DatePickerComponent, ValidationErrorsPipe, TranslateModule, SelectButtonComponent],
12
+ templateUrl: "./dynamic-form.component.html",
13
+ styleUrl: "./dynamic-form.component.scss"
14
+ })
15
+ export class DynamicFormComponent implements OnInit {
16
+ @Input({ required: true }) dynamicFormData: DynamicFormData;
17
+ inputsNames: string[] = [];
18
+ formGroup: FormGroup;
19
+ inputsMap: InputsMap;
20
+ readonly fieldType = FormFieldTypeEnum;
21
+ getFormControl = FormUtils.getFormControl;
22
+
23
+ ngOnInit(): void {
24
+ this.formGroup = this.dynamicFormData?.formGroup as FormGroup;
25
+ this.inputsMap = this.dynamicFormData?.inputsMap as InputsMap;
26
+ this.inputsNames = Object.keys(this.inputsMap || {});
27
+ }
28
+
29
+ }
@@ -0,0 +1,56 @@
1
+ import {FormGroup} from "@angular/forms";
2
+ import {LabelValue} from "../form-components";
3
+
4
+ export type InputType = "text" | "textarea";
5
+ export type InputContentType = "text" | "email" | "password" | "number";
6
+
7
+ export interface Dropdown<T = unknown> {
8
+ id?: string;
9
+ keyValue: T;
10
+ label: string;
11
+ hidden?: boolean;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ export interface InputsMapData {
16
+ // General props
17
+ label: string;
18
+ rowSize?: "half" | "full";
19
+ fieldType: FormFieldTypeEnum;
20
+ inputId?: string;
21
+
22
+ // Date
23
+ dateRange?: DateRangeInterface;
24
+ isTimeOnly?: boolean;
25
+ showIcon?: boolean;
26
+
27
+ //select button
28
+ selectButtonOptions?: LabelValue<any>[];
29
+ }
30
+
31
+ export interface InputsMap {
32
+ [key: string]: InputsMapData;
33
+ }
34
+
35
+ export interface DynamicFormData {
36
+ isActive?: boolean;
37
+ formGroup: FormGroup | null;
38
+ inputsMap: InputsMap | null;
39
+ title?: string;
40
+ isReadOnlyForm?: boolean;
41
+ formValidationErrorsKeys?: string[];
42
+ }
43
+
44
+ export interface DateRangeInterface {
45
+ min?: Date | null; // Static range, hard coded
46
+ max?: Date | null; // Static range, hard coded
47
+ notBeforeDateInput?: string; // For dynamic date range validation
48
+ notAfterDateInput?: string; // For dynamic date range validation
49
+ notBeforeOrSameDateInput?: string;
50
+ notAfterOrSameDateInput?: string;
51
+ }
52
+
53
+ export enum FormFieldTypeEnum {
54
+ DATE_PICKER = "date-picker",
55
+ SELECT_BUTTON = "select-button"
56
+ }
@@ -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,24 @@
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
+ }
16
+ export enum ErrorsWithValuesKeysEnum {
17
+ minlength = "MIN_LENGTH",
18
+ maxlength = "MAX_LENGTH",
19
+ min = "MIN",
20
+ max = "MAX",
21
+ maxSize = "MAX_SIZE",
22
+ maxFiles = "MAX_FILES",
23
+ allowedTypes = "ALLOWED_TYPES"
24
+ }
@@ -0,0 +1,53 @@
1
+ import { inject, Injectable } from "@angular/core";
2
+ import { TranslateService } from "@ngx-translate/core";
3
+ import { InterpolationParameters } from "@ngx-translate/core/lib/translate.service";
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(errorKey as keyof typeof ErrorsWithValuesKeysEnum, errorValue);
23
+ }
24
+
25
+ return this.getTranslation(BasicErrorKeysEnum.default);
26
+ }
27
+
28
+ // Basic error keys are the keys that don't have any values to interpolate. like required, email, etc.
29
+ private isBasicErrorKey(key: string): key is keyof typeof BasicErrorKeysEnum {
30
+ return Object.keys(BasicErrorKeysEnum).includes(key as BasicErrorKeysEnum);
31
+ }
32
+
33
+ // Error keys with values are the keys that have values to interpolate. like minlength, maxlength, etc.
34
+ private isErrorWithValueKey(key: string): key is keyof typeof ErrorsWithValuesKeysEnum {
35
+ return Object.keys(ErrorsWithValuesKeysEnum).includes(key as ErrorsWithValuesKeysEnum);
36
+ }
37
+
38
+ private getErrorWithValueMessage(errorKey: keyof typeof ErrorsWithValuesKeysEnum, errorValue: any): string {
39
+ const messages: Record<keyof typeof ErrorsWithValuesKeysEnum, (value: any) => string> = {
40
+ minlength: (val) =>
41
+ this.getTranslation(ErrorsWithValuesKeysEnum.minlength, { requiredLength: val?.requiredLength, actualLength: val?.actualLength }),
42
+ maxlength: (val) =>
43
+ this.getTranslation(ErrorsWithValuesKeysEnum.maxlength, { requiredLength: val?.requiredLength, actualLength: val?.actualLength }),
44
+ min: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.min, { min: val?.min }),
45
+ max: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.max, { max: val?.max }),
46
+ maxSize: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.maxSize, { size: val?.requiredLength }),
47
+ maxFiles: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.maxFiles, { size: val?.requiredLength }),
48
+ allowedTypes: (val) => this.getTranslation(ErrorsWithValuesKeysEnum.allowedTypes, { types: val?.join(", ") })
49
+ };
50
+
51
+ return messages[errorKey](errorValue);
52
+ }
53
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./error-keys.enum";
2
+ export * from "./validation-message.pipe";
3
+ export * from "./form-validation.service";
@@ -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,48 @@
1
+ <div class="field flex flex-col gap-2 my-3 relative">
2
+ @if (label) {
3
+ <label [for]="inputId">
4
+ {{ label }}
5
+ @if (required) {
6
+ <span [class.text-red-700]="isInvalid">*</span>
7
+ }
8
+ </label>
9
+ }
10
+ @if (!required) {
11
+ <span class="absolute top-[6px] left-0 text-[10px] text-gray-400">{{'forms.config.optional' | translate}}</span>
12
+ }
13
+ <p-auto-complete
14
+ (completeMethod)="search($event)"
15
+ (onSelect)="onSelect($event)"
16
+ [delay]="delay"
17
+ [disabled]="disabled"
18
+ [formControl]="control"
19
+ [id]="inputId"
20
+ [inputStyleClass]="'reset-default-styles w-full' + (basicInput ? ' basic-style': ' ')"
21
+ [minLength]="minLengthToSearch"
22
+ [name]="name"
23
+ [ngClass]="{ 'p-invalid ng-dirty ng-invalid': isInvalid}"
24
+ [placeholder]="placeholder"
25
+ [styleClass]="'w-full'"
26
+ [suggestions]="items">
27
+ <ng-template let-item pTemplate="item">
28
+ @if (selectedItemTemplate) {
29
+ <ng-container
30
+ [ngTemplateOutletContext]="{ $implicit: item }"
31
+ [ngTemplateOutlet]="selectedItemTemplate"
32
+ />
33
+ }
34
+ </ng-template>
35
+ </p-auto-complete>
36
+
37
+ @if (hint) {
38
+ <small class="p-mt-1">{{ hint }}</small>
39
+ }
40
+ @if (isInvalid && (control.dirty || control.touched)) {
41
+ <small class="p-error text-red-700">
42
+ @for (error of control.errors | validationErrors; track error) {
43
+ {{ error }}<br>
44
+ }
45
+ </small>
46
+ }
47
+
48
+ </div>
@@ -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,48 @@
1
+ import { JsonPipe, NgClass, NgIf, NgTemplateOutlet } from "@angular/common";
2
+ import { Component, EventEmitter, Input, Output, TemplateRef } from "@angular/core";
3
+ import { ReactiveFormsModule } from "@angular/forms";
4
+ import { ValidationErrorsPipe } from "../../@utils/validations/validation-message.pipe";
5
+ import { PrimeTemplate } from "primeng/api";
6
+ import { AutoComplete, AutoCompleteCompleteEvent, AutoCompleteSelectEvent } from "primeng/autocomplete";
7
+ import { BaseInputComponent } from "../base-input.component";
8
+ import {TranslatePipe} from "@ngx-translate/core";
9
+
10
+ @Component({
11
+ selector: "stc-auto-complete",
12
+ standalone: true,
13
+ imports: [
14
+ ReactiveFormsModule,
15
+ AutoComplete,
16
+ PrimeTemplate,
17
+ NgIf,
18
+ NgTemplateOutlet,
19
+ NgClass,
20
+ JsonPipe,
21
+ ValidationErrorsPipe,
22
+ TranslatePipe
23
+ ],
24
+ templateUrl: "./auto-complete.component.html",
25
+ styleUrl: "./auto-complete.component.scss"
26
+ })
27
+ export class AutoCompleteComponent extends BaseInputComponent {
28
+ @Input() selectedItemTemplate: TemplateRef<unknown> | null = null;
29
+ // eslint-disable-next-line @angular-eslint/no-output-on-prefix
30
+ @Output() onSearch: EventEmitter<string> = new EventEmitter<string>();
31
+ @Output() selectOption: EventEmitter<AutoCompleteSelectEvent> = new EventEmitter<AutoCompleteSelectEvent>();
32
+ @Input() items: any[] = [];
33
+ @Input() minLengthToSearch = 3;
34
+ @Input() delay = 300; // default value
35
+ @Input() basicInput!: boolean;
36
+
37
+ constructor() {
38
+ super();
39
+ }
40
+
41
+ search(event: AutoCompleteCompleteEvent) {
42
+ this.onSearch.emit(event.query);
43
+ }
44
+
45
+ onSelect(event: AutoCompleteSelectEvent) {
46
+ this.selectOption.emit(event);
47
+ }
48
+ }
@@ -0,0 +1,41 @@
1
+ import { Component, Input, OnDestroy, OnInit } from "@angular/core";
2
+ import { FormControl, Validators } from "@angular/forms";
3
+ import { Subject, takeUntil } from "rxjs";
4
+
5
+ @Component({
6
+ template: ""
7
+ })
8
+ export abstract class BaseInputComponent implements OnInit, OnDestroy {
9
+ @Input({ required: true }) control!: FormControl;
10
+ @Input() name: string = "";
11
+ @Input() label?: string;
12
+ @Input() placeholder: string = "";
13
+ @Input() inputId!: string;
14
+ @Input() readonly: boolean = false;
15
+ @Input() disabled: boolean = false;
16
+ @Input() hint?: string;
17
+
18
+ protected destroy$ = new Subject<void>();
19
+
20
+ get required(): boolean {
21
+ return this.control.hasValidator(Validators.required);
22
+ }
23
+
24
+ get isInvalid(): boolean {
25
+ return this.control.invalid && this.control.touched;
26
+ }
27
+
28
+ ngOnInit() {
29
+ this.inputId = `input-${this.name + "-" + Math.random().toString(36).substring(7)}`;
30
+ this.control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((v) => {
31
+ if (v) {
32
+ this.control.markAsTouched();
33
+ }
34
+ });
35
+ }
36
+
37
+ ngOnDestroy(): void {
38
+ this.destroy$.next();
39
+ this.destroy$.complete();
40
+ }
41
+ }
@@ -0,0 +1,53 @@
1
+ <div class="field flex flex-col gap-2 my-3 relative" >
2
+ @if (label) {
3
+ <label [for]="inputId">
4
+ {{ label }}
5
+ @if (required) {
6
+ <span [class.text-red-700]="isInvalid">*</span>
7
+ }
8
+ </label>
9
+ }
10
+ @if (!required) {
11
+ <span class="absolute top-[6px] left-0 text-[10px] text-gray-400">{{'forms.config.optional' | translate}}</span>
12
+ }
13
+ <p-datepicker
14
+ [selectionMode]="selectionMode"
15
+ [disabled]="disabled"
16
+ [formControl]="control"
17
+ [iconDisplay]="'input'"
18
+ [showClear]="showClear"
19
+ (onClear)="afterClearDate()"
20
+ [id]="inputId"
21
+ [inputStyleClass]="'reset-default-styles ' + (basicInput ? 'basic-style' : '')"
22
+ [ngClass]="{ 'p-invalid ng-dirty ng-invalid': isInvalid }"
23
+ [placeholder]="placeholder"
24
+ [showIcon]="showIcon" [styleClass]="'w-full'"
25
+ appendTo="body"
26
+ [timeOnly]="isTimeOnly"
27
+ [hourFormat]="hourFormat"
28
+ [minDate]="minDate"
29
+ [maxDate]="maxDate">
30
+ @if (isTimeOnly) {
31
+ <ng-template #inputicon let-clickCallBack="clickCallBack">
32
+ <i class="text-[18px] font-icon-time-clock" (click)="clickCallBack($event)"></i>
33
+ </ng-template>
34
+ <ng-template pTemplate="footer">
35
+ <div class="p-datepicker-buttonbar">
36
+ <button pButton type="button" class="p-button-text" (click)="selectCurrentTime($event)">الان
37
+ </button>
38
+ <button pButton type="button" class="p-button-text" (click)="clearButtonClick($event)"> الغاء</button>
39
+ </div>
40
+ </ng-template>
41
+ }
42
+ </p-datepicker>
43
+ @if (hint) {
44
+ <small class="p-mt-1">{{ hint }}</small>
45
+ }
46
+ @if (isInvalid && (control.dirty || control.touched)) {
47
+ <small class="p-error text-red-700">
48
+ @for (error of control.errors | validationErrors; track error) {
49
+ {{ error }}<br>
50
+ }
51
+ </small>
52
+ }
53
+ </div>
@@ -0,0 +1,4 @@
1
+ .p-datepicker-clear-icon {
2
+ @apply cursor-pointer absolute top-[50%] left-[13px] -translate-y-1/2 text-[var(--p-datepicker-input-icon-color)] ml-[calc(var(--p-icon-size))] leading-none;
3
+ }
4
+
@@ -0,0 +1,23 @@
1
+ import {ComponentFixture, TestBed} from '@angular/core/testing';
2
+ import {
3
+ DatePickerComponent
4
+ } from 'nx-BOD-workspace/libs/ui-components/src/lib/form-components/components/date-picker/date-picker.component';
5
+
6
+ describe('DatePickerComponent', () => {
7
+ let component: DatePickerComponent;
8
+ let fixture: ComponentFixture<DatePickerComponent>;
9
+
10
+ beforeEach(async () => {
11
+ await TestBed.configureTestingModule({
12
+ imports: [DatePickerComponent],
13
+ }).compileComponents();
14
+
15
+ fixture = TestBed.createComponent(DatePickerComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });