import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { BaseApplication } from '@core/typings/application.typing'; import { ReferenceFieldsUI } from '@core/typings/ui/reference-fields.typing'; import { FormDefinitionComponent } from '@features/configure-forms/form.typing'; import { CustomDataTablesService } from '@features/custom-data-tables/custom-data-table.service'; import { KeyValue } from '@features/custom-data-tables/custom-data-tables.typing'; import { ReferenceFieldsService } from '@features/reference-fields/services/reference-fields.service'; import { UserService } from '@features/users/user.service'; import { TypeaheadSelectOption } from '@yourcause/common'; import { isEqual } from 'lodash'; import { delay, Subscription } from 'rxjs'; @Component({ selector: 'gc-custom-data-table', templateUrl: './gc-custom-data-table.component.html', styleUrls: ['./gc-custom-data-table.component.scss'] }) export class GcCustomDataTableComponent implements OnInit, OnDestroy { @Input() data: string|string[]; @Input() label: string; @Input() placeholder: string; @Input() defaultVal: string; @Input() description: string; @Input() tabIndex: number; @Input() tooltipText: string; @Input() showQuestionMarkForTooltip = false; @Input() selectedCustomDataTable: string; @Input() parentReferenceFieldId: number; @Input() inputType: ReferenceFieldsUI.ReferenceFieldTypes; @Input() inline = false; @Input() supportsMultiple = false; @Input() disabled: boolean; @Input() requiredOverride: boolean; @Input() compKey: string; @Input() hideWithoutParentVal = false; @Input() hideLabel = false; @Input() isFormBuilderView: boolean; @Input() parentFormGroup: FormGroup; @Input() parentFields: Partial; @Input() comp: FormDefinitionComponent; @Output() onDataChanged = new EventEmitter(); options: TypeaheadSelectOption[] = []; InputTypes = ReferenceFieldsUI.ReferenceFieldTypes; sub = new Subscription(); filteredOptions: KeyValue[]; hideField = false; ready = false; constructor ( private customDataTablesService: CustomDataTablesService, private userService: UserService, private referenceFieldsService: ReferenceFieldsService ) { this.sub.add( this.referenceFieldsService.changesTo$('parentPicklistValueMap') .pipe(delay(1000)) .subscribe(async () => { if (this.parentReferenceFieldId && !!this.parentFields) { // if this custom data table has a parent reference field id, get the value from the state if ( this.parentReferenceFieldId in this.referenceFieldsService.parentPicklistValueMap ) { // if there is a value on the state for this CDT's parent ref field // set child picklist options await this.prepareOptionsForTranslate(); // only reset if options don't include current value const currentValueValid = this.checkCurrentValueValidity( this.options as TypeaheadSelectOption[], this.data ); if (!this.disabled && this.parentFormGroup && !currentValueValid) { const formValue = this.getValidFormValue(); if (!isEqual(this.control.value, formValue)) { this.control.setValue(formValue); if (!this.control.dirty) { this.dataChanged(formValue); } } } } this.hideField = this.getHideWithoutParent(); } }) ); } get customDataTableOptionsMap () { return this.customDataTablesService.customDataTableOptionsMap; } get isRequired () { if (this.comp) { return this.comp.validate.required; } return false; } get control () { return this.parentFormGroup?.get(this.compKey); } async ngOnInit () { if (!this.inputType) { this.inputType = ReferenceFieldsUI.ReferenceFieldTypes.CustomDataTable; } if (this.comp) { if (this.selectedCustomDataTable) { await this.prepareOptionsForTranslate(); } } this.ready = true; } checkCurrentValueValidity ( options: TypeaheadSelectOption[], data: string|string[] ) { const optionValues = options.map((opt) => opt.value); if (data instanceof Array) { return data.every((val) => optionValues.includes(val)); } else { return optionValues.includes(data); } } getValidFormValue () { if ( this.data instanceof Array && this.data.length > 0 ) { const optionValues = (this.options as TypeaheadSelectOption[]).map((opt) => { return opt.value; }); return this.data.filter((val) => { return optionValues.includes(val); }); } return this.getEmptyFormValue(); } getEmptyFormValue (): string|string[] { const onlyOneOption = this.options.length === 1; if (this.supportsMultiple) { return onlyOneOption ? [this.options[0].value] : []; } else { return onlyOneOption ? this.options[0].value : ''; } } dataChanged (value: any) { this.onDataChanged.emit(value); } getHideWithoutParent () { if (!this.isFormBuilderView) { const onAForm = !!this.parentFields; const parentVal = this.referenceFieldsService.parentPicklistValueMap[ this.parentReferenceFieldId ]; // checking for length in case array of values const parentHasValue = parentVal && (parentVal.length > 0); const hide = this.hideWithoutParentVal && onAForm && !parentHasValue; if (hide && this.comp.clearOnHide) { // if this component is set to clear on hide, we respect this setting (though this is a different kind of 'hide') this.clearControlValue(); } return hide; } return false; } clearControlValue () { this.control.setValue(this.getEmptyFormValue()); } async prepareOptionsForTranslate () { await this.customDataTablesService.setCustomDataTableOptionsFromGuid( this.selectedCustomDataTable, true, this.userService.getCurrentUserCulture() ); this.setOptions(); } setOptions () { const parentMapVal = this.referenceFieldsService.parentPicklistValueMap[ this.parentReferenceFieldId ]; const currentVal = this.parentFormGroup?.value[ this.compKey ]; this.options = this.customDataTablesService.getTypeaheadOptionsForCdt( this.selectedCustomDataTable, currentVal, this.supportsMultiple, parentMapVal ); } ngOnDestroy () { this.sub.unsubscribe(); } }