import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Validators } from '@angular/forms'; import { ArrayHelpersService, FilterModalTypes, FilterOption, FilterOptions, FilterTypes, OneOfFilterOption, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup, TypeToken } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { isEqual } from 'lodash'; import { Subscription } from 'rxjs'; import { LogicBuilderService } from '../logic-builder.service'; import { ConditionalLogicResultType, LogicColumn, LogicColumnDisplay, LogicCondition, RelatedLogicValueCondition, ValueLogicCondition } from '../logic-builder.typing'; @Component({ selector: 'gc-conditional-logic-builder-condition', templateUrl: './conditional-logic-builder-condition.component.html', styleUrls: ['./conditional-logic-builder-condition.component.scss'] }) export class ConditionalLogicBuilderConditionComponent implements OnInit, OnDestroy { @Input() condition: LogicCondition>; @Input() availableColumns: LogicColumnDisplay[]; @Output() conditionChange = new EventEmitter>>(); @Output() validChange = new EventEmitter(); @Output() removeCondition = new EventEmitter(); formGroup: TypeSafeFormGroup< RelatedLogicValueCondition, LogicColumn>&{ resultType: ConditionalLogicResultType; } >; formSub: Subscription; columnOptions: TypeaheadSelectOption>[]; comparisonOptions: TypeaheadSelectOption[]; sourceColumnDisplay: LogicColumnDisplay; filterOptions: TypeaheadSelectOption[]; filter: FilterOption; resultTypeOptions = this.logicBuilderService.getResultTypeOptions(); $filterOptionsType = new TypeToken(); ConditionalLogicResultType = ConditionalLogicResultType; otherComponentOptions: TypeaheadSelectOption[] = []; constructor ( private formBuilder: TypeSafeFormBuilder, private i18n: I18nService, private arrayHelper: ArrayHelpersService, private logicBuilderService: LogicBuilderService ) { } get apiComparison () { return this.formGroup?.value.comparison; } get sourceColumn () { return this.formGroup?.value.sourceColumn; } get value () { return this.formGroup?.value.value; } get relatedColumn () { return this.formGroup?.value.relatedColumn; } get resultType () { return this.formGroup?.value.resultType; } ngOnInit () { this.setSourceColumnDisplayAndOptions(this.condition.sourceColumn); this.setFormGroup(); this.setFilterOption(true); this.setColumnOptions(); } getInitialValue (resultType: ConditionalLogicResultType) { if (resultType === ConditionalLogicResultType.STATIC_VALUE) { let value = (this.condition as ValueLogicCondition>).value; const applicableOptions = this.getApplicableOptions(); if ( applicableOptions.includes(OneOfFilterOption) && this.condition.comparison === FilterModalTypes.equals && !(value instanceof Array) ) { value = [value] as any; } return value; } return null; } private setFormGroup () { // TODO: figure out what this should do if the source column is removed let relatedColumn: LogicColumn = null; let resultType = ConditionalLogicResultType.STATIC_VALUE; if ('relatedColumn' in this.condition) { if (!!this.condition.relatedColumn) { relatedColumn = this.condition.relatedColumn; resultType = ConditionalLogicResultType.OTHER_COLUMN; } } this.formGroup = this.formBuilder.group({ sourceColumn: [this.condition.sourceColumn, Validators.required], comparison: [this.condition.comparison, Validators.required], value: [this.getInitialValue(resultType)], useAnd: [this.condition.useAnd], resultType, relatedColumn: [relatedColumn], identifier: this.condition.identifier } as any); this.validChange.emit(this.formGroup.valid); this.formSub = this.formGroup.valueChanges.subscribe(() => { this.validChange.emit(this.formGroup.valid); const formVal = this.formGroup.value; let value = formVal.value; let _relatedColumn: LogicColumn = null; if (formVal.resultType === ConditionalLogicResultType.OTHER_COLUMN) { value = null; _relatedColumn = this.formGroup.value.relatedColumn; } this.conditionChange.emit({ sourceColumn: formVal.sourceColumn, comparison: formVal.comparison, useAnd: formVal.useAnd, identifier: formVal.identifier, value, relatedColumn: _relatedColumn }); }); } setFilterOption (isInit: boolean) { if (this.sourceColumn && this.apiComparison) { const applicableOptions = this.getApplicableOptions(); this.filter = applicableOptions.find((option) => { return option.api === this.apiComparison; }); const shouldShowOtherColumnSelect = this.logicBuilderService.shouldShowOtherColumnSelector( this.otherComponentOptions, this.apiComparison ); if ( !shouldShowOtherColumnSelect && this.resultType === ConditionalLogicResultType.OTHER_COLUMN ) { this.formGroup.get('resultType').setValue(ConditionalLogicResultType.STATIC_VALUE); } } else { this.filter = null; } /* If the filter changes and this is not init, clear value */ if (!isInit) { this.clearValue(); } } onColumnChange () { this.setSourceColumnDisplayAndOptions(this.formGroup.value.sourceColumn); this.setComparisonOptions(); /* Anytime the column changes, we should clear filter and value */ this.clearFilter(); this.clearValue(); } handleResultTypeChange (resultType: ConditionalLogicResultType) { if (resultType === ConditionalLogicResultType.OTHER_COLUMN) { this.formGroup.get('relatedColumn').setValue(this.otherComponentOptions[0].value); } else if (resultType === ConditionalLogicResultType.STATIC_VALUE) { this.clearValue(); } } handleDeleteClick () { this.removeCondition.emit(); } private clearFilter () { this.formGroup.get('comparison').setValue(null); } private clearValue () { this.formGroup.get('value').setValue(null); } private setSourceColumnDisplayAndOptions (sourceColumn: LogicColumn) { this.sourceColumnDisplay = this.availableColumns.find(column => { return isEqual(column.column, sourceColumn); }); if (this.sourceColumnDisplay) { if ('filterOptions' in this.sourceColumnDisplay) { this.filterOptions = this.sourceColumnDisplay.filterOptions; } const foundCurrentCol = this.availableColumns.find((col) => { return isEqual(col.column, sourceColumn); }); this.otherComponentOptions = foundCurrentCol?.otherColumnOptions ?? []; } else { this.otherComponentOptions = []; } } private setColumnOptions () { this.columnOptions = this.arrayHelper.sort( this.availableColumns.map((availableColumn) => { return { value: availableColumn.column, label: availableColumn.label }; }), 'label'); this.setComparisonOptions(); } private setComparisonOptions () { const applicableOptions = this.getApplicableOptions() .map((type) => { return { value: type.api, label: this.i18n.translate(type.displayKey, {}, type.display) }; }); this.comparisonOptions = this.arrayHelper.sort( applicableOptions, 'label' ); } private getApplicableOptions () { return FilterOptions.filter(type => { return type.types.includes(this.sourceColumnDisplay?.type); }); } ngOnDestroy () { this.formSub.unsubscribe(); } }