/* eslint-disable @typescript-eslint/ban-types */ import { FilterModalTypes, TypeaheadSelectOption } from '@yourcause/common'; import { BaseLogicState, BaseLogicStateRecord } from './logic-state.typing'; // TODO: upgrade to ts 4.1 (ng 11.1) and /leverage template literal types so that we don't have to manually type out 1, 2, and 3 layers of depth and just have a string e.g. 'prop1.prop2.prop3' versus ['prop1', 'prop2', 'prop3'] export type PropColumn = [K]; export type NestedPropColumn = [K1, K2]; export type NestedNestedPropColumn = [K1, K2, K3]; // we assume that the objects we are evaluating are only ever 3 layers deep // due to this assumption, we can type our columns as an array of property(K1), subProperty(K2), and subSubProperty(K3) // with the typing either being [K1] [K1, K2] or [K1, K2, K3] // ex. // we have an object: /* { prop1: { prop2: { prop3: 'value' } } } */ // to get to 'value', we would set a column as ['prop1', 'prop2', 'prop3'] export type LogicColumn = PropColumn|NestedPropColumn|NestedNestedPropColumn; export type LogicColumnValue> = C extends NestedNestedPropColumn ? T[K1][K2][K3] : C extends NestedPropColumn ? T[K1][K2] : T extends PropColumn ? T[K1] : unknown; type Comparison = T extends boolean ? FilterModalTypes.equals|FilterModalTypes.notEqual : FilterModalTypes; export interface BaseLogicCondition< T, K extends LogicColumn > { /* tuple(array) for property being evaluated [property(K1), subProperty(K2)?, subSubProperty(K3)?] */ sourceColumn: K; /* require this result to be true for the next result */ useAnd: boolean; comparison: FilterModalTypes; identifier: string; } export interface BaseValueLogicCondition< T, K extends LogicColumn > extends BaseLogicCondition { /* standard operator for two value conditions e.g. equals, greater than, etc. */ comparison: Exclude>, FilterModalTypes.isBlank|FilterModalTypes.notBlank|FilterModalTypes.contains|FilterModalTypes.multiValueIncludes>; } export interface ValueLogicCondition< T, K extends LogicColumn > extends BaseValueLogicCondition { /* value (must match type of column) */ value: LogicColumnValue; } export interface RelatedLogicCondition< T, K extends LogicColumn, R extends LogicColumn > extends BaseValueLogicCondition { /* tuple(array) for related property being evaluated [property(K1), subProperty(K2)?, subSubProperty(K3)?] (must match type of sourceColumn) */ relatedColumn: R; } export interface RelatedLogicValueCondition< T, K extends LogicColumn, R extends LogicColumn > extends RelatedLogicCondition { value: LogicColumnValue; } export interface OneOfLogicCondition> extends BaseLogicCondition { comparison: FilterModalTypes.multiValueIncludes; /* for 'one of' conditions, must be an array of values */ value: LogicColumnValue[]; } export interface ContainsLogicCondition> extends BaseLogicCondition { comparison: FilterModalTypes.contains; /* 'contains' only applies to string columns */ value: LogicColumnValue extends string ? string : unknown; } export interface NonValueLogicCondition> extends BaseLogicCondition { /* 'blank' and 'not blank' do not need a value */ comparison: FilterModalTypes.isBlank|FilterModalTypes.notBlank; } export type LogicCondition> = ValueLogicCondition| RelatedLogicCondition>| RelatedLogicValueCondition>| OneOfLogicCondition| NonValueLogicCondition| ContainsLogicCondition; export type LogicGroupType = LogicGroup|GlobalLogicGroup|GlobalValueLogicGroup; // TODO: support operations e.g. Add A and B export interface LogicGroup { /* array of groups or conditions to be evaluated */ conditions: (LogicCondition>|LogicGroup)[]; /* require this result to be true for the next result */ useAnd: boolean; identifier: string; } export enum EvaluationType { ConditionallyTrue, ConditionallyFalse, AlwaysTrue, AlwaysFalse } export enum ConditionalLogicResultType { STATIC_VALUE, OTHER_COLUMN, VALIDATION_MESSAGE, RELATIVE_DATE } export interface GlobalLogicGroup extends LogicGroup { // show versus hide // become valid versus become invalid evaluationType: EvaluationType; // TODO: not really applicable for non-boolean results, need to figure out more dynamic way of achieving this } export interface GlobalValueLogicGroup extends GlobalLogicGroup { result: V; // For set value logic, this is the value to set if the conditions pass resultType: ConditionalLogicResultType; resultConfig?: { operator: 'plus'|'minus'; constant: number; constantUnits: 'days'|'weeks'|'years'; }; } export interface BaseRunResult extends BaseLogicStateRecord { dependencies: T[]; } export type LogicRunResult = BaseRunResult, V>; export interface LogicResult { visible: boolean; value: V; } export interface LogicForColumn extends LogicRunResult { group: GlobalLogicGroup; column: LogicColumn; } export interface ListLogicForColumn extends LogicRunResult { groups: GlobalValueLogicGroup[]; column: LogicColumn; } export type LogicState = BaseLogicState>; export type ListLogicState = BaseLogicState>; export interface BaseLogicColumnDisplay { label: string; column: LogicColumn; otherColumnOptions: TypeaheadSelectOption>[]; } export interface StandardLogicColumnDisplay extends BaseLogicColumnDisplay { type: Exclude; } export interface SelectLogicColumnDisplay extends BaseLogicColumnDisplay { type: 'multiValueList'|'multi-list'|'multiListFuzzyText'|'list'|LogicFilterTypes; filterOptions: TypeaheadSelectOption[]; } export type LogicColumnDisplay = SelectLogicColumnDisplay|StandardLogicColumnDisplay; export interface LogicComparisonDisplay { label: string; comparison: FilterModalTypes; } export interface LogicInverseDisplay extends TypeaheadSelectOption { label: string; value: T; } export type LogicEvaluationTypeDisplayOptionsConditional = [ LogicInverseDisplay, LogicInverseDisplay, LogicInverseDisplay, LogicInverseDisplay ]; export type LogicEvaluationTypeDisplayOptionsValidity = [ LogicInverseDisplay, LogicInverseDisplay, LogicInverseDisplay ]; export type LogicFilterTypes = 'text' |'number' |'multi-list' |'multiListFuzzyText' |'multiValueList' |'multiValueText' |'boolean' |'currency' |'date' |'list'; export type LogicValueFormatType = 'text' |'date' |'number' |'currency' |'checkbox' |'select';