From 123be6cd10468fc48b0b9828e2dc496c27fb5d5a Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 28 Oct 2024 21:43:28 -0700 Subject: [PATCH 01/18] feature / conditional logic extend item teype --- .../activity/lib/types/conditionalLogic.ts | 320 +++++++++++++++++- src/entities/activity/model/mappers.ts | 72 +++- .../conditional-logic/model/conditions.ts | 101 ++++++ .../pass-survey/model/AnswerValidator.ts | 251 +++++++++++++- .../pass-survey/model/IAnswerValidator.ts | 79 ++++- .../model/PipelineVisibilityChecker.ts | 141 +++++++- src/shared/api/services/ActivityItemDto.ts | 319 ++++++++++++++++- 7 files changed, 1256 insertions(+), 27 deletions(-) diff --git a/src/entities/activity/lib/types/conditionalLogic.ts b/src/entities/activity/lib/types/conditionalLogic.ts index 3504bcb50..f3a9ebd54 100644 --- a/src/entities/activity/lib/types/conditionalLogic.ts +++ b/src/entities/activity/lib/types/conditionalLogic.ts @@ -15,10 +15,130 @@ type Condition = | EqualCondition | NotEqualCondition | BetweenCondition - | OutsideOfCondition; + | OutsideOfCondition + | GreaterThanDateCondition + | LessThanDateCondition + | EqualToDateCondition + | NotEqualToDateCondition + | BetweenDatesCondition + | OutsideOfDatesCondition + | GreaterThanTimeCondition + | LessThanTimeCondition + | EqualToTimeCondition + | NotEqualToTimeCondition + | BetweenTimesCondition + | OutsideOfTimesCondition + | GreaterThanTimeRangeCondition + | LessThanTimeRangeCondition + | EqualToTimeRangeCondition + | NotEqualToTimeRangeCondition + | BetweenTimesRangeCondition + | OutsideOfTimesRangeCondition + | GreaterThanSliderRowCondition + | LessThanSliderRowCondition + | EqualToSliderRowCondition + | NotEqualToSliderRowCondition + | BetweenSliderRowCondition + | OutsideOfSliderRowCondition + | EqualToRowOptionCondition + | NotEqualToRowOptionCondition + | IncludesRowOptionCondition + | NotIncludesRowOptionCondition; export type ConditionType = Condition['type']; +type EqualToRowOptionCondition = { + activityItemName: string; + type: 'EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; +}; + +type NotEqualToRowOptionCondition = { + activityItemName: string; + type: 'NOT_EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; +}; + +type IncludesRowOptionCondition = { + activityItemName: string; + type: 'INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; +}; + +type NotIncludesRowOptionCondition = { + activityItemName: string; + type: 'NOT_INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; +}; + +type EqualToSliderRowCondition = { + activityItemName: string; + type: 'EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; +}; + +type NotEqualToSliderRowCondition = { + activityItemName: string; + type: 'NOT_EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; +}; + +type GreaterThanSliderRowCondition = { + activityItemName: string; + type: 'GREATER_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; +}; + +type LessThanSliderRowCondition = { + activityItemName: string; + type: 'LESS_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; +}; + +type BetweenSliderRowCondition = { + activityItemName: string; + type: 'BETWEEN_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; + }; +}; + +type OutsideOfSliderRowCondition = { + activityItemName: string; + type: 'OUTSIDE_OF_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; + }; +}; + type IncludesOptionCondition = { activityItemName: string; type: 'INCLUDES_OPTION'; @@ -100,3 +220,201 @@ type OutsideOfCondition = { maxValue: number; }; }; + +type GreaterThanDateCondition = { + activityItemName: string; + type: 'GREATER_THAN_DATE'; + payload: { + date: string; + }; +}; + +type LessThanDateCondition = { + activityItemName: string; + type: 'LESS_THAN_DATE'; + payload: { + date: string; + }; +}; + +type EqualToDateCondition = { + activityItemName: string; + type: 'EQUAL_TO_DATE'; + payload: { + date: string; + }; +}; + +type NotEqualToDateCondition = { + activityItemName: string; + type: 'NOT_EQUAL_TO_DATE'; + payload: { + date: string; + }; +}; + +type BetweenDatesCondition = { + activityItemName: string; + type: 'BETWEEN_DATES'; + payload: { + minDate: string; + maxDate: string; + }; +}; + +type OutsideOfDatesCondition = { + activityItemName: string; + type: 'OUTSIDE_OF_DATES'; + payload: { + minDate: string; + maxDate: string; + }; +}; + +type GreaterThanTimeCondition = { + activityItemName: string; + type: 'GREATER_THAN_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type LessThanTimeCondition = { + activityItemName: string; + type: 'LESS_THAN_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type EqualToTimeCondition = { + activityItemName: string; + type: 'EQUAL_TO_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type NotEqualToTimeCondition = { + activityItemName: string; + type: 'NOT_EQUAL_TO_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type BetweenTimesCondition = { + activityItemName: string; + type: 'BETWEEN_TIMES'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type OutsideOfTimesCondition = { + activityItemName: string; + type: 'OUTSIDE_OF_TIMES'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type GreaterThanTimeRangeCondition = { + activityItemName: string; + type: 'GREATER_THAN_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type LessThanTimeRangeCondition = { + activityItemName: string; + type: 'LESS_THAN_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type EqualToTimeRangeCondition = { + activityItemName: string; + type: 'EQUAL_TO_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type NotEqualToTimeRangeCondition = { + activityItemName: string; + type: 'NOT_EQUAL_TO_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type BetweenTimesRangeCondition = { + activityItemName: string; + type: 'BETWEEN_TIMES_RANGE'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type OutsideOfTimesRangeCondition = { + activityItemName: string; + type: 'OUTSIDE_OF_TIMES_RANGE'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 1b72378d0..0900f0062 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -44,7 +44,6 @@ function mapTimerValue(dtoTimer: number | null) { if (dtoTimer) { return getMsFromSeconds(dtoTimer); } - return null; } @@ -69,8 +68,62 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { activityItemName: condition.itemName, }; - // @ts-expect-error - delete updatedCondition.itemName; + switch (condition.type) { + case 'INCLUDES_OPTION': + case 'NOT_INCLUDES_OPTION': + case 'EQUAL_TO_OPTION': + case 'NOT_EQUAL_TO_OPTION': + updatedCondition.payload = { optionValue: condition.payload.optionValue }; + break; + + case 'GREATER_THAN_DATE': + case 'LESS_THAN_DATE': + case 'EQUAL_TO_DATE': + case 'NOT_EQUAL_TO_DATE': + updatedCondition.payload = { date: condition.payload.date }; + break; + + case 'BETWEEN_DATES': + case 'OUTSIDE_OF_DATES': + updatedCondition.payload = { + minDate: condition.payload.minDate, + maxDate: condition.payload.maxDate, + }; + break; + + case 'GREATER_THAN_TIME': + case 'LESS_THAN_TIME': + case 'EQUAL_TO_TIME': + case 'NOT_EQUAL_TO_TIME': + updatedCondition.payload = { time: condition.payload.time }; + break; + + case 'BETWEEN_TIMES': + case 'OUTSIDE_OF_TIMES': + updatedCondition.payload = { + minTime: condition.payload.minTime, + maxTime: condition.payload.maxTime, + }; + break; + + case 'GREATER_THAN_TIME_RANGE': + case 'LESS_THAN_TIME_RANGE': + case 'EQUAL_TO_TIME_RANGE': + case 'NOT_EQUAL_TO_TIME_RANGE': + updatedCondition.payload = { time: condition.payload.time }; + break; + + case 'BETWEEN_TIMES_RANGE': + case 'OUTSIDE_OF_TIMES_RANGE': + updatedCondition.payload = { + minTime: condition.payload.minTime, + maxTime: condition.payload.maxTime, + }; + break; + + default: + updatedCondition.payload = condition.payload; + } return updatedCondition; }), @@ -126,7 +179,7 @@ function mapToAbTest(dto: ABTrailsItemDto): ActivityItem { fontSizeBeginEnd: nodesSettingsDto.fontSizeBeginEnd, }, deviceType: config.deviceType, - nodes: nodes.map(x => ({ + nodes: nodes.map(x => ({ ...x, })), tutorials: tutorials.tutorials.map(x => ({ @@ -240,6 +293,7 @@ function mapToAudio(dto: AudioItemDto): ActivityItem { hasTopNavigation: false, isHidden: dto.isHidden, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -264,6 +318,7 @@ function mapToAudioPlayer(dto: AudioPlayerItemDto): ActivityItem { hasTopNavigation: false, isHidden: dto.isHidden, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -285,6 +340,7 @@ function mapToDate(dto: DateItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -328,6 +384,7 @@ function mapToGeolocation(dto: GeolocationItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -406,6 +463,7 @@ function mapToStackedCheckboxes(dto: MultiSelectionRowsItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, isHidden: dto.isHidden, + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -430,6 +488,7 @@ function mapToNumberSelect(dto: NumberSelectionItemDto): ActivityItem { hasTopNavigation: false, isHidden: dto.isHidden, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -451,6 +510,7 @@ function mapToPhoto(dto: PhotoItemDto): ActivityItem { hasTopNavigation: false, isHidden: dto.isHidden, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -509,6 +569,7 @@ function mapToStackedRadio(dto: SingleSelectionRowsItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, isHidden: dto.isHidden, + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -567,6 +628,7 @@ function mapToStackedSlider(dto: SliderRowsItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, isHidden: dto.isHidden, + ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -618,7 +680,6 @@ function mapToParagraphText(dto: ParagraphTextItemDto): ActivityItem { canBeReset: true, hasTopNavigation: false, isHidden: dto.isHidden, - ...mapConditionalLogic(dto.conditionalLogic), }; } @@ -663,6 +724,7 @@ function mapToVideo(dto: VideoItemDto): ActivityItem { hasTopNavigation: false, isHidden: dto.isHidden, ...mapAdditionalText(dto.config), + ...mapConditionalLogic(dto.conditionalLogic), }; } diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index de016babd..5b5c6a08f 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -74,3 +74,104 @@ export const doesNotIncludeValue = ( return !input.includes(value); }; + +export const isGreaterThanDate = (input: Maybe, date: string) => { + if (!input) return false; + return new Date(input) > new Date(date); +}; + +export const isLessThanDate = (input: Maybe, date: string) => { + if (!input) return false; + return new Date(input) < new Date(date); +}; + +export const isEqualToDate = (input: Maybe, date: string) => { + if (!input) return false; + return new Date(input).getTime() === new Date(date).getTime(); +}; + +export const isNotEqualToDate = (input: Maybe, date: string) => { + if (!input) return false; + return new Date(input).getTime() !== new Date(date).getTime(); +}; + +export const isBetweenDates = ( + input: Maybe, + minDate: string, + maxDate: string, +) => { + if (!input) return false; + const inputDate = new Date(input); + return inputDate > new Date(minDate) && inputDate < new Date(maxDate); +}; + +export const isOutsideOfDates = ( + input: Maybe, + minDate: string, + maxDate: string, +) => { + if (!input) return false; + const inputDate = new Date(input); + return inputDate < new Date(minDate) || inputDate > new Date(maxDate); +}; + +export const isGreaterThanTime = ( + input: Maybe<{ hours: number; minutes: number }>, + time: { hours: number; minutes: number }, +) => { + if (!input) return false; + return ( + input.hours > time.hours || + (input.hours === time.hours && input.minutes > time.minutes) + ); +}; + +export const isLessThanTime = ( + input: Maybe<{ hours: number; minutes: number }>, + time: { hours: number; minutes: number }, +) => { + if (!input) return false; + return ( + input.hours < time.hours || + (input.hours === time.hours && input.minutes < time.minutes) + ); +}; + +export const isEqualToTime = ( + input: Maybe<{ hours: number; minutes: number }>, + time: { hours: number; minutes: number }, +) => { + if (!input) return false; + return input.hours === time.hours && input.minutes === time.minutes; +}; + +export const isNotEqualToTime = ( + input: Maybe<{ hours: number; minutes: number }>, + time: { hours: number; minutes: number }, +) => { + if (!input) return false; + return input.hours !== time.hours || input.minutes !== time.minutes; +}; + +export const isBetweenTimes = ( + input: Maybe<{ hours: number; minutes: number }>, + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, +) => { + if (!input) return false; + return ( + isGreaterThanTime(input, minTime) && isLessThanTime(input, maxTime) + ); +}; + +export const isOutsideOfTimes = ( + input: Maybe<{ hours: number; minutes: number }>, + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, +) => { + if (!input) return false; + return ( + isLessThanTime(input, minTime) || isGreaterThanTime(input, maxTime) + ); +}; + diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 44bb32e35..e53d0cbf5 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -19,13 +19,45 @@ import { TimeRangeResponse, } from '../lib/types/payload'; -export function AnswerValidator( - params?: AnswerValidatorArgs, -): IAnswerValidator { +import { isBefore, isAfter, isEqual } from 'date-fns'; +import { parseISO } from 'date-fns'; + +export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator { const { items, answers, step = 0 } = params ?? {}; const currentPipelineItem = items?.[step]; const currentAnswer = answers?.[step]; + const getAnswerDate = (): string | null => { + const answer = currentAnswer?.answer as Maybe; + return answer ?? null; + }; + + const getAnswerTime = (): { hours: number; minutes: number } | null => { + const answer = currentAnswer?.answer as Maybe<{ hours: number; minutes: number }>; + return answer ?? null; + }; + + const getAnswerTimeRange = (): { + startTime: { hours: number; minutes: number } | null; + endTime: { hours: number; minutes: number } | null; + } | null => { + const answer = currentAnswer?.answer as Maybe; + return answer ?? null; + }; + const getSliderRowValue = (rowIndex: number): number | null => { + const answer = currentAnswer?.answer as Maybe; + return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; + }; + + const getRowOptionValue = (rowIndex: number): string | null => { + const answer = currentAnswer?.answer as Maybe; + return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; + }; + + const timeToMinutes = (time: { hours: number; minutes: number }): number => { + return time.hours * 60 + time.minutes; + }; + return { isCorrect() { if (!currentPipelineItem?.validationOptions) { @@ -33,44 +65,88 @@ export function AnswerValidator( } const { correctAnswer } = currentPipelineItem.validationOptions; - return correctAnswer === currentAnswer?.answer; }, + isEqualToSliderRow(rowIndex: number, value: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && rowValue === value; + }, + + isNotEqualToSliderRow(rowIndex: number, value: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && rowValue !== value; + }, + + isGreaterThanSliderRow(rowIndex: number, value: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && rowValue > value; + }, + + isLessThanSliderRow(rowIndex: number, value: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && rowValue < value; + }, + + isBetweenSliderRowValues(rowIndex: number, minValue: number, maxValue: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && rowValue >= minValue && rowValue <= maxValue; + }, + + isOutsideOfSliderRowValues(rowIndex: number, minValue: number, maxValue: number) { + const rowValue = getSliderRowValue(rowIndex); + return rowValue !== null && (rowValue < minValue || rowValue > maxValue); + }, + + // Methods for options per row + isEqualToRowOption(rowIndex: number, optionValue: string) { + const selectedOption = getRowOptionValue(rowIndex); + return selectedOption !== null && selectedOption === optionValue; + }, + + isNotEqualToRowOption(rowIndex: number, optionValue: string) { + const selectedOption = getRowOptionValue(rowIndex); + return selectedOption !== null && selectedOption !== optionValue; + }, + + includesRowOption(rowIndex: number, optionValue: string) { + const selectedOption = getRowOptionValue(rowIndex); + return selectedOption !== null && includesValue([selectedOption], optionValue); + }, + + notIncludesRowOption(rowIndex: number, optionValue: string) { + const selectedOption = getRowOptionValue(rowIndex); + return selectedOption !== null && doesNotIncludeValue([selectedOption], optionValue); + }, + isBetweenValues(min: number, max: number) { const answer = currentAnswer?.answer as Maybe; - return isBetweenValues(answer, min, max); }, isOutsideOfValues(min: number, max: number) { const answer = currentAnswer?.answer as Maybe; - return isOutsideOfValues(answer, min, max); }, isEqualToValue(value: any) { const answer = currentAnswer?.answer as Maybe; - return isEqualToValue(answer, value); }, isEqualToOption(optionValue: string) { const answer = currentAnswer?.answer as Maybe; const selectedOption = answer?.value?.toString(); - return isEqualToValue(selectedOption, optionValue); }, - isGreaterThen(value: number) { + isGreaterThan(value: number) { const answer = currentAnswer?.answer as Maybe; - return isGreaterThan(answer, value); }, - isLessThen(value: number) { + isLessThan(value: number) { const answer = currentAnswer?.answer as Maybe; - return isLessThan(answer, value); }, @@ -78,7 +154,6 @@ export function AnswerValidator( const answer = (currentAnswer?.answer as Maybe)?.map(item => item.value.toString(), ); - return includesValue(answer, optionValue); }, @@ -86,9 +161,157 @@ export function AnswerValidator( const answer = (currentAnswer?.answer as Maybe)?.map(item => item.value.toString(), ); - return doesNotIncludeValue(answer, optionValue); }, + + isGreaterThanDate(date: string) { + const answerDate = getAnswerDate(); + return answerDate ? isAfter(parseISO(answerDate), parseISO(date)) : false; + }, + + isLessThanDate(date: string) { + const answerDate = getAnswerDate(); + return answerDate ? isBefore(parseISO(answerDate), parseISO(date)) : false; + }, + + isEqualToDate(date: string) { + const answerDate = getAnswerDate(); + return answerDate ? isEqual(parseISO(answerDate), parseISO(date)) : false; + }, + + isNotEqualToDate(date: string) { + const answerDate = getAnswerDate(); + return answerDate ? !isEqual(parseISO(answerDate), parseISO(date)) : false; + }, + + isBetweenDates(minDate: string, maxDate: string) { + const answerDate = getAnswerDate(); + return answerDate + ? !isBefore(parseISO(answerDate), parseISO(minDate)) && + !isAfter(parseISO(answerDate), parseISO(maxDate)) + : false; + }, + + isOutsideOfDates(minDate: string, maxDate: string) { + const answerDate = getAnswerDate(); + return answerDate + ? isBefore(parseISO(answerDate), parseISO(minDate)) || + isAfter(parseISO(answerDate), parseISO(maxDate)) + : false; + }, + + isGreaterThanTime(time: { hours: number; minutes: number }) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) > timeToMinutes(time) + : false; + }, + + isLessThanTime(time: { hours: number; minutes: number }) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) < timeToMinutes(time) + : false; + }, + + isEqualToTime(time: { hours: number; minutes: number }) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) === timeToMinutes(time) + : false; + }, + + isNotEqualToTime(time: { hours: number; minutes: number }) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) !== timeToMinutes(time) + : false; + }, + + isBetweenTimes( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) >= timeToMinutes(minTime) && + timeToMinutes(answerTime) <= timeToMinutes(maxTime) + : false; + }, + + isOutsideOfTimes( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ) { + const answerTime = getAnswerTime(); + return answerTime + ? timeToMinutes(answerTime) < timeToMinutes(minTime) || + timeToMinutes(answerTime) > timeToMinutes(maxTime) + : false; + }, + + isGreaterThanTimeRange(time: { hours: number; minutes: number }) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + return timeToMinutes(answerTimeRange.startTime) > timeToMinutes(time); + }, + + isLessThanTimeRange(time: { hours: number; minutes: number }) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + return timeToMinutes(answerTimeRange.startTime) < timeToMinutes(time); + }, + + isEqualToTimeRange(time: { hours: number; minutes: number }) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + return timeToMinutes(answerTimeRange.startTime) === timeToMinutes(time); + }, + + isNotEqualToTimeRange(time: { hours: number; minutes: number }) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + return timeToMinutes(answerTimeRange.startTime) !== timeToMinutes(time); + }, + + isBetweenTimesRange( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + const answerMinutes = timeToMinutes(answerTimeRange.startTime); + return ( + answerMinutes >= timeToMinutes(minTime) && + answerMinutes <= timeToMinutes(maxTime) + ); + }, + + isOutsideOfTimesRange( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ) { + const answerTimeRange = getAnswerTimeRange(); + if (!answerTimeRange || !answerTimeRange.startTime) { + return false; + } + const answerMinutes = timeToMinutes(answerTimeRange.startTime); + return ( + answerMinutes < timeToMinutes(minTime) || + answerMinutes > timeToMinutes(maxTime) + ); + }, + isValidAnswer() { switch (currentPipelineItem?.type) { case 'TimeRange': { diff --git a/src/features/pass-survey/model/IAnswerValidator.ts b/src/features/pass-survey/model/IAnswerValidator.ts index 719bc160d..a25d20b6b 100644 --- a/src/features/pass-survey/model/IAnswerValidator.ts +++ b/src/features/pass-survey/model/IAnswerValidator.ts @@ -18,13 +18,88 @@ export interface IAnswerValidator { isEqualToOption(optionValue: string): boolean; - isGreaterThen(value: number): boolean; + isGreaterThan(value: number): boolean; - isLessThen(value: number): boolean; + isLessThan(value: number): boolean; includesOption(optionValue: string): boolean; notIncludesOption(optionValue: string): boolean; + isGreaterThanDate(date: string): boolean; + + isLessThanDate(date: string): boolean; + + isEqualToDate(date: string): boolean; + + isNotEqualToDate(date: string): boolean; + + isBetweenDates(minDate: string, maxDate: string): boolean; + + isOutsideOfDates(minDate: string, maxDate: string): boolean; + + isGreaterThanTime(time: { hours: number; minutes: number }): boolean; + + isLessThanTime(time: { hours: number; minutes: number }): boolean; + + isEqualToTime(time: { hours: number; minutes: number }): boolean; + + isNotEqualToTime(time: { hours: number; minutes: number }): boolean; + + isBetweenTimes( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ): boolean; + + isOutsideOfTimes( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ): boolean; + + isGreaterThanTimeRange(time: { hours: number; minutes: number }): boolean; + + isLessThanTimeRange(time: { hours: number; minutes: number }): boolean; + + isEqualToTimeRange(time: { hours: number; minutes: number }): boolean; + + isNotEqualToTimeRange(time: { hours: number; minutes: number }): boolean; + + isBetweenTimesRange( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ): boolean; + + isOutsideOfTimesRange( + minTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number }, + ): boolean; + isEqualToSliderRow(rowIndex: number, value: number): boolean; + + isNotEqualToSliderRow(rowIndex: number, value: number): boolean; + + isGreaterThanSliderRow(rowIndex: number, value: number): boolean; + + isLessThanSliderRow(rowIndex: number, value: number): boolean; + + isBetweenSliderRowValues( + rowIndex: number, + minValue: number, + maxValue: number, + ): boolean; + + isOutsideOfSliderRowValues( + rowIndex: number, + minValue: number, + maxValue: number, + ): boolean; + + isEqualToRowOption(rowIndex: number, optionValue: string): boolean; + + isNotEqualToRowOption(rowIndex: number, optionValue: string): boolean; + + includesRowOption(rowIndex: number, optionValue: string): boolean; + + notIncludesRowOption(rowIndex: number, optionValue: string): boolean; + isValidAnswer(): boolean; } diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 3f706e49c..c7e246421 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -37,12 +37,11 @@ export function PipelineVisibilityChecker( condition.payload.maxValue, ); - case 'OUTSIDE_OF': { + case 'OUTSIDE_OF': return answerValidator.isOutsideOfValues( condition.payload.minValue, condition.payload.maxValue, ); - } case 'EQUAL': return answerValidator.isEqualToValue(condition.payload.value); @@ -59,10 +58,10 @@ export function PipelineVisibilityChecker( ); case 'GREATER_THAN': - return answerValidator.isGreaterThen(condition.payload.value); + return answerValidator.isGreaterThan(condition.payload.value); case 'LESS_THAN': - return answerValidator.isLessThen(condition.payload.value); + return answerValidator.isLessThan(condition.payload.value); case 'INCLUDES_OPTION': return answerValidator.includesOption(condition.payload.optionValue); @@ -72,6 +71,140 @@ export function PipelineVisibilityChecker( condition.payload.optionValue, ); + case 'GREATER_THAN_DATE': + return answerValidator.isGreaterThanDate(condition.payload.date); + + case 'LESS_THAN_DATE': + return answerValidator.isLessThanDate(condition.payload.date); + + case 'EQUAL_TO_DATE': + return answerValidator.isEqualToDate(condition.payload.date); + + case 'NOT_EQUAL_TO_DATE': + return answerValidator.isNotEqualToDate(condition.payload.date); + + case 'BETWEEN_DATES': + return answerValidator.isBetweenDates( + condition.payload.minDate, + condition.payload.maxDate, + ); + + case 'OUTSIDE_OF_DATES': + return answerValidator.isOutsideOfDates( + condition.payload.minDate, + condition.payload.maxDate, + ); + + case 'GREATER_THAN_TIME': + return answerValidator.isGreaterThanTime(condition.payload.time); + + case 'LESS_THAN_TIME': + return answerValidator.isLessThanTime(condition.payload.time); + + case 'EQUAL_TO_TIME': + return answerValidator.isEqualToTime(condition.payload.time); + + case 'NOT_EQUAL_TO_TIME': + return answerValidator.isNotEqualToTime(condition.payload.time); + + case 'BETWEEN_TIMES': + return answerValidator.isBetweenTimes( + condition.payload.minTime, + condition.payload.maxTime, + ); + + case 'OUTSIDE_OF_TIMES': + return answerValidator.isOutsideOfTimes( + condition.payload.minTime, + condition.payload.maxTime, + ); + + case 'GREATER_THAN_TIME_RANGE': + return answerValidator.isGreaterThanTimeRange(condition.payload.time); + + case 'LESS_THAN_TIME_RANGE': + return answerValidator.isLessThanTimeRange(condition.payload.time); + + case 'EQUAL_TO_TIME_RANGE': + return answerValidator.isEqualToTimeRange(condition.payload.time); + + case 'NOT_EQUAL_TO_TIME_RANGE': + return answerValidator.isNotEqualToTimeRange(condition.payload.time); + + case 'BETWEEN_TIMES_RANGE': + return answerValidator.isBetweenTimesRange( + condition.payload.minTime, + condition.payload.maxTime, + ); + + case 'OUTSIDE_OF_TIMES_RANGE': + return answerValidator.isOutsideOfTimesRange( + condition.payload.minTime, + condition.payload.maxTime, + ); + + case 'EQUAL_TO_SLIDER_ROWS': + return answerValidator.isEqualToSliderRow( + condition.payload.rowIndex, + condition.payload.value, + ); + + case 'NOT_EQUAL_TO_SLIDER_ROWS': + return answerValidator.isNotEqualToSliderRow( + condition.payload.rowIndex, + condition.payload.value, + ); + + case 'GREATER_THAN_SLIDER_ROWS': + return answerValidator.isGreaterThanSliderRow( + condition.payload.rowIndex, + condition.payload.value, + ); + + case 'LESS_THAN_SLIDER_ROWS': + return answerValidator.isLessThanSliderRow( + condition.payload.rowIndex, + condition.payload.value, + ); + + case 'BETWEEN_SLIDER_ROWS': + return answerValidator.isBetweenSliderRowValues( + condition.payload.rowIndex, + condition.payload.minValue, + condition.payload.maxValue, + ); + + case 'OUTSIDE_OF_SLIDER_ROWS': + return answerValidator.isOutsideOfSliderRowValues( + condition.payload.rowIndex, + condition.payload.minValue, + condition.payload.maxValue, + ); + + case 'EQUAL_TO_ROW_OPTION': + return answerValidator.isEqualToRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); + + case 'NOT_EQUAL_TO_ROW_OPTION': + return answerValidator.isNotEqualToRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); + + case 'INCLUDES_ROW_OPTION': + return answerValidator.includesRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); + + case 'NOT_INCLUDES_ROW_OPTION': + return answerValidator.notIncludesRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); + default: return true; } diff --git a/src/shared/api/services/ActivityItemDto.ts b/src/shared/api/services/ActivityItemDto.ts index 1e0cb8866..4e3f54ad7 100644 --- a/src/shared/api/services/ActivityItemDto.ts +++ b/src/shared/api/services/ActivityItemDto.ts @@ -45,7 +45,127 @@ type ConditionDto = | EqualConditionDto | NotEqualConditionDto | BetweenConditionDto - | OutsideOfConditionDto; + | OutsideOfConditionDto + | GreaterThanDateConditionDto + | LessThanDateConditionDto + | EqualToDateConditionDto + | NotEqualToDateConditionDto + | BetweenDatesConditionDto + | OutsideOfDatesConditionDto + | GreaterThanTimeConditionDto + | LessThanTimeConditionDto + | EqualToTimeConditionDto + | NotEqualToTimeConditionDto + | BetweenTimesConditionDto + | OutsideOfTimesConditionDto + | GreaterThanTimeRangeConditionDto + | LessThanTimeRangeConditionDto + | EqualToTimeRangeConditionDto + | NotEqualToTimeRangeConditionDto + | BetweenTimesRangeConditionDto + | OutsideOfTimesRangeConditionDto + | EqualToRowOptionConditionDto + | NotEqualToRowOptionConditionDto + | IncludesRowOptionConditionDto + | NotIncludesRowOptionConditionDto + | EqualToSliderRowConditionDto + | NotEqualToSliderRowConditionDto + | GreaterThanSliderRowConditionDto + | LessThanSliderRowConditionDto + | BetweenSliderRowConditionDto + | OutsideOfSliderRowConditionDto; + + type EqualToRowOptionConditionDto = { + itemName: string; + type: 'EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; + }; + + type NotEqualToRowOptionConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; + }; + + type EqualToSliderRowConditionDto = { + itemName: string; + type: 'EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; + }; + + type NotEqualToSliderRowConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; + }; + + type GreaterThanSliderRowConditionDto = { + itemName: string; + type: 'GREATER_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; + }; + + type LessThanSliderRowConditionDto = { + itemName: string; + type: 'LESS_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; + }; + }; + + type BetweenSliderRowConditionDto = { + itemName: string; + type: 'BETWEEN_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; + }; + }; + + type OutsideOfSliderRowConditionDto = { + itemName: string; + type: 'OUTSIDE_OF_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; + }; + }; + + type IncludesRowOptionConditionDto = { + itemName: string; + type: 'INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; + }; + + type NotIncludesRowOptionConditionDto = { + itemName: string; + type: 'NOT_INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; + }; + }; type IncludesOptionConditionDto = { itemName: string; @@ -128,6 +248,203 @@ type OutsideOfConditionDto = { maxValue: number; }; }; +type GreaterThanDateConditionDto = { + itemName: string; + type: 'GREATER_THAN_DATE'; + payload: { + date: string; + }; +}; + +type LessThanDateConditionDto = { + itemName: string; + type: 'LESS_THAN_DATE'; + payload: { + date: string; + }; +}; + +type EqualToDateConditionDto = { + itemName: string; + type: 'EQUAL_TO_DATE'; + payload: { + date: string; + }; +}; + +type NotEqualToDateConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_DATE'; + payload: { + date: string; + }; +}; + +type BetweenDatesConditionDto = { + itemName: string; + type: 'BETWEEN_DATES'; + payload: { + minDate: string; + maxDate: string; + }; +}; + +type OutsideOfDatesConditionDto = { + itemName: string; + type: 'OUTSIDE_OF_DATES'; + payload: { + minDate: string; + maxDate: string; + }; +}; + +type GreaterThanTimeConditionDto = { + itemName: string; + type: 'GREATER_THAN_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type LessThanTimeConditionDto = { + itemName: string; + type: 'LESS_THAN_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type EqualToTimeConditionDto = { + itemName: string; + type: 'EQUAL_TO_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type NotEqualToTimeConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_TIME'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type BetweenTimesConditionDto = { + itemName: string; + type: 'BETWEEN_TIMES'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type OutsideOfTimesConditionDto = { + itemName: string; + type: 'OUTSIDE_OF_TIMES'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type GreaterThanTimeRangeConditionDto = { + itemName: string; + type: 'GREATER_THAN_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type LessThanTimeRangeConditionDto = { + itemName: string; + type: 'LESS_THAN_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type EqualToTimeRangeConditionDto = { + itemName: string; + type: 'EQUAL_TO_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type NotEqualToTimeRangeConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_TIME_RANGE'; + payload: { + time: { + hours: number; + minutes: number; + }; + }; +}; + +type BetweenTimesRangeConditionDto = { + itemName: string; + type: 'BETWEEN_TIMES_RANGE'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; + +type OutsideOfTimesRangeConditionDto = { + itemName: string; + type: 'OUTSIDE_OF_TIMES_RANGE'; + payload: { + minTime: { + hours: number; + minutes: number; + }; + maxTime: { + hours: number; + minutes: number; + }; + }; +}; type ButtonsConfiguration = { removeBackButton: boolean; From 3fe3cce8dcf9cdf1127c9fc175440ef0d8d2c044 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 28 Oct 2024 22:00:44 -0700 Subject: [PATCH 02/18] lint fix --- src/entities/activity/model/mappers.ts | 6 +- .../conditional-logic/model/conditions.ts | 9 +- .../pass-survey/model/AnswerValidator.ts | 56 ++++-- .../pass-survey/model/IAnswerValidator.ts | 2 +- .../model/PipelineVisibilityChecker.ts | 2 +- src/shared/api/services/ActivityItemDto.ts | 174 +++++++++--------- 6 files changed, 134 insertions(+), 115 deletions(-) diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 0900f0062..4f3a59f51 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -73,7 +73,9 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { case 'NOT_INCLUDES_OPTION': case 'EQUAL_TO_OPTION': case 'NOT_EQUAL_TO_OPTION': - updatedCondition.payload = { optionValue: condition.payload.optionValue }; + updatedCondition.payload = { + optionValue: condition.payload.optionValue, + }; break; case 'GREATER_THAN_DATE': @@ -179,7 +181,7 @@ function mapToAbTest(dto: ABTrailsItemDto): ActivityItem { fontSizeBeginEnd: nodesSettingsDto.fontSizeBeginEnd, }, deviceType: config.deviceType, - nodes: nodes.map(x => ({ + nodes: nodes.map(x => ({ ...x, })), tutorials: tutorials.tutorials.map(x => ({ diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 5b5c6a08f..191625976 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -159,9 +159,7 @@ export const isBetweenTimes = ( maxTime: { hours: number; minutes: number }, ) => { if (!input) return false; - return ( - isGreaterThanTime(input, minTime) && isLessThanTime(input, maxTime) - ); + return isGreaterThanTime(input, minTime) && isLessThanTime(input, maxTime); }; export const isOutsideOfTimes = ( @@ -170,8 +168,5 @@ export const isOutsideOfTimes = ( maxTime: { hours: number; minutes: number }, ) => { if (!input) return false; - return ( - isLessThanTime(input, minTime) || isGreaterThanTime(input, maxTime) - ); + return isLessThanTime(input, minTime) || isGreaterThanTime(input, maxTime); }; - diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index e53d0cbf5..95eb7956c 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -1,3 +1,6 @@ +import { isBefore, isAfter, isEqual } from 'date-fns'; +import { parseISO } from 'date-fns'; + import { doesNotIncludeValue, includesValue, @@ -19,10 +22,9 @@ import { TimeRangeResponse, } from '../lib/types/payload'; -import { isBefore, isAfter, isEqual } from 'date-fns'; -import { parseISO } from 'date-fns'; - -export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator { +export function AnswerValidator( + params?: AnswerValidatorArgs, +): IAnswerValidator { const { items, answers, step = 0 } = params ?? {}; const currentPipelineItem = items?.[step]; const currentAnswer = answers?.[step]; @@ -31,9 +33,12 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator const answer = currentAnswer?.answer as Maybe; return answer ?? null; }; - + const getAnswerTime = (): { hours: number; minutes: number } | null => { - const answer = currentAnswer?.answer as Maybe<{ hours: number; minutes: number }>; + const answer = currentAnswer?.answer as Maybe<{ + hours: number; + minutes: number; + }>; return answer ?? null; }; @@ -88,12 +93,20 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator return rowValue !== null && rowValue < value; }, - isBetweenSliderRowValues(rowIndex: number, minValue: number, maxValue: number) { + isBetweenSliderRowValues( + rowIndex: number, + minValue: number, + maxValue: number, + ) { const rowValue = getSliderRowValue(rowIndex); return rowValue !== null && rowValue >= minValue && rowValue <= maxValue; }, - isOutsideOfSliderRowValues(rowIndex: number, minValue: number, maxValue: number) { + isOutsideOfSliderRowValues( + rowIndex: number, + minValue: number, + maxValue: number, + ) { const rowValue = getSliderRowValue(rowIndex); return rowValue !== null && (rowValue < minValue || rowValue > maxValue); }, @@ -111,13 +124,18 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator includesRowOption(rowIndex: number, optionValue: string) { const selectedOption = getRowOptionValue(rowIndex); - return selectedOption !== null && includesValue([selectedOption], optionValue); + return ( + selectedOption !== null && includesValue([selectedOption], optionValue) + ); }, notIncludesRowOption(rowIndex: number, optionValue: string) { const selectedOption = getRowOptionValue(rowIndex); - return selectedOption !== null && doesNotIncludeValue([selectedOption], optionValue); - }, + return ( + selectedOption !== null && + doesNotIncludeValue([selectedOption], optionValue) + ); + }, isBetweenValues(min: number, max: number) { const answer = currentAnswer?.answer as Maybe; @@ -171,7 +189,9 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator isLessThanDate(date: string) { const answerDate = getAnswerDate(); - return answerDate ? isBefore(parseISO(answerDate), parseISO(date)) : false; + return answerDate + ? isBefore(parseISO(answerDate), parseISO(date)) + : false; }, isEqualToDate(date: string) { @@ -181,14 +201,16 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator isNotEqualToDate(date: string) { const answerDate = getAnswerDate(); - return answerDate ? !isEqual(parseISO(answerDate), parseISO(date)) : false; + return answerDate + ? !isEqual(parseISO(answerDate), parseISO(date)) + : false; }, isBetweenDates(minDate: string, maxDate: string) { const answerDate = getAnswerDate(); return answerDate ? !isBefore(parseISO(answerDate), parseISO(minDate)) && - !isAfter(parseISO(answerDate), parseISO(maxDate)) + !isAfter(parseISO(answerDate), parseISO(maxDate)) : false; }, @@ -196,7 +218,7 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator const answerDate = getAnswerDate(); return answerDate ? isBefore(parseISO(answerDate), parseISO(minDate)) || - isAfter(parseISO(answerDate), parseISO(maxDate)) + isAfter(parseISO(answerDate), parseISO(maxDate)) : false; }, @@ -235,7 +257,7 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator const answerTime = getAnswerTime(); return answerTime ? timeToMinutes(answerTime) >= timeToMinutes(minTime) && - timeToMinutes(answerTime) <= timeToMinutes(maxTime) + timeToMinutes(answerTime) <= timeToMinutes(maxTime) : false; }, @@ -246,7 +268,7 @@ export function AnswerValidator(params?: AnswerValidatorArgs): IAnswerValidator const answerTime = getAnswerTime(); return answerTime ? timeToMinutes(answerTime) < timeToMinutes(minTime) || - timeToMinutes(answerTime) > timeToMinutes(maxTime) + timeToMinutes(answerTime) > timeToMinutes(maxTime) : false; }, diff --git a/src/features/pass-survey/model/IAnswerValidator.ts b/src/features/pass-survey/model/IAnswerValidator.ts index a25d20b6b..1f375d223 100644 --- a/src/features/pass-survey/model/IAnswerValidator.ts +++ b/src/features/pass-survey/model/IAnswerValidator.ts @@ -92,7 +92,7 @@ export interface IAnswerValidator { minValue: number, maxValue: number, ): boolean; - + isEqualToRowOption(rowIndex: number, optionValue: string): boolean; isNotEqualToRowOption(rowIndex: number, optionValue: string): boolean; diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index c7e246421..33fc6f149 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -142,7 +142,7 @@ export function PipelineVisibilityChecker( condition.payload.minTime, condition.payload.maxTime, ); - + case 'EQUAL_TO_SLIDER_ROWS': return answerValidator.isEqualToSliderRow( condition.payload.rowIndex, diff --git a/src/shared/api/services/ActivityItemDto.ts b/src/shared/api/services/ActivityItemDto.ts index 4e3f54ad7..524681313 100644 --- a/src/shared/api/services/ActivityItemDto.ts +++ b/src/shared/api/services/ActivityItemDto.ts @@ -74,98 +74,98 @@ type ConditionDto = | LessThanSliderRowConditionDto | BetweenSliderRowConditionDto | OutsideOfSliderRowConditionDto; - - type EqualToRowOptionConditionDto = { - itemName: string; - type: 'EQUAL_TO_ROW_OPTION'; - payload: { - optionValue: string; - rowIndex: number; - }; + +type EqualToRowOptionConditionDto = { + itemName: string; + type: 'EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; }; +}; - type NotEqualToRowOptionConditionDto = { - itemName: string; - type: 'NOT_EQUAL_TO_ROW_OPTION'; - payload: { - optionValue: string; - rowIndex: number; - }; +type NotEqualToRowOptionConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; }; +}; - type EqualToSliderRowConditionDto = { - itemName: string; - type: 'EQUAL_TO_SLIDER_ROWS'; - payload: { - value: number; - rowIndex: number; - }; +type EqualToSliderRowConditionDto = { + itemName: string; + type: 'EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; }; - - type NotEqualToSliderRowConditionDto = { - itemName: string; - type: 'NOT_EQUAL_TO_SLIDER_ROWS'; - payload: { - value: number; - rowIndex: number; - }; +}; + +type NotEqualToSliderRowConditionDto = { + itemName: string; + type: 'NOT_EQUAL_TO_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; }; - - type GreaterThanSliderRowConditionDto = { - itemName: string; - type: 'GREATER_THAN_SLIDER_ROWS'; - payload: { - value: number; - rowIndex: number; - }; +}; + +type GreaterThanSliderRowConditionDto = { + itemName: string; + type: 'GREATER_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; }; - - type LessThanSliderRowConditionDto = { - itemName: string; - type: 'LESS_THAN_SLIDER_ROWS'; - payload: { - value: number; - rowIndex: number; - }; +}; + +type LessThanSliderRowConditionDto = { + itemName: string; + type: 'LESS_THAN_SLIDER_ROWS'; + payload: { + value: number; + rowIndex: number; }; - - type BetweenSliderRowConditionDto = { - itemName: string; - type: 'BETWEEN_SLIDER_ROWS'; - payload: { - minValue: number; - maxValue: number; - rowIndex: number; - }; +}; + +type BetweenSliderRowConditionDto = { + itemName: string; + type: 'BETWEEN_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; }; - - type OutsideOfSliderRowConditionDto = { - itemName: string; - type: 'OUTSIDE_OF_SLIDER_ROWS'; - payload: { - minValue: number; - maxValue: number; - rowIndex: number; - }; +}; + +type OutsideOfSliderRowConditionDto = { + itemName: string; + type: 'OUTSIDE_OF_SLIDER_ROWS'; + payload: { + minValue: number; + maxValue: number; + rowIndex: number; }; - - type IncludesRowOptionConditionDto = { - itemName: string; - type: 'INCLUDES_ROW_OPTION'; - payload: { - optionValue: string; - rowIndex: number; - }; +}; + +type IncludesRowOptionConditionDto = { + itemName: string; + type: 'INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; }; - - type NotIncludesRowOptionConditionDto = { - itemName: string; - type: 'NOT_INCLUDES_ROW_OPTION'; - payload: { - optionValue: string; - rowIndex: number; - }; +}; + +type NotIncludesRowOptionConditionDto = { + itemName: string; + type: 'NOT_INCLUDES_ROW_OPTION'; + payload: { + optionValue: string; + rowIndex: number; }; +}; type IncludesOptionConditionDto = { itemName: string; @@ -252,7 +252,7 @@ type GreaterThanDateConditionDto = { itemName: string; type: 'GREATER_THAN_DATE'; payload: { - date: string; + date: string; }; }; @@ -268,7 +268,7 @@ type EqualToDateConditionDto = { itemName: string; type: 'EQUAL_TO_DATE'; payload: { - date: string; + date: string; }; }; @@ -276,7 +276,7 @@ type NotEqualToDateConditionDto = { itemName: string; type: 'NOT_EQUAL_TO_DATE'; payload: { - date: string; + date: string; }; }; @@ -284,8 +284,8 @@ type BetweenDatesConditionDto = { itemName: string; type: 'BETWEEN_DATES'; payload: { - minDate: string; - maxDate: string; + minDate: string; + maxDate: string; }; }; @@ -293,8 +293,8 @@ type OutsideOfDatesConditionDto = { itemName: string; type: 'OUTSIDE_OF_DATES'; payload: { - minDate: string; - maxDate: string; + minDate: string; + maxDate: string; }; }; From efe93a7e635f3fbbeaa8c95fe821e3cbd3e6a794 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 28 Oct 2024 22:54:59 -0700 Subject: [PATCH 03/18] fixing tests and creating new test --- .../activity/lib/types/conditionalLogic.ts | 2 +- src/entities/activity/model/mappers.ts | 5 +- .../model/tests/AnswerValidator.test.ts | 4 +- .../tests/PipelineVisibilityChecker.test.ts | 56 +++++++++++++++++-- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/entities/activity/lib/types/conditionalLogic.ts b/src/entities/activity/lib/types/conditionalLogic.ts index f3a9ebd54..bd2816a73 100644 --- a/src/entities/activity/lib/types/conditionalLogic.ts +++ b/src/entities/activity/lib/types/conditionalLogic.ts @@ -5,7 +5,7 @@ export type ConditionalLogic = { conditions: Array; }; -type Condition = +export type Condition = | IncludesOptionCondition | NotIncludesOptionCondition | EqualToOptionCondition diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 4f3a59f51..2f114103c 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -63,9 +63,10 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { conditionalLogic: { match: dto.match, conditions: dto.conditions.map(condition => { + const { itemName, ...rest } = condition; const updatedCondition = { - ...condition, - activityItemName: condition.itemName, + ...rest, + activityItemName: itemName, }; switch (condition.type) { diff --git a/src/features/pass-survey/model/tests/AnswerValidator.test.ts b/src/features/pass-survey/model/tests/AnswerValidator.test.ts index 2efd1eb8c..c60b47ee7 100644 --- a/src/features/pass-survey/model/tests/AnswerValidator.test.ts +++ b/src/features/pass-survey/model/tests/AnswerValidator.test.ts @@ -24,11 +24,11 @@ const imitateValidate = (validator: IAnswerValidator, type: ConditionType) => { break; } case 'GREATER_THAN': { - result = validator.isGreaterThen(0); + result = validator.isGreaterThan(0); break; } case 'LESS_THAN': { - result = validator.isLessThen(10); + result = validator.isLessThan(10); break; } case 'INCLUDES_OPTION': { diff --git a/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts b/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts index 15a8ebb33..dfb00abc6 100644 --- a/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts +++ b/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts @@ -1,6 +1,7 @@ import { ConditionType, Match, + Condition } from '@app/entities/activity/lib/types/conditionalLogic'; import { @@ -11,6 +12,7 @@ import { getEmptyRadioItem as getRadioItem, getSliderItem, getRadioResponse, + getStackedSliderItem, } from './testHelpers'; import { Answers } from '../../lib/hooks/useActivityStorageRecord'; import { PipelineItem } from '../../lib/types/payload'; @@ -36,9 +38,15 @@ describe('PipelineVisibilityChecker: penetration tests', () => { type: ConditionType, payload: any, ) => { + const condition: Condition = { + activityItemName: itemPointedTo.name!, + type, + payload, + } as Condition; + itemSetTo.conditionalLogic = { match: match, - conditions: [{ activityItemName: itemPointedTo.name!, type, payload }], + conditions: [condition], }; }; @@ -219,7 +227,7 @@ describe('PipelineVisibilityChecker: penetration tests', () => { maxValue: 4, }); - const answers: Answers = { '0': { answer: 1 }, '1': { answer: 2 } }; + const answers: Answers = { '0': { answer: 1 }, '1': { answer: 4 } }; const { isItemVisible } = PipelineVisibilityChecker(items, answers); @@ -479,7 +487,7 @@ describe('PipelineVisibilityChecker: penetration tests', () => { payload: { optionValue: '3', }, - }); + } as Condition); const answers: Answers = { '0': { answer: getCheckboxResponse([1, 2]) }, @@ -506,7 +514,7 @@ describe('PipelineVisibilityChecker: penetration tests', () => { payload: { optionValue: '3', }, - }); + } as Condition); const answers: Answers = { '0': { answer: getCheckboxResponse([1, 2]) }, @@ -519,4 +527,44 @@ describe('PipelineVisibilityChecker: penetration tests', () => { expect(result).toEqual(true); }); + + it('Should return true when slider row value equals specified value and type is EQUAL_TO_SLIDER_ROWS', () => { + const items = [getRadioItem('mock-radio-1'), getStackedSliderItem()]; + items[1].name = 'mock-slider-1'; + + setCondition(items[0], items[1], 'any', 'EQUAL_TO_SLIDER_ROWS', { + rowIndex: 0, + value: 5, + }); + + const answers: Answers = { + '1': { answer: [5] }, + }; + + const { isItemVisible } = PipelineVisibilityChecker(items, answers); + + const result = isItemVisible(0); + + expect(result).toEqual(true); + }); + + it('Should return false when slider row value does not equal specified value and type is EQUAL_TO_SLIDER_ROWS', () => { + const items = [getRadioItem('mock-radio-1'), getStackedSliderItem()]; + items[1].name = 'mock-slider-1'; + + setCondition(items[0], items[1], 'any', 'EQUAL_TO_SLIDER_ROWS', { + rowIndex: 0, + value: 5, + }); + + const answers: Answers = { + '1': { answer: [3] }, + }; + + const { isItemVisible } = PipelineVisibilityChecker(items, answers); + + const result = isItemVisible(0); + + expect(result).toEqual(false); + }); }); From c9dc06315ea0918c2e2a7fa33167a89d888c5e33 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 28 Oct 2024 22:57:54 -0700 Subject: [PATCH 04/18] eslint fix to test --- src/features/pass-survey/model/tests/AnswerValidator.test.ts | 2 +- .../pass-survey/model/tests/PipelineVisibilityChecker.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/pass-survey/model/tests/AnswerValidator.test.ts b/src/features/pass-survey/model/tests/AnswerValidator.test.ts index c60b47ee7..93ca01ccc 100644 --- a/src/features/pass-survey/model/tests/AnswerValidator.test.ts +++ b/src/features/pass-survey/model/tests/AnswerValidator.test.ts @@ -28,7 +28,7 @@ const imitateValidate = (validator: IAnswerValidator, type: ConditionType) => { break; } case 'LESS_THAN': { - result = validator.isLessThan(10); + result = validator.isLessThan(10); break; } case 'INCLUDES_OPTION': { diff --git a/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts b/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts index dfb00abc6..1e2b96bef 100644 --- a/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts +++ b/src/features/pass-survey/model/tests/PipelineVisibilityChecker.test.ts @@ -1,7 +1,7 @@ import { ConditionType, Match, - Condition + Condition, } from '@app/entities/activity/lib/types/conditionalLogic'; import { @@ -12,7 +12,7 @@ import { getEmptyRadioItem as getRadioItem, getSliderItem, getRadioResponse, - getStackedSliderItem, + getStackedSliderItem, } from './testHelpers'; import { Answers } from '../../lib/hooks/useActivityStorageRecord'; import { PipelineItem } from '../../lib/types/payload'; From f75d90a9f349c37718f8ca3d94b0b507522a62eb Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 20:28:36 -0700 Subject: [PATCH 05/18] fixing type GREATER THAN TIME --- .../activity/lib/services/timeToMinutes.ts | 3 + .../conditional-logic/model/conditions.ts | 21 ++- .../pass-survey/model/AnswerValidator.ts | 130 ++++++++++++++---- .../model/PipelineVisibilityChecker.ts | 26 +++- src/shared/ui/DateTimePicker.tsx | 15 +- 5 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 src/entities/activity/lib/services/timeToMinutes.ts diff --git a/src/entities/activity/lib/services/timeToMinutes.ts b/src/entities/activity/lib/services/timeToMinutes.ts new file mode 100644 index 000000000..b8e758d18 --- /dev/null +++ b/src/entities/activity/lib/services/timeToMinutes.ts @@ -0,0 +1,3 @@ +export const timeToMinutes = (time: { hours: number; minutes: number }): number => { + return time.hours * 60 + time.minutes; + }; \ No newline at end of file diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 191625976..d7633b682 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -1,3 +1,5 @@ +import { timeToMinutes } from "@app/entities/activity/lib/services/timeToMinutes"; + export const isBetweenValues = ( input: Maybe, min: number, @@ -117,13 +119,18 @@ export const isOutsideOfDates = ( export const isGreaterThanTime = ( input: Maybe<{ hours: number; minutes: number }>, - time: { hours: number; minutes: number }, -) => { - if (!input) return false; - return ( - input.hours > time.hours || - (input.hours === time.hours && input.minutes > time.minutes) - ); + time: { hours: number; minutes: number } +): boolean => { + if (!input) { + return false; + } + + const inputMinutes = timeToMinutes(input); + const conditionMinutes = timeToMinutes(time); + + const result = inputMinutes > conditionMinutes; + + return result; }; export const isLessThanTime = ( diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 95eb7956c..5028347c5 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -34,14 +34,35 @@ export function AnswerValidator( return answer ?? null; }; + function isValidTimeFormat(time: any): boolean { + return time && typeof time.hours === 'number' && typeof time.minutes === 'number'; + } const getAnswerTime = (): { hours: number; minutes: number } | null => { - const answer = currentAnswer?.answer as Maybe<{ - hours: number; - minutes: number; - }>; + let answer = currentAnswer?.answer as Maybe<{ hours: number; minutes: number }>; + + if (typeof answer === 'string') { + answer = convertTo24HourFormat(answer); + } + return answer ?? null; }; + function convert24HourTo12Hour(hours: number, minutes: number): { hours: number, minutes: number, period: 'AM' | 'PM' } { + const period = hours >= 12 ? 'PM' : 'AM'; + const adjustedHours = hours % 12 || 12; // Converts 0 to 12 for midnight and uses 12-hour format + return { hours: adjustedHours, minutes, period }; + } + + function convertToMinutes(time: { hours: number, minutes: number, period?: 'AM' | 'PM' }): number { + let totalMinutes = time.hours * 60 + time.minutes; + if (time.period === 'PM' && time.hours !== 12) { + totalMinutes += 12 * 60; + } else if (time.period === 'AM' && time.hours === 12) { + totalMinutes -= 12 * 60; + } + return totalMinutes; + } + const getAnswerTimeRange = (): { startTime: { hours: number; minutes: number } | null; endTime: { hours: number; minutes: number } | null; @@ -49,6 +70,20 @@ export function AnswerValidator( const answer = currentAnswer?.answer as Maybe; return answer ?? null; }; + + const convertTo24HourFormat = (timeStr: string): { hours: number; minutes: number } => { + const [time, modifier] = timeStr.split(' '); + let [hours, minutes] = time.split(':').map(Number); + + if (modifier.toLowerCase() === 'pm' && hours < 12) { + hours += 12; + } + if (modifier.toLowerCase() === 'am' && hours === 12) { + hours = 0; + } + + return { hours, minutes }; + }; const getSliderRowValue = (rowIndex: number): number | null => { const answer = currentAnswer?.answer as Maybe; return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; @@ -224,60 +259,97 @@ export function AnswerValidator( isGreaterThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) > timeToMinutes(time) - : false; + + if (!isValidTimeFormat(time)) { + return false; + } + + const normalizedTime = timeToMinutes(time); + const normalizedAnswerTime = answerTime ? timeToMinutes(answerTime) : null; + + return normalizedAnswerTime !== null && normalizedAnswerTime > normalizedTime; }, isLessThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) < timeToMinutes(time) - : false; + if (!answerTime) return false; + + const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + const answerTimeInMinutes = convertToMinutes(answerTime); + + return answerTimeInMinutes < backendTimeInMinutes; }, isEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) === timeToMinutes(time) - : false; + if (!answerTime) return false; + + const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + const answerTimeInMinutes = convertToMinutes(answerTime); + + return answerTimeInMinutes === backendTimeInMinutes; }, isNotEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) !== timeToMinutes(time) - : false; + if (!answerTime) return false; + + const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + const answerTimeInMinutes = convertToMinutes(answerTime); + + return answerTimeInMinutes !== backendTimeInMinutes; }, isBetweenTimes( minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number } ) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) >= timeToMinutes(minTime) && - timeToMinutes(answerTime) <= timeToMinutes(maxTime) - : false; + if (!answerTime) return false; + + const minTimeInMinutes = convertToMinutes(convert24HourTo12Hour(minTime.hours, minTime.minutes)); + const maxTimeInMinutes = convertToMinutes(convert24HourTo12Hour(maxTime.hours, maxTime.minutes)); + const answerTimeInMinutes = convertToMinutes(answerTime); + + return answerTimeInMinutes >= minTimeInMinutes && answerTimeInMinutes <= maxTimeInMinutes; }, isOutsideOfTimes( minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number }, + maxTime: { hours: number; minutes: number } ) { const answerTime = getAnswerTime(); - return answerTime - ? timeToMinutes(answerTime) < timeToMinutes(minTime) || - timeToMinutes(answerTime) > timeToMinutes(maxTime) - : false; + if (!answerTime) return false; + + const minTimeInMinutes = convertToMinutes(convert24HourTo12Hour(minTime.hours, minTime.minutes)); + const maxTimeInMinutes = convertToMinutes(convert24HourTo12Hour(maxTime.hours, maxTime.minutes)); + const answerTimeInMinutes = convertToMinutes(answerTime); + + return answerTimeInMinutes < minTimeInMinutes || answerTimeInMinutes > maxTimeInMinutes; }, - isGreaterThanTimeRange(time: { hours: number; minutes: number }) { + isGreaterThanTimeRange(time: { hours: number; minutes: number, fieldName?: 'from' | 'to' }) { + if (!time || typeof time.hours !== 'number' || typeof time.minutes !== 'number') { + return false; + } + const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + + if (!answerTimeRange || (!answerTimeRange.startTime && !answerTimeRange.endTime)) { + return false; + } + + const fieldName = time.fieldName || 'from'; + const answerTime = fieldName === 'from' ? answerTimeRange.startTime : answerTimeRange.endTime; + + if (!answerTime) { return false; } - return timeToMinutes(answerTimeRange.startTime) > timeToMinutes(time); + + const normalizedConditionTime = timeToMinutes(time); + const normalizedAnswerTime = timeToMinutes(answerTime); + + return normalizedAnswerTime > normalizedConditionTime; }, isLessThanTimeRange(time: { hours: number; minutes: number }) { diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 33fc6f149..19984cc70 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -6,6 +6,15 @@ export function PipelineVisibilityChecker( pipeline: PipelineItem[], answers: Answers, ) { + + function parseTimeString(timeStr: string) { + const [hours, minutes] = timeStr.split(':').map(Number); + return { hours, minutes }; + } + + function isValidTimeFormat(time: any): boolean { + return time && typeof time.hours === 'number' && typeof time.minutes === 'number'; + } function isItemVisible(index: number) { if (index >= pipeline.length) { return false; @@ -95,8 +104,21 @@ export function PipelineVisibilityChecker( condition.payload.maxDate, ); - case 'GREATER_THAN_TIME': - return answerValidator.isGreaterThanTime(condition.payload.time); + case 'GREATER_THAN_TIME': { + let conditionTime = condition.payload?.time; + + if (typeof conditionTime === 'string') { + conditionTime = parseTimeString(conditionTime); + } + + if (!isValidTimeFormat(conditionTime)) { + return false; + } + + const isGreater = answerValidator.isGreaterThanTime(conditionTime); + + return isGreater; + } case 'LESS_THAN_TIME': return answerValidator.isLessThanTime(condition.payload.time); diff --git a/src/shared/ui/DateTimePicker.tsx b/src/shared/ui/DateTimePicker.tsx index 175c2477b..af8f1d9d7 100644 --- a/src/shared/ui/DateTimePicker.tsx +++ b/src/shared/ui/DateTimePicker.tsx @@ -70,12 +70,15 @@ export const DateTimePicker: FC = ({ + isVisible={isDatePickerVisible} + date={value ? new Date(value) : new Date()} + mode={mode} + onConfirm={date => { + const utcDate = new Date(date.toISOString()); + confirm(utcDate); + }} + onCancel={hideDatePicker} +/> ); }; From 6f6b2476def5bb6ff9905d1acfa3264350c899b1 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 20:47:13 -0700 Subject: [PATCH 06/18] lint fix --- .../activity/lib/services/timeToMinutes.ts | 9 +- .../conditional-logic/model/conditions.ts | 6 +- .../pass-survey/model/AnswerValidator.ts | 146 ++++++++++++------ .../model/PipelineVisibilityChecker.ts | 33 ++-- src/shared/ui/DateTimePicker.tsx | 18 +-- 5 files changed, 134 insertions(+), 78 deletions(-) diff --git a/src/entities/activity/lib/services/timeToMinutes.ts b/src/entities/activity/lib/services/timeToMinutes.ts index b8e758d18..96df31d99 100644 --- a/src/entities/activity/lib/services/timeToMinutes.ts +++ b/src/entities/activity/lib/services/timeToMinutes.ts @@ -1,3 +1,6 @@ -export const timeToMinutes = (time: { hours: number; minutes: number }): number => { - return time.hours * 60 + time.minutes; - }; \ No newline at end of file +export const timeToMinutes = (time: { + hours: number; + minutes: number; +}): number => { + return time.hours * 60 + time.minutes; +}; diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index d7633b682..66750b22b 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -1,4 +1,4 @@ -import { timeToMinutes } from "@app/entities/activity/lib/services/timeToMinutes"; +import { timeToMinutes } from '@app/entities/activity/lib/services/timeToMinutes'; export const isBetweenValues = ( input: Maybe, @@ -119,7 +119,7 @@ export const isOutsideOfDates = ( export const isGreaterThanTime = ( input: Maybe<{ hours: number; minutes: number }>, - time: { hours: number; minutes: number } + time: { hours: number; minutes: number }, ): boolean => { if (!input) { return false; @@ -129,7 +129,7 @@ export const isGreaterThanTime = ( const conditionMinutes = timeToMinutes(time); const result = inputMinutes > conditionMinutes; - + return result; }; diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 5028347c5..ebdbe94aa 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -35,30 +35,42 @@ export function AnswerValidator( }; function isValidTimeFormat(time: any): boolean { - return time && typeof time.hours === 'number' && typeof time.minutes === 'number'; + return ( + time && typeof time.hours === 'number' && typeof time.minutes === 'number' + ); } const getAnswerTime = (): { hours: number; minutes: number } | null => { - let answer = currentAnswer?.answer as Maybe<{ hours: number; minutes: number }>; - + let answer = currentAnswer?.answer as Maybe<{ + hours: number; + minutes: number; + }>; + if (typeof answer === 'string') { answer = convertTo24HourFormat(answer); } - + return answer ?? null; }; - function convert24HourTo12Hour(hours: number, minutes: number): { hours: number, minutes: number, period: 'AM' | 'PM' } { + function convert24HourTo12Hour( + hours: number, + minutes: number, + ): { hours: number; minutes: number; period: 'AM' | 'PM' } { const period = hours >= 12 ? 'PM' : 'AM'; const adjustedHours = hours % 12 || 12; // Converts 0 to 12 for midnight and uses 12-hour format return { hours: adjustedHours, minutes, period }; } - function convertToMinutes(time: { hours: number, minutes: number, period?: 'AM' | 'PM' }): number { + function convertToMinutes(time: { + hours: number; + minutes: number; + period?: 'AM' | 'PM'; + }): number { let totalMinutes = time.hours * 60 + time.minutes; if (time.period === 'PM' && time.hours !== 12) { - totalMinutes += 12 * 60; + totalMinutes += 12 * 60; } else if (time.period === 'AM' && time.hours === 12) { - totalMinutes -= 12 * 60; + totalMinutes -= 12 * 60; } return totalMinutes; } @@ -71,17 +83,19 @@ export function AnswerValidator( return answer ?? null; }; - const convertTo24HourFormat = (timeStr: string): { hours: number; minutes: number } => { + const convertTo24HourFormat = ( + timeStr: string, + ): { hours: number; minutes: number } => { const [time, modifier] = timeStr.split(' '); let [hours, minutes] = time.split(':').map(Number); - + if (modifier.toLowerCase() === 'pm' && hours < 12) { hours += 12; } if (modifier.toLowerCase() === 'am' && hours === 12) { hours = 0; } - + return { hours, minutes }; }; const getSliderRowValue = (rowIndex: number): number | null => { @@ -259,96 +273,134 @@ export function AnswerValidator( isGreaterThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - + if (!isValidTimeFormat(time)) { return false; } - + const normalizedTime = timeToMinutes(time); - const normalizedAnswerTime = answerTime ? timeToMinutes(answerTime) : null; - - return normalizedAnswerTime !== null && normalizedAnswerTime > normalizedTime; + const normalizedAnswerTime = answerTime + ? timeToMinutes(answerTime) + : null; + + return ( + normalizedAnswerTime !== null && normalizedAnswerTime > normalizedTime + ); }, isLessThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + + const backendTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(time.hours, time.minutes), + ); const answerTimeInMinutes = convertToMinutes(answerTime); - + return answerTimeInMinutes < backendTimeInMinutes; }, isEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + + const backendTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(time.hours, time.minutes), + ); const answerTimeInMinutes = convertToMinutes(answerTime); - + return answerTimeInMinutes === backendTimeInMinutes; }, isNotEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const backendTimeInMinutes = convertToMinutes(convert24HourTo12Hour(time.hours, time.minutes)); + + const backendTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(time.hours, time.minutes), + ); const answerTimeInMinutes = convertToMinutes(answerTime); - + return answerTimeInMinutes !== backendTimeInMinutes; }, isBetweenTimes( minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number } + maxTime: { hours: number; minutes: number }, ) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const minTimeInMinutes = convertToMinutes(convert24HourTo12Hour(minTime.hours, minTime.minutes)); - const maxTimeInMinutes = convertToMinutes(convert24HourTo12Hour(maxTime.hours, maxTime.minutes)); + + const minTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(minTime.hours, minTime.minutes), + ); + const maxTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(maxTime.hours, maxTime.minutes), + ); const answerTimeInMinutes = convertToMinutes(answerTime); - - return answerTimeInMinutes >= minTimeInMinutes && answerTimeInMinutes <= maxTimeInMinutes; + + return ( + answerTimeInMinutes >= minTimeInMinutes && + answerTimeInMinutes <= maxTimeInMinutes + ); }, isOutsideOfTimes( minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number } + maxTime: { hours: number; minutes: number }, ) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const minTimeInMinutes = convertToMinutes(convert24HourTo12Hour(minTime.hours, minTime.minutes)); - const maxTimeInMinutes = convertToMinutes(convert24HourTo12Hour(maxTime.hours, maxTime.minutes)); + + const minTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(minTime.hours, minTime.minutes), + ); + const maxTimeInMinutes = convertToMinutes( + convert24HourTo12Hour(maxTime.hours, maxTime.minutes), + ); const answerTimeInMinutes = convertToMinutes(answerTime); - - return answerTimeInMinutes < minTimeInMinutes || answerTimeInMinutes > maxTimeInMinutes; + + return ( + answerTimeInMinutes < minTimeInMinutes || + answerTimeInMinutes > maxTimeInMinutes + ); }, - isGreaterThanTimeRange(time: { hours: number; minutes: number, fieldName?: 'from' | 'to' }) { - if (!time || typeof time.hours !== 'number' || typeof time.minutes !== 'number') { + isGreaterThanTimeRange(time: { + hours: number; + minutes: number; + fieldName?: 'from' | 'to'; + }) { + if ( + !time || + typeof time.hours !== 'number' || + typeof time.minutes !== 'number' + ) { return false; } - + const answerTimeRange = getAnswerTimeRange(); - - if (!answerTimeRange || (!answerTimeRange.startTime && !answerTimeRange.endTime)) { + + if ( + !answerTimeRange || + (!answerTimeRange.startTime && !answerTimeRange.endTime) + ) { return false; } - + const fieldName = time.fieldName || 'from'; - const answerTime = fieldName === 'from' ? answerTimeRange.startTime : answerTimeRange.endTime; - + const answerTime = + fieldName === 'from' + ? answerTimeRange.startTime + : answerTimeRange.endTime; + if (!answerTime) { return false; } - + const normalizedConditionTime = timeToMinutes(time); const normalizedAnswerTime = timeToMinutes(answerTime); - + return normalizedAnswerTime > normalizedConditionTime; }, diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 19984cc70..2f39975a4 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -6,14 +6,15 @@ export function PipelineVisibilityChecker( pipeline: PipelineItem[], answers: Answers, ) { - function parseTimeString(timeStr: string) { const [hours, minutes] = timeStr.split(':').map(Number); return { hours, minutes }; } function isValidTimeFormat(time: any): boolean { - return time && typeof time.hours === 'number' && typeof time.minutes === 'number'; + return ( + time && typeof time.hours === 'number' && typeof time.minutes === 'number' + ); } function isItemVisible(index: number) { if (index >= pipeline.length) { @@ -104,22 +105,22 @@ export function PipelineVisibilityChecker( condition.payload.maxDate, ); - case 'GREATER_THAN_TIME': { - let conditionTime = condition.payload?.time; - - if (typeof conditionTime === 'string') { - conditionTime = parseTimeString(conditionTime); - } - - if (!isValidTimeFormat(conditionTime)) { - return false; - } - - const isGreater = answerValidator.isGreaterThanTime(conditionTime); - - return isGreater; + case 'GREATER_THAN_TIME': { + let conditionTime = condition.payload?.time; + + if (typeof conditionTime === 'string') { + conditionTime = parseTimeString(conditionTime); + } + + if (!isValidTimeFormat(conditionTime)) { + return false; } + const isGreater = answerValidator.isGreaterThanTime(conditionTime); + + return isGreater; + } + case 'LESS_THAN_TIME': return answerValidator.isLessThanTime(condition.payload.time); diff --git a/src/shared/ui/DateTimePicker.tsx b/src/shared/ui/DateTimePicker.tsx index af8f1d9d7..b7cd25651 100644 --- a/src/shared/ui/DateTimePicker.tsx +++ b/src/shared/ui/DateTimePicker.tsx @@ -70,15 +70,15 @@ export const DateTimePicker: FC = ({ { - const utcDate = new Date(date.toISOString()); - confirm(utcDate); - }} - onCancel={hideDatePicker} -/> + isVisible={isDatePickerVisible} + date={value ? new Date(value) : new Date()} + mode={mode} + onConfirm={date => { + const utcDate = new Date(date.toISOString()); + confirm(utcDate); + }} + onCancel={hideDatePicker} + /> ); }; From e8ffcd94bf33c63c58c7442198f1de50204e7be7 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 20:57:29 -0700 Subject: [PATCH 07/18] turning minutes const to fix lint --- src/features/pass-survey/model/AnswerValidator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index ebdbe94aa..e9411d2e4 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -87,15 +87,15 @@ export function AnswerValidator( timeStr: string, ): { hours: number; minutes: number } => { const [time, modifier] = timeStr.split(' '); - let [hours, minutes] = time.split(':').map(Number); - + // let [hours, minutes] = time.split(':').map(Number); + let [hours, const minutes] = time.split(':').map(Number); if (modifier.toLowerCase() === 'pm' && hours < 12) { hours += 12; } if (modifier.toLowerCase() === 'am' && hours === 12) { hours = 0; } - + return { hours, minutes }; }; const getSliderRowValue = (rowIndex: number): number | null => { From 7599d9aa4000d7279a6c96338e25b1fb3776deee Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 21:05:05 -0700 Subject: [PATCH 08/18] fixing lint, turning minute into cons --- src/features/pass-survey/model/AnswerValidator.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index e9411d2e4..822edb0e6 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -87,17 +87,20 @@ export function AnswerValidator( timeStr: string, ): { hours: number; minutes: number } => { const [time, modifier] = timeStr.split(' '); - // let [hours, minutes] = time.split(':').map(Number); - let [hours, const minutes] = time.split(':').map(Number); + let [hours, tempMinutes] = time.split(':').map(Number); + + const minutes = tempMinutes; + if (modifier.toLowerCase() === 'pm' && hours < 12) { hours += 12; } if (modifier.toLowerCase() === 'am' && hours === 12) { hours = 0; } - + return { hours, minutes }; }; + const getSliderRowValue = (rowIndex: number): number | null => { const answer = currentAnswer?.answer as Maybe; return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; From 865274c46fa29bb701c03fc1d3354dc751d96619 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 21:08:31 -0700 Subject: [PATCH 09/18] fixing lint desestructuring hour and minute --- .../pass-survey/model/AnswerValidator.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 822edb0e6..6e3aa37ac 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -87,18 +87,19 @@ export function AnswerValidator( timeStr: string, ): { hours: number; minutes: number } => { const [time, modifier] = timeStr.split(' '); - let [hours, tempMinutes] = time.split(':').map(Number); - - const minutes = tempMinutes; - + const [hoursStr, minutesStr] = time.split(':'); + const hours = parseInt(hoursStr, 10); + const minutes = parseInt(minutesStr, 10); + + let adjustedHours = hours; if (modifier.toLowerCase() === 'pm' && hours < 12) { - hours += 12; + adjustedHours += 12; } if (modifier.toLowerCase() === 'am' && hours === 12) { - hours = 0; + adjustedHours = 0; } - - return { hours, minutes }; + + return { hours: adjustedHours, minutes }; }; const getSliderRowValue = (rowIndex: number): number | null => { From 099877d1907e70f4d34c0c9a73aa2ca7c44a6ad0 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Wed, 30 Oct 2024 21:09:15 -0700 Subject: [PATCH 10/18] lint fix --- src/features/pass-survey/model/AnswerValidator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 6e3aa37ac..601dbc284 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -90,7 +90,7 @@ export function AnswerValidator( const [hoursStr, minutesStr] = time.split(':'); const hours = parseInt(hoursStr, 10); const minutes = parseInt(minutesStr, 10); - + let adjustedHours = hours; if (modifier.toLowerCase() === 'pm' && hours < 12) { adjustedHours += 12; @@ -98,7 +98,7 @@ export function AnswerValidator( if (modifier.toLowerCase() === 'am' && hours === 12) { adjustedHours = 0; } - + return { hours: adjustedHours, minutes }; }; From cc4ca1f38201a5cfaff081d89cfad88c046405cb Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Thu, 7 Nov 2024 23:14:01 -0800 Subject: [PATCH 11/18] fixing multiple row option --- ios/Podfile.lock | 2 +- src/entities/activity/model/mappers.ts | 8 +++++ .../pass-survey/model/AnswerValidator.ts | 34 +++++++++---------- .../model/PipelineVisibilityChecker.ts | 10 ++---- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 14dffba12..aef35349a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1751,4 +1751,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d42abdc8fa507a4e3e735244c7602178c3fea946 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 2f114103c..8a9009f21 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -79,6 +79,14 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { }; break; + case 'INCLUDES_ROW_OPTION': + case 'NOT_INCLUDES_ROW_OPTION': + updatedCondition.payload = { + optionValue: condition.payload.optionValue, + rowIndex: condition.payload.rowIndex, + }; + break; + case 'GREATER_THAN_DATE': case 'LESS_THAN_DATE': case 'EQUAL_TO_DATE': diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index 601dbc284..b6432db5f 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -57,7 +57,7 @@ export function AnswerValidator( minutes: number, ): { hours: number; minutes: number; period: 'AM' | 'PM' } { const period = hours >= 12 ? 'PM' : 'AM'; - const adjustedHours = hours % 12 || 12; // Converts 0 to 12 for midnight and uses 12-hour format + const adjustedHours = hours % 12 || 12; return { hours: adjustedHours, minutes, period }; } @@ -107,9 +107,13 @@ export function AnswerValidator( return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; }; - const getRowOptionValue = (rowIndex: number): string | null => { - const answer = currentAnswer?.answer as Maybe; - return answer && answer[rowIndex] !== undefined ? answer[rowIndex] : null; + const getRowOptionValues = (rowIndex: number): string[] | null => { + const answer = currentAnswer?.answer as Maybe<{ id: string; text: string }[][]>; + if (answer && answer[rowIndex]) { + const optionIds = answer[rowIndex].map(option => option.id); + return optionIds; + } + return null; }; const timeToMinutes = (time: { hours: number; minutes: number }): number => { @@ -164,30 +168,24 @@ export function AnswerValidator( return rowValue !== null && (rowValue < minValue || rowValue > maxValue); }, - // Methods for options per row isEqualToRowOption(rowIndex: number, optionValue: string) { - const selectedOption = getRowOptionValue(rowIndex); - return selectedOption !== null && selectedOption === optionValue; + const selectedOption = getRowOptionValues(rowIndex); + return selectedOption !== null && selectedOption.includes(optionValue); }, isNotEqualToRowOption(rowIndex: number, optionValue: string) { - const selectedOption = getRowOptionValue(rowIndex); - return selectedOption !== null && selectedOption !== optionValue; + const selectedOption = getRowOptionValues(rowIndex); + return selectedOption !== null && !selectedOption.includes(optionValue); }, includesRowOption(rowIndex: number, optionValue: string) { - const selectedOption = getRowOptionValue(rowIndex); - return ( - selectedOption !== null && includesValue([selectedOption], optionValue) - ); + const selectedOptions = getRowOptionValues(Number(rowIndex)); + return selectedOptions !== null && includesValue(selectedOptions, optionValue); }, notIncludesRowOption(rowIndex: number, optionValue: string) { - const selectedOption = getRowOptionValue(rowIndex); - return ( - selectedOption !== null && - doesNotIncludeValue([selectedOption], optionValue) - ); + const selectedOptions = getRowOptionValues(Number(rowIndex)); + return selectedOptions !== null && doesNotIncludeValue(selectedOptions, optionValue); }, isBetweenValues(min: number, max: number) { diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 2f39975a4..2ef9341c4 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -217,16 +217,10 @@ export function PipelineVisibilityChecker( ); case 'INCLUDES_ROW_OPTION': - return answerValidator.includesRowOption( - condition.payload.rowIndex, - condition.payload.optionValue, - ); + return answerValidator.includesRowOption(condition.payload.rowIndex, condition.payload.optionValue); case 'NOT_INCLUDES_ROW_OPTION': - return answerValidator.notIncludesRowOption( - condition.payload.rowIndex, - condition.payload.optionValue, - ); + return answerValidator.notIncludesRowOption(condition.payload.rowIndex, condition.payload.optionValue); default: return true; From dea68fc5359ac35aa32225d27ee1faf4624aa129 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Thu, 7 Nov 2024 23:20:39 -0800 Subject: [PATCH 12/18] reverting Podfile.lock --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aef35349a..14dffba12 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1751,4 +1751,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d42abdc8fa507a4e3e735244c7602178c3fea946 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 From c862598e269caf0b22a21327116e8beda1f999d4 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 11 Nov 2024 01:31:18 -0800 Subject: [PATCH 13/18] feature/including time range logic, improving IanswerValidator and answerValidator, adding fieldValue to time range and improving payloads --- .../activity/lib/types/conditionalLogic.ts | 6 + src/entities/activity/model/mappers.ts | 13 +- .../conditional-logic/model/conditions.ts | 196 ++++++++++--- .../pass-survey/model/AnswerValidator.ts | 271 ++++++++---------- .../pass-survey/model/IAnswerValidator.ts | 22 +- .../model/PipelineVisibilityChecker.ts | 32 ++- src/shared/api/services/ActivityItemDto.ts | 6 + 7 files changed, 343 insertions(+), 203 deletions(-) diff --git a/src/entities/activity/lib/types/conditionalLogic.ts b/src/entities/activity/lib/types/conditionalLogic.ts index bd2816a73..4595359b4 100644 --- a/src/entities/activity/lib/types/conditionalLogic.ts +++ b/src/entities/activity/lib/types/conditionalLogic.ts @@ -353,6 +353,7 @@ type GreaterThanTimeRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -364,6 +365,7 @@ type LessThanTimeRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -375,6 +377,7 @@ type EqualToTimeRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -386,6 +389,7 @@ type NotEqualToTimeRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -401,6 +405,7 @@ type BetweenTimesRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -416,5 +421,6 @@ type OutsideOfTimesRangeCondition = { hours: number; minutes: number; }; + fieldName: string; }; }; diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 8a9009f21..56962a2e9 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -81,10 +81,10 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { case 'INCLUDES_ROW_OPTION': case 'NOT_INCLUDES_ROW_OPTION': - updatedCondition.payload = { - optionValue: condition.payload.optionValue, - rowIndex: condition.payload.rowIndex, - }; + updatedCondition.payload = { + optionValue: condition.payload.optionValue, + rowIndex: condition.payload.rowIndex, + }; break; case 'GREATER_THAN_DATE': @@ -121,7 +121,10 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { case 'LESS_THAN_TIME_RANGE': case 'EQUAL_TO_TIME_RANGE': case 'NOT_EQUAL_TO_TIME_RANGE': - updatedCondition.payload = { time: condition.payload.time }; + updatedCondition.payload = { + time: condition.payload.time, + fieldName: condition.payload.fieldName, + }; break; case 'BETWEEN_TIMES_RANGE': diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 66750b22b..059c84c18 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -1,4 +1,7 @@ +import { isAfter, isBefore, isEqual, parseISO } from 'date-fns'; + import { timeToMinutes } from '@app/entities/activity/lib/services/timeToMinutes'; +import { HourMinute } from '@app/shared/lib/types/dateTime'; export const isBetweenValues = ( input: Maybe, @@ -24,7 +27,6 @@ export const isOutsideOfValues = ( return input < min || input > max; }; -// and isEqualToOption export const isEqualToValue = ( input: unknown, valueToCompareWith: NonNullable, @@ -77,24 +79,17 @@ export const doesNotIncludeValue = ( return !input.includes(value); }; -export const isGreaterThanDate = (input: Maybe, date: string) => { - if (!input) return false; - return new Date(input) > new Date(date); +export const isGreaterThanDate = (input: string, date: string) => { + return input ? isAfter(parseISO(input), parseISO(date)) : false; }; -export const isLessThanDate = (input: Maybe, date: string) => { - if (!input) return false; - return new Date(input) < new Date(date); +export const isLessThanDate = (input: string, date: string) => { + return input ? isBefore(parseISO(input), parseISO(date)) : false; }; export const isEqualToDate = (input: Maybe, date: string) => { if (!input) return false; - return new Date(input).getTime() === new Date(date).getTime(); -}; - -export const isNotEqualToDate = (input: Maybe, date: string) => { - if (!input) return false; - return new Date(input).getTime() !== new Date(date).getTime(); + return input ? isEqual(parseISO(input), parseISO(date)) : false; }; export const isBetweenDates = ( @@ -102,9 +97,14 @@ export const isBetweenDates = ( minDate: string, maxDate: string, ) => { - if (!input) return false; - const inputDate = new Date(input); - return inputDate > new Date(minDate) && inputDate < new Date(maxDate); + return input + ? !isBefore(parseISO(input), parseISO(minDate)) && + !isAfter(parseISO(input), parseISO(maxDate)) + : false; +}; + +export const isNotEqualToDate = (input: Maybe, date: string) => { + return input ? !isEqual(parseISO(input), parseISO(date)) : false; }; export const isOutsideOfDates = ( @@ -112,19 +112,103 @@ export const isOutsideOfDates = ( minDate: string, maxDate: string, ) => { - if (!input) return false; - const inputDate = new Date(input); - return inputDate < new Date(minDate) || inputDate > new Date(maxDate); + return input + ? isBefore(parseISO(input), parseISO(minDate)) || + isAfter(parseISO(input), parseISO(maxDate)) + : false; +}; + +const getTimeBasedOnFieldName = ( + fieldName: string, + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, +): HourMinute | null => { + return fieldName === 'from' ? timeRange.startTime : timeRange.endTime; +}; + +export const isGreaterThanTimeRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { time, fieldName }: { time: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + return selectedTime + ? timeToMinutes(selectedTime) > timeToMinutes(time) + : false; +}; + +export const isLessThanTimeRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { time, fieldName }: { time: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + return selectedTime + ? timeToMinutes(selectedTime) < timeToMinutes(time) + : false; +}; + +export const isEqualToTimeRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { time, fieldName }: { time: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + return selectedTime + ? timeToMinutes(selectedTime) == timeToMinutes(time) + : false; +}; + +export const isNotEqualToTimeRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { time, fieldName }: { time: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + return selectedTime + ? timeToMinutes(selectedTime) !== timeToMinutes(time) + : false; +}; + +export const isBetweenTimesRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { + minTime, + maxTime, + fieldName, + }: { minTime: HourMinute; maxTime: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const selectedTimeInMinutes = selectedTime + ? timeToMinutes(selectedTime) + : null; + + return ( + selectedTimeInMinutes !== null && + selectedTimeInMinutes >= timeToMinutes(minTime) && + selectedTimeInMinutes <= timeToMinutes(maxTime) + ); +}; + +export const isOutsideOfTimesRange = ( + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + { + minTime, + maxTime, + fieldName, + }: { minTime: HourMinute; maxTime: HourMinute; fieldName: string }, +): boolean => { + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const selectedTimeInMinutes = selectedTime + ? timeToMinutes(selectedTime) + : null; + + return ( + selectedTimeInMinutes !== null && + (selectedTimeInMinutes < timeToMinutes(minTime) || + selectedTimeInMinutes > timeToMinutes(maxTime)) + ); }; export const isGreaterThanTime = ( - input: Maybe<{ hours: number; minutes: number }>, + input: { hours: number; minutes: number }, time: { hours: number; minutes: number }, ): boolean => { - if (!input) { - return false; - } - const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); @@ -134,30 +218,39 @@ export const isGreaterThanTime = ( }; export const isLessThanTime = ( - input: Maybe<{ hours: number; minutes: number }>, + input: { hours: number; minutes: number }, time: { hours: number; minutes: number }, ) => { - if (!input) return false; - return ( - input.hours < time.hours || - (input.hours === time.hours && input.minutes < time.minutes) - ); + const inputMinutes = timeToMinutes(input); + const conditionMinutes = timeToMinutes(time); + + const result = inputMinutes < conditionMinutes; + + return result; }; export const isEqualToTime = ( - input: Maybe<{ hours: number; minutes: number }>, + input: { hours: number; minutes: number }, time: { hours: number; minutes: number }, ) => { - if (!input) return false; - return input.hours === time.hours && input.minutes === time.minutes; + const inputMinutes = timeToMinutes(input); + const conditionMinutes = timeToMinutes(time); + + const result = inputMinutes == conditionMinutes; + + return result; }; export const isNotEqualToTime = ( - input: Maybe<{ hours: number; minutes: number }>, + input: { hours: number; minutes: number }, time: { hours: number; minutes: number }, ) => { - if (!input) return false; - return input.hours !== time.hours || input.minutes !== time.minutes; + const inputMinutes = timeToMinutes(input); + const conditionMinutes = timeToMinutes(time); + + const result = inputMinutes !== conditionMinutes; + + return result; }; export const isBetweenTimes = ( @@ -177,3 +270,34 @@ export const isOutsideOfTimes = ( if (!input) return false; return isLessThanTime(input, minTime) || isGreaterThanTime(input, maxTime); }; + +export const isOutsideOfSliderRowValues = ( + rowValue: number, + minValue: number, + maxValue: number, +) => { + return rowValue !== null && (rowValue < minValue || rowValue > maxValue); +}; + +export const isBetweenSliderRowValues = ( + rowValue: number, + minValue: number, + maxValue: number, +) => { + return rowValue !== null && rowValue >= minValue && rowValue <= maxValue; +}; + +export const isLessThanSliderRow = (rowValue: number, value: number) => { + return rowValue !== null && rowValue < value; +}; + +export const isGreaterThanSliderRow = (rowValue: number, value: number) => { + return rowValue !== null && rowValue > value; +}; +export const isNotEqualToSliderRow = (rowValue: number, value: number) => { + return rowValue !== null && rowValue !== value; +}; + +export const isEqualToSliderRow = (rowValue: number, value: number) => { + return rowValue !== null && rowValue === value; +}; diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index b6432db5f..d661f58d0 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -1,6 +1,3 @@ -import { isBefore, isAfter, isEqual } from 'date-fns'; -import { parseISO } from 'date-fns'; - import { doesNotIncludeValue, includesValue, @@ -9,7 +6,32 @@ import { isGreaterThan, isLessThan, isOutsideOfValues, + isGreaterThanTime, + isLessThanTime, + isEqualToTime, + isNotEqualToTime, + isNotEqualToDate, + isBetweenDates, + isBetweenTimes, + isGreaterThanTimeRange, + isLessThanTimeRange, + isEqualToTimeRange, + isNotEqualToTimeRange, + isBetweenTimesRange, + isOutsideOfTimesRange, + isOutsideOfTimes, + isOutsideOfSliderRowValues, + isBetweenSliderRowValues, + isLessThanSliderRow, + isGreaterThanSliderRow, + isNotEqualToSliderRow, + isEqualToSliderRow, + isGreaterThanDate, + isLessThanDate, + isEqualToDate, + isOutsideOfDates, } from '@app/entities/conditional-logic/model/conditions'; +import { HourMinute } from '@app/shared/lib/types/dateTime'; import { Item } from '@app/shared/ui/survey/CheckBox/types'; import { AnswerValidatorArgs, @@ -29,6 +51,13 @@ export function AnswerValidator( const currentPipelineItem = items?.[step]; const currentAnswer = answers?.[step]; + const getTimeBasedOnFieldName = ( + fieldName: string, + timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + ): HourMinute | null => { + return fieldName === 'from' ? timeRange.startTime : timeRange.endTime; + }; + const getAnswerDate = (): string | null => { const answer = currentAnswer?.answer as Maybe; return answer ?? null; @@ -52,15 +81,6 @@ export function AnswerValidator( return answer ?? null; }; - function convert24HourTo12Hour( - hours: number, - minutes: number, - ): { hours: number; minutes: number; period: 'AM' | 'PM' } { - const period = hours >= 12 ? 'PM' : 'AM'; - const adjustedHours = hours % 12 || 12; - return { hours: adjustedHours, minutes, period }; - } - function convertToMinutes(time: { hours: number; minutes: number; @@ -108,7 +128,9 @@ export function AnswerValidator( }; const getRowOptionValues = (rowIndex: number): string[] | null => { - const answer = currentAnswer?.answer as Maybe<{ id: string; text: string }[][]>; + const answer = currentAnswer?.answer as Maybe< + { id: string; text: string }[][] + >; if (answer && answer[rowIndex]) { const optionIds = answer[rowIndex].map(option => option.id); return optionIds; @@ -132,22 +154,28 @@ export function AnswerValidator( isEqualToSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && rowValue === value; + if (!rowValue) return false; + return isEqualToSliderRow(rowValue, value); }, isNotEqualToSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && rowValue !== value; + if (!rowValue) return false; + + return isNotEqualToSliderRow(rowValue, value); }, isGreaterThanSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && rowValue > value; + if (!rowValue) return false; + + return isGreaterThanSliderRow(rowValue, value); }, isLessThanSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && rowValue < value; + if (!rowValue) return false; + return isLessThanSliderRow(rowValue, value); }, isBetweenSliderRowValues( @@ -156,7 +184,9 @@ export function AnswerValidator( maxValue: number, ) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && rowValue >= minValue && rowValue <= maxValue; + if (!rowValue) return false; + + return isBetweenSliderRowValues(rowValue, minValue, maxValue); }, isOutsideOfSliderRowValues( @@ -165,7 +195,10 @@ export function AnswerValidator( maxValue: number, ) { const rowValue = getSliderRowValue(rowIndex); - return rowValue !== null && (rowValue < minValue || rowValue > maxValue); + + if (!rowValue) return false; + + return isOutsideOfSliderRowValues(rowValue, minValue, maxValue); }, isEqualToRowOption(rowIndex: number, optionValue: string) { @@ -180,12 +213,17 @@ export function AnswerValidator( includesRowOption(rowIndex: number, optionValue: string) { const selectedOptions = getRowOptionValues(Number(rowIndex)); - return selectedOptions !== null && includesValue(selectedOptions, optionValue); + return ( + selectedOptions !== null && includesValue(selectedOptions, optionValue) + ); }, notIncludesRowOption(rowIndex: number, optionValue: string) { const selectedOptions = getRowOptionValues(Number(rowIndex)); - return selectedOptions !== null && doesNotIncludeValue(selectedOptions, optionValue); + return ( + selectedOptions !== null && + doesNotIncludeValue(selectedOptions, optionValue) + ); }, isBetweenValues(min: number, max: number) { @@ -235,95 +273,73 @@ export function AnswerValidator( isGreaterThanDate(date: string) { const answerDate = getAnswerDate(); - return answerDate ? isAfter(parseISO(answerDate), parseISO(date)) : false; + if (!answerDate) return false; + return isGreaterThanDate(answerDate, date); }, isLessThanDate(date: string) { const answerDate = getAnswerDate(); - return answerDate - ? isBefore(parseISO(answerDate), parseISO(date)) - : false; + if (!answerDate) return false; + return isLessThanDate(answerDate, date); }, isEqualToDate(date: string) { const answerDate = getAnswerDate(); - return answerDate ? isEqual(parseISO(answerDate), parseISO(date)) : false; + if (!answerDate) return false; + return isEqualToDate(answerDate, date); }, isNotEqualToDate(date: string) { const answerDate = getAnswerDate(); - return answerDate - ? !isEqual(parseISO(answerDate), parseISO(date)) - : false; + if (!answerDate) return false; + return isNotEqualToDate(answerDate, date); }, isBetweenDates(minDate: string, maxDate: string) { const answerDate = getAnswerDate(); - return answerDate - ? !isBefore(parseISO(answerDate), parseISO(minDate)) && - !isAfter(parseISO(answerDate), parseISO(maxDate)) - : false; + if (!answerDate) return false; + return isBetweenDates(answerDate, minDate, maxDate); }, isOutsideOfDates(minDate: string, maxDate: string) { const answerDate = getAnswerDate(); - return answerDate - ? isBefore(parseISO(answerDate), parseISO(minDate)) || - isAfter(parseISO(answerDate), parseISO(maxDate)) - : false; + return isOutsideOfDates(answerDate, minDate, maxDate); }, isGreaterThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!isValidTimeFormat(time)) { + if (!isValidTimeFormat(time) || !answerTime) { return false; } - - const normalizedTime = timeToMinutes(time); - const normalizedAnswerTime = answerTime - ? timeToMinutes(answerTime) - : null; - - return ( - normalizedAnswerTime !== null && normalizedAnswerTime > normalizedTime - ); + return isGreaterThanTime(answerTime, time); }, isLessThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!answerTime) return false; - const backendTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(time.hours, time.minutes), - ); - const answerTimeInMinutes = convertToMinutes(answerTime); - - return answerTimeInMinutes < backendTimeInMinutes; + if (!isValidTimeFormat(time) || !answerTime) { + return false; + } + return isLessThanTime(answerTime, time); }, isEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!answerTime) return false; - - const backendTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(time.hours, time.minutes), - ); - const answerTimeInMinutes = convertToMinutes(answerTime); - return answerTimeInMinutes === backendTimeInMinutes; + if (!isValidTimeFormat(time) || !answerTime) { + return false; + } + return isEqualToTime(answerTime, time); }, isNotEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!answerTime) return false; - - const backendTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(time.hours, time.minutes), - ); - const answerTimeInMinutes = convertToMinutes(answerTime); - return answerTimeInMinutes !== backendTimeInMinutes; + if (!isValidTimeFormat(time) || !answerTime) { + return false; + } + return isNotEqualToTime(answerTime, time); }, isBetweenTimes( @@ -332,19 +348,7 @@ export function AnswerValidator( ) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const minTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(minTime.hours, minTime.minutes), - ); - const maxTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(maxTime.hours, maxTime.minutes), - ); - const answerTimeInMinutes = convertToMinutes(answerTime); - - return ( - answerTimeInMinutes >= minTimeInMinutes && - answerTimeInMinutes <= maxTimeInMinutes - ); + return isBetweenTimes(answerTime, minTime, maxTime); }, isOutsideOfTimes( @@ -353,111 +357,74 @@ export function AnswerValidator( ) { const answerTime = getAnswerTime(); if (!answerTime) return false; - - const minTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(minTime.hours, minTime.minutes), - ); - const maxTimeInMinutes = convertToMinutes( - convert24HourTo12Hour(maxTime.hours, maxTime.minutes), - ); - const answerTimeInMinutes = convertToMinutes(answerTime); - - return ( - answerTimeInMinutes < minTimeInMinutes || - answerTimeInMinutes > maxTimeInMinutes - ); + return isOutsideOfTimes(answerTime, minTime, maxTime); }, - isGreaterThanTimeRange(time: { - hours: number; - minutes: number; - fieldName?: 'from' | 'to'; - }) { - if ( - !time || - typeof time.hours !== 'number' || - typeof time.minutes !== 'number' - ) { - return false; - } - + isGreaterThanTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if ( - !answerTimeRange || - (!answerTimeRange.startTime && !answerTimeRange.endTime) - ) { - return false; - } - - const fieldName = time.fieldName || 'from'; - const answerTime = - fieldName === 'from' - ? answerTimeRange.startTime - : answerTimeRange.endTime; - - if (!answerTime) { + if (!answerTimeRange) { return false; } - - const normalizedConditionTime = timeToMinutes(time); - const normalizedAnswerTime = timeToMinutes(answerTime); - - return normalizedAnswerTime > normalizedConditionTime; + return isGreaterThanTimeRange(answerTimeRange, { time, fieldName }); }, - isLessThanTimeRange(time: { hours: number; minutes: number }) { + isEqualToTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + + if (!answerTimeRange) { return false; } - return timeToMinutes(answerTimeRange.startTime) < timeToMinutes(time); + return isEqualToTimeRange(answerTimeRange, { time, fieldName }); }, - isEqualToTimeRange(time: { hours: number; minutes: number }) { + isLessThanTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + if (!answerTimeRange) { return false; } - return timeToMinutes(answerTimeRange.startTime) === timeToMinutes(time); + return isLessThanTimeRange(answerTimeRange, { time, fieldName }); }, - isNotEqualToTimeRange(time: { hours: number; minutes: number }) { + isNotEqualToTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + if (!answerTimeRange) { return false; } - return timeToMinutes(answerTimeRange.startTime) !== timeToMinutes(time); + return isNotEqualToTimeRange(answerTimeRange, { time, fieldName }); }, - isBetweenTimesRange( - minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number }, + minTime: HourMinute, + maxTime: HourMinute, + fieldName: string, ) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + if (!answerTimeRange) { return false; } - const answerMinutes = timeToMinutes(answerTimeRange.startTime); - return ( - answerMinutes >= timeToMinutes(minTime) && - answerMinutes <= timeToMinutes(maxTime) - ); + return isBetweenTimesRange(answerTimeRange, { + minTime, + maxTime, + fieldName, + }); }, isOutsideOfTimesRange( - minTime: { hours: number; minutes: number }, - maxTime: { hours: number; minutes: number }, + minTime: HourMinute, + maxTime: HourMinute, + fieldName: string, ) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange || !answerTimeRange.startTime) { + + if (!answerTimeRange) { return false; } - const answerMinutes = timeToMinutes(answerTimeRange.startTime); - return ( - answerMinutes < timeToMinutes(minTime) || - answerMinutes > timeToMinutes(maxTime) - ); + + return isOutsideOfTimesRange(answerTimeRange, { + minTime, + maxTime, + fieldName, + }); }, isValidAnswer() { diff --git a/src/features/pass-survey/model/IAnswerValidator.ts b/src/features/pass-survey/model/IAnswerValidator.ts index 1f375d223..82d64deb6 100644 --- a/src/features/pass-survey/model/IAnswerValidator.ts +++ b/src/features/pass-survey/model/IAnswerValidator.ts @@ -56,22 +56,36 @@ export interface IAnswerValidator { maxTime: { hours: number; minutes: number }, ): boolean; - isGreaterThanTimeRange(time: { hours: number; minutes: number }): boolean; + isGreaterThanTimeRange( + time: { hours: number; minutes: number }, + fieldName: string, + ): boolean; - isLessThanTimeRange(time: { hours: number; minutes: number }): boolean; + isLessThanTimeRange( + time: { hours: number; minutes: number }, + fieldName: string, + ): boolean; - isEqualToTimeRange(time: { hours: number; minutes: number }): boolean; + isEqualToTimeRange( + time: { hours: number; minutes: number }, + fieldName: string, + ): boolean; - isNotEqualToTimeRange(time: { hours: number; minutes: number }): boolean; + isNotEqualToTimeRange( + time: { hours: number; minutes: number }, + fieldName: string, + ): boolean; isBetweenTimesRange( minTime: { hours: number; minutes: number }, maxTime: { hours: number; minutes: number }, + fieldName: string, ): boolean; isOutsideOfTimesRange( minTime: { hours: number; minutes: number }, maxTime: { hours: number; minutes: number }, + fieldName: string, ): boolean; isEqualToSliderRow(rowIndex: number, value: number): boolean; diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 2ef9341c4..87ef0202f 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -143,27 +143,41 @@ export function PipelineVisibilityChecker( ); case 'GREATER_THAN_TIME_RANGE': - return answerValidator.isGreaterThanTimeRange(condition.payload.time); + return answerValidator.isGreaterThanTimeRange( + condition.payload.time, + condition.payload.fieldName, + ); case 'LESS_THAN_TIME_RANGE': - return answerValidator.isLessThanTimeRange(condition.payload.time); + return answerValidator.isLessThanTimeRange( + condition.payload.time, + condition.payload.fieldName, + ); case 'EQUAL_TO_TIME_RANGE': - return answerValidator.isEqualToTimeRange(condition.payload.time); + return answerValidator.isEqualToTimeRange( + condition.payload.time, + condition.payload.fieldName, + ); case 'NOT_EQUAL_TO_TIME_RANGE': - return answerValidator.isNotEqualToTimeRange(condition.payload.time); + return answerValidator.isNotEqualToTimeRange( + condition.payload.time, + condition.payload.fieldName, + ); case 'BETWEEN_TIMES_RANGE': return answerValidator.isBetweenTimesRange( condition.payload.minTime, condition.payload.maxTime, + condition.payload.fieldName, ); case 'OUTSIDE_OF_TIMES_RANGE': return answerValidator.isOutsideOfTimesRange( condition.payload.minTime, condition.payload.maxTime, + condition.payload.fieldName, ); case 'EQUAL_TO_SLIDER_ROWS': @@ -217,10 +231,16 @@ export function PipelineVisibilityChecker( ); case 'INCLUDES_ROW_OPTION': - return answerValidator.includesRowOption(condition.payload.rowIndex, condition.payload.optionValue); + return answerValidator.includesRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); case 'NOT_INCLUDES_ROW_OPTION': - return answerValidator.notIncludesRowOption(condition.payload.rowIndex, condition.payload.optionValue); + return answerValidator.notIncludesRowOption( + condition.payload.rowIndex, + condition.payload.optionValue, + ); default: return true; diff --git a/src/shared/api/services/ActivityItemDto.ts b/src/shared/api/services/ActivityItemDto.ts index 524681313..22a5147ed 100644 --- a/src/shared/api/services/ActivityItemDto.ts +++ b/src/shared/api/services/ActivityItemDto.ts @@ -380,6 +380,7 @@ type GreaterThanTimeRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -391,6 +392,7 @@ type LessThanTimeRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -402,6 +404,7 @@ type EqualToTimeRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -413,6 +416,7 @@ type NotEqualToTimeRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -428,6 +432,7 @@ type BetweenTimesRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; @@ -443,6 +448,7 @@ type OutsideOfTimesRangeConditionDto = { hours: number; minutes: number; }; + fieldName: string; }; }; From 3509bd1d0c3dbd45aa55171a912c817de4a49a28 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 11 Nov 2024 01:38:53 -0800 Subject: [PATCH 14/18] set delete updatedCondition.itemName back --- src/entities/activity/model/mappers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 56962a2e9..9f5745e43 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -68,7 +68,8 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { ...rest, activityItemName: itemName, }; - + // @ts-expect-error + delete updatedCondition.itemName; switch (condition.type) { case 'INCLUDES_OPTION': case 'NOT_INCLUDES_OPTION': From 9fbf467d85c660ab3ab59650f91699385eba5ca9 Mon Sep 17 00:00:00 2001 From: felipeMetaLab Date: Mon, 18 Nov 2024 04:36:01 -0800 Subject: [PATCH 15/18] Update src/entities/conditional-logic/model/conditions.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Vital --- src/entities/conditional-logic/model/conditions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 059c84c18..ed2511fb2 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -89,7 +89,7 @@ export const isLessThanDate = (input: string, date: string) => { export const isEqualToDate = (input: Maybe, date: string) => { if (!input) return false; - return input ? isEqual(parseISO(input), parseISO(date)) : false; + return isEqual(parseISO(input), parseISO(date)); }; export const isBetweenDates = ( From d5bb2701e4b3b4687efc2cc5206287b7ce795360 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 18 Nov 2024 16:37:00 -0800 Subject: [PATCH 16/18] fix: fixing comments on PR, adding new casses to mapConditionalLogic, fixing order of condition, improving isValidTimeFormat validation, and creating a parse Time string --- .../activity/lib/types/conditionalLogic.ts | 14 ++++---- src/entities/activity/model/mappers.ts | 33 ++++++++++++------ .../conditional-logic/model/conditions.ts | 25 +++++++++++--- .../pass-survey/model/AnswerValidator.ts | 34 ++++--------------- .../model/PipelineVisibilityChecker.ts | 8 +++-- 5 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/entities/activity/lib/types/conditionalLogic.ts b/src/entities/activity/lib/types/conditionalLogic.ts index 4595359b4..8366da184 100644 --- a/src/entities/activity/lib/types/conditionalLogic.ts +++ b/src/entities/activity/lib/types/conditionalLogic.ts @@ -34,16 +34,16 @@ export type Condition = | NotEqualToTimeRangeCondition | BetweenTimesRangeCondition | OutsideOfTimesRangeCondition - | GreaterThanSliderRowCondition - | LessThanSliderRowCondition - | EqualToSliderRowCondition - | NotEqualToSliderRowCondition - | BetweenSliderRowCondition - | OutsideOfSliderRowCondition | EqualToRowOptionCondition | NotEqualToRowOptionCondition | IncludesRowOptionCondition - | NotIncludesRowOptionCondition; + | NotIncludesRowOptionCondition + | EqualToSliderRowCondition + | NotEqualToSliderRowCondition + | GreaterThanSliderRowCondition + | LessThanSliderRowCondition + | BetweenSliderRowCondition + | OutsideOfSliderRowCondition; export type ConditionType = Condition['type']; diff --git a/src/entities/activity/model/mappers.ts b/src/entities/activity/model/mappers.ts index 9f5745e43..63a3ab5ef 100644 --- a/src/entities/activity/model/mappers.ts +++ b/src/entities/activity/model/mappers.ts @@ -68,8 +68,6 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { ...rest, activityItemName: itemName, }; - // @ts-expect-error - delete updatedCondition.itemName; switch (condition.type) { case 'INCLUDES_OPTION': case 'NOT_INCLUDES_OPTION': @@ -80,6 +78,8 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { }; break; + case 'NOT_EQUAL_TO_ROW_OPTION': + case 'EQUAL_TO_ROW_OPTION': case 'INCLUDES_ROW_OPTION': case 'NOT_INCLUDES_ROW_OPTION': updatedCondition.payload = { @@ -88,6 +88,25 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { }; break; + case 'GREATER_THAN_SLIDER_ROWS': + case 'LESS_THAN_SLIDER_ROWS': + case 'NOT_EQUAL_TO_SLIDER_ROWS': + case 'EQUAL_TO_SLIDER_ROWS': + updatedCondition.payload = { + value: condition.payload.value, + rowIndex: condition.payload.rowIndex, + }; + break; + + case 'BETWEEN_SLIDER_ROWS': + case 'OUTSIDE_OF_SLIDER_ROWS': + updatedCondition.payload = { + maxValue: condition.payload.maxValue, + minValue: condition.payload.minValue, + rowIndex: condition.payload.rowIndex, + }; + break; + case 'GREATER_THAN_DATE': case 'LESS_THAN_DATE': case 'EQUAL_TO_DATE': @@ -110,6 +129,8 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { updatedCondition.payload = { time: condition.payload.time }; break; + case 'OUTSIDE_OF_TIMES_RANGE': + case 'BETWEEN_TIMES_RANGE': case 'BETWEEN_TIMES': case 'OUTSIDE_OF_TIMES': updatedCondition.payload = { @@ -128,14 +149,6 @@ function mapConditionalLogic(dto: ConditionalLogicDto | null) { }; break; - case 'BETWEEN_TIMES_RANGE': - case 'OUTSIDE_OF_TIMES_RANGE': - updatedCondition.payload = { - minTime: condition.payload.minTime, - maxTime: condition.payload.maxTime, - }; - break; - default: updatedCondition.payload = condition.payload; } diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 059c84c18..3492f04e3 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -124,14 +124,20 @@ const getTimeBasedOnFieldName = ( ): HourMinute | null => { return fieldName === 'from' ? timeRange.startTime : timeRange.endTime; }; - +const parseTimeString = (time: string): HourMinute => { + const [hours, minutes] = time.split(':').map(Number); + return { hours, minutes }; +}; export const isGreaterThanTimeRange = ( timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const normalizedTime = + typeof time === 'string' ? parseTimeString(time) : time; + return selectedTime - ? timeToMinutes(selectedTime) > timeToMinutes(time) + ? timeToMinutes(selectedTime) > timeToMinutes(normalizedTime) : false; }; @@ -140,8 +146,11 @@ export const isLessThanTimeRange = ( { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const normalizedTime = + typeof time === 'string' ? parseTimeString(time) : time; + return selectedTime - ? timeToMinutes(selectedTime) < timeToMinutes(time) + ? timeToMinutes(selectedTime) < timeToMinutes(normalizedTime) : false; }; @@ -150,8 +159,11 @@ export const isEqualToTimeRange = ( { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const normalizedTime = + typeof time === 'string' ? parseTimeString(time) : time; + return selectedTime - ? timeToMinutes(selectedTime) == timeToMinutes(time) + ? timeToMinutes(selectedTime) == timeToMinutes(normalizedTime) : false; }; @@ -160,8 +172,11 @@ export const isNotEqualToTimeRange = ( { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); + const normalizedTime = + typeof time === 'string' ? parseTimeString(time) : time; + return selectedTime - ? timeToMinutes(selectedTime) !== timeToMinutes(time) + ? timeToMinutes(selectedTime) !== timeToMinutes(normalizedTime) : false; }; diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index d661f58d0..b7738dcc6 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -51,23 +51,21 @@ export function AnswerValidator( const currentPipelineItem = items?.[step]; const currentAnswer = answers?.[step]; - const getTimeBasedOnFieldName = ( - fieldName: string, - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, - ): HourMinute | null => { - return fieldName === 'from' ? timeRange.startTime : timeRange.endTime; - }; - const getAnswerDate = (): string | null => { const answer = currentAnswer?.answer as Maybe; return answer ?? null; }; - function isValidTimeFormat(time: any): boolean { + function isValidTimeFormat( + time: { hours: number; minutes: number } | null | undefined, + ): boolean { return ( - time && typeof time.hours === 'number' && typeof time.minutes === 'number' + !!time && + typeof time.hours === 'number' && + typeof time.minutes === 'number' ); } + const getAnswerTime = (): { hours: number; minutes: number } | null => { let answer = currentAnswer?.answer as Maybe<{ hours: number; @@ -81,20 +79,6 @@ export function AnswerValidator( return answer ?? null; }; - function convertToMinutes(time: { - hours: number; - minutes: number; - period?: 'AM' | 'PM'; - }): number { - let totalMinutes = time.hours * 60 + time.minutes; - if (time.period === 'PM' && time.hours !== 12) { - totalMinutes += 12 * 60; - } else if (time.period === 'AM' && time.hours === 12) { - totalMinutes -= 12 * 60; - } - return totalMinutes; - } - const getAnswerTimeRange = (): { startTime: { hours: number; minutes: number } | null; endTime: { hours: number; minutes: number } | null; @@ -138,10 +122,6 @@ export function AnswerValidator( return null; }; - const timeToMinutes = (time: { hours: number; minutes: number }): number => { - return time.hours * 60 + time.minutes; - }; - return { isCorrect() { if (!currentPipelineItem?.validationOptions) { diff --git a/src/features/pass-survey/model/PipelineVisibilityChecker.ts b/src/features/pass-survey/model/PipelineVisibilityChecker.ts index 87ef0202f..bc841a36b 100644 --- a/src/features/pass-survey/model/PipelineVisibilityChecker.ts +++ b/src/features/pass-survey/model/PipelineVisibilityChecker.ts @@ -11,9 +11,13 @@ export function PipelineVisibilityChecker( return { hours, minutes }; } - function isValidTimeFormat(time: any): boolean { + function isValidTimeFormat( + time: { hours: number; minutes: number } | null | undefined, + ): boolean { return ( - time && typeof time.hours === 'number' && typeof time.minutes === 'number' + !!time && + typeof time.hours === 'number' && + typeof time.minutes === 'number' ); } function isItemVisible(index: number) { From 685be0a64973614dd7c0a5e60617f2f2f675d4e8 Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Mon, 18 Nov 2024 17:36:57 -0800 Subject: [PATCH 17/18] removing input validation from answerValidator and passing to conditions, and fixing unit tests --- .../conditional-logic/model/conditions.ts | 89 +++++++++++++++---- .../pass-survey/model/AnswerValidator.ts | 57 ------------ 2 files changed, 70 insertions(+), 76 deletions(-) diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 64ff55801..70e3ee7ff 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -15,6 +15,13 @@ export const isBetweenValues = ( return input > min && input < max; }; +function isValidTimeFormat( + time: { hours: number; minutes: number } | null | undefined, +): boolean { + return ( + !!time && typeof time.hours === 'number' && typeof time.minutes === 'number' + ); +} export const isOutsideOfValues = ( input: Maybe, min: number, @@ -79,11 +86,12 @@ export const doesNotIncludeValue = ( return !input.includes(value); }; -export const isGreaterThanDate = (input: string, date: string) => { +export const isGreaterThanDate = (input: Maybe, date: string) => { + if (!input) return false; return isAfter(parseISO(input), parseISO(date)); }; - -export const isLessThanDate = (input: string, date: string) => { +export const isLessThanDate = (input: Maybe, date: string) => { + if (!input) return false; return isBefore(parseISO(input), parseISO(date)); }; @@ -97,6 +105,7 @@ export const isBetweenDates = ( minDate: string, maxDate: string, ) => { + if (!input) return false; return input ? !isBefore(parseISO(input), parseISO(minDate)) && !isAfter(parseISO(input), parseISO(maxDate)) @@ -104,6 +113,7 @@ export const isBetweenDates = ( }; export const isNotEqualToDate = (input: Maybe, date: string) => { + if (!input) return false; return !isEqual(parseISO(input), parseISO(date)); }; @@ -112,6 +122,7 @@ export const isOutsideOfDates = ( minDate: string, maxDate: string, ) => { + if (!input) return false; return input ? isBefore(parseISO(input), parseISO(minDate)) || isAfter(parseISO(input), parseISO(maxDate)) @@ -129,9 +140,13 @@ const parseTimeString = (time: string): HourMinute => { return { hours, minutes }; }; export const isGreaterThanTimeRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { + if (!isValidTimeFormat(time) || !timeRange) return false; const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const normalizedTime = typeof time === 'string' ? parseTimeString(time) : time; @@ -142,9 +157,14 @@ export const isGreaterThanTimeRange = ( }; export const isLessThanTimeRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { + if (!isValidTimeFormat(time) || !timeRange) return false; + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const normalizedTime = typeof time === 'string' ? parseTimeString(time) : time; @@ -155,9 +175,14 @@ export const isLessThanTimeRange = ( }; export const isEqualToTimeRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { + if (!isValidTimeFormat(time) || !timeRange) return false; + const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const normalizedTime = typeof time === 'string' ? parseTimeString(time) : time; @@ -168,9 +193,13 @@ export const isEqualToTimeRange = ( }; export const isNotEqualToTimeRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { time, fieldName }: { time: HourMinute; fieldName: string }, ): boolean => { + if (!isValidTimeFormat(time) || !timeRange) return false; const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const normalizedTime = typeof time === 'string' ? parseTimeString(time) : time; @@ -181,13 +210,17 @@ export const isNotEqualToTimeRange = ( }; export const isBetweenTimesRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { minTime, maxTime, fieldName, }: { minTime: HourMinute; maxTime: HourMinute; fieldName: string }, ): boolean => { + if (!timeRange) return false; const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const selectedTimeInMinutes = selectedTime ? timeToMinutes(selectedTime) @@ -201,13 +234,17 @@ export const isBetweenTimesRange = ( }; export const isOutsideOfTimesRange = ( - timeRange: { startTime: HourMinute | null; endTime: HourMinute | null }, + timeRange: Maybe<{ + startTime: HourMinute | null; + endTime: HourMinute | null; + }>, { minTime, maxTime, fieldName, }: { minTime: HourMinute; maxTime: HourMinute; fieldName: string }, ): boolean => { + if (!timeRange) return false; const selectedTime = getTimeBasedOnFieldName(fieldName, timeRange); const selectedTimeInMinutes = selectedTime ? timeToMinutes(selectedTime) @@ -221,9 +258,10 @@ export const isOutsideOfTimesRange = ( }; export const isGreaterThanTime = ( - input: { hours: number; minutes: number }, + input: Maybe<{ hours: number; minutes: number }>, time: { hours: number; minutes: number }, ): boolean => { + if (!isValidTimeFormat(time) || !input) return false; const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); @@ -233,9 +271,11 @@ export const isGreaterThanTime = ( }; export const isLessThanTime = ( - input: { hours: number; minutes: number }, + input: Maybe<{ hours: number; minutes: number }>, time: { hours: number; minutes: number }, ) => { + if (!isValidTimeFormat(time) || !input) return false; + const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); @@ -245,9 +285,11 @@ export const isLessThanTime = ( }; export const isEqualToTime = ( - input: { hours: number; minutes: number }, + input: Maybe<{ hours: number; minutes: number }>, time: { hours: number; minutes: number }, ) => { + if (!isValidTimeFormat(time) || !input) return false; + const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); @@ -257,9 +299,11 @@ export const isEqualToTime = ( }; export const isNotEqualToTime = ( - input: { hours: number; minutes: number }, + input: Maybe<{ hours: number; minutes: number }>, time: { hours: number; minutes: number }, ) => { + if (!isValidTimeFormat(time) || !input) return false; + const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); @@ -274,6 +318,7 @@ export const isBetweenTimes = ( maxTime: { hours: number; minutes: number }, ) => { if (!input) return false; + return isGreaterThanTime(input, minTime) && isLessThanTime(input, maxTime); }; @@ -287,7 +332,7 @@ export const isOutsideOfTimes = ( }; export const isOutsideOfSliderRowValues = ( - rowValue: number, + rowValue: number | null, minValue: number, maxValue: number, ) => { @@ -295,24 +340,30 @@ export const isOutsideOfSliderRowValues = ( }; export const isBetweenSliderRowValues = ( - rowValue: number, + rowValue: number | null, minValue: number, maxValue: number, ) => { return rowValue !== null && rowValue >= minValue && rowValue <= maxValue; }; -export const isLessThanSliderRow = (rowValue: number, value: number) => { +export const isLessThanSliderRow = (rowValue: number | null, value: number) => { return rowValue !== null && rowValue < value; }; -export const isGreaterThanSliderRow = (rowValue: number, value: number) => { +export const isGreaterThanSliderRow = ( + rowValue: number | null, + value: number, +) => { return rowValue !== null && rowValue > value; }; -export const isNotEqualToSliderRow = (rowValue: number, value: number) => { +export const isNotEqualToSliderRow = ( + rowValue: number | null, + value: number, +) => { return rowValue !== null && rowValue !== value; }; -export const isEqualToSliderRow = (rowValue: number, value: number) => { +export const isEqualToSliderRow = (rowValue: number | null, value: number) => { return rowValue !== null && rowValue === value; }; diff --git a/src/features/pass-survey/model/AnswerValidator.ts b/src/features/pass-survey/model/AnswerValidator.ts index b7738dcc6..24135ec47 100644 --- a/src/features/pass-survey/model/AnswerValidator.ts +++ b/src/features/pass-survey/model/AnswerValidator.ts @@ -56,16 +56,6 @@ export function AnswerValidator( return answer ?? null; }; - function isValidTimeFormat( - time: { hours: number; minutes: number } | null | undefined, - ): boolean { - return ( - !!time && - typeof time.hours === 'number' && - typeof time.minutes === 'number' - ); - } - const getAnswerTime = (): { hours: number; minutes: number } | null => { let answer = currentAnswer?.answer as Maybe<{ hours: number; @@ -134,27 +124,22 @@ export function AnswerValidator( isEqualToSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; return isEqualToSliderRow(rowValue, value); }, isNotEqualToSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; - return isNotEqualToSliderRow(rowValue, value); }, isGreaterThanSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; return isGreaterThanSliderRow(rowValue, value); }, isLessThanSliderRow(rowIndex: number, value: number) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; return isLessThanSliderRow(rowValue, value); }, @@ -164,7 +149,6 @@ export function AnswerValidator( maxValue: number, ) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; return isBetweenSliderRowValues(rowValue, minValue, maxValue); }, @@ -176,8 +160,6 @@ export function AnswerValidator( ) { const rowValue = getSliderRowValue(rowIndex); - if (!rowValue) return false; - return isOutsideOfSliderRowValues(rowValue, minValue, maxValue); }, @@ -253,31 +235,26 @@ export function AnswerValidator( isGreaterThanDate(date: string) { const answerDate = getAnswerDate(); - if (!answerDate) return false; return isGreaterThanDate(answerDate, date); }, isLessThanDate(date: string) { const answerDate = getAnswerDate(); - if (!answerDate) return false; return isLessThanDate(answerDate, date); }, isEqualToDate(date: string) { const answerDate = getAnswerDate(); - if (!answerDate) return false; return isEqualToDate(answerDate, date); }, isNotEqualToDate(date: string) { const answerDate = getAnswerDate(); - if (!answerDate) return false; return isNotEqualToDate(answerDate, date); }, isBetweenDates(minDate: string, maxDate: string) { const answerDate = getAnswerDate(); - if (!answerDate) return false; return isBetweenDates(answerDate, minDate, maxDate); }, @@ -288,37 +265,24 @@ export function AnswerValidator( isGreaterThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - - if (!isValidTimeFormat(time) || !answerTime) { - return false; - } return isGreaterThanTime(answerTime, time); }, isLessThanTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!isValidTimeFormat(time) || !answerTime) { - return false; - } return isLessThanTime(answerTime, time); }, isEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!isValidTimeFormat(time) || !answerTime) { - return false; - } return isEqualToTime(answerTime, time); }, isNotEqualToTime(time: { hours: number; minutes: number }) { const answerTime = getAnswerTime(); - if (!isValidTimeFormat(time) || !answerTime) { - return false; - } return isNotEqualToTime(answerTime, time); }, @@ -327,7 +291,6 @@ export function AnswerValidator( maxTime: { hours: number; minutes: number }, ) { const answerTime = getAnswerTime(); - if (!answerTime) return false; return isBetweenTimes(answerTime, minTime, maxTime); }, @@ -336,41 +299,28 @@ export function AnswerValidator( maxTime: { hours: number; minutes: number }, ) { const answerTime = getAnswerTime(); - if (!answerTime) return false; return isOutsideOfTimes(answerTime, minTime, maxTime); }, isGreaterThanTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } return isGreaterThanTimeRange(answerTimeRange, { time, fieldName }); }, isEqualToTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } return isEqualToTimeRange(answerTimeRange, { time, fieldName }); }, isLessThanTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } return isLessThanTimeRange(answerTimeRange, { time, fieldName }); }, isNotEqualToTimeRange(time: HourMinute, fieldName: string) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } return isNotEqualToTimeRange(answerTimeRange, { time, fieldName }); }, isBetweenTimesRange( @@ -379,9 +329,6 @@ export function AnswerValidator( fieldName: string, ) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } return isBetweenTimesRange(answerTimeRange, { minTime, maxTime, @@ -396,10 +343,6 @@ export function AnswerValidator( ) { const answerTimeRange = getAnswerTimeRange(); - if (!answerTimeRange) { - return false; - } - return isOutsideOfTimesRange(answerTimeRange, { minTime, maxTime, From ec1e3d1c77bc24f9e348a4bc198797edd914350e Mon Sep 17 00:00:00 2001 From: Felipe Imperio Date: Tue, 19 Nov 2024 13:08:10 -0800 Subject: [PATCH 18/18] improving validators on conditions.ts --- src/entities/conditional-logic/model/conditions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/entities/conditional-logic/model/conditions.ts b/src/entities/conditional-logic/model/conditions.ts index 70e3ee7ff..d6170f758 100644 --- a/src/entities/conditional-logic/model/conditions.ts +++ b/src/entities/conditional-logic/model/conditions.ts @@ -188,7 +188,7 @@ export const isEqualToTimeRange = ( typeof time === 'string' ? parseTimeString(time) : time; return selectedTime - ? timeToMinutes(selectedTime) == timeToMinutes(normalizedTime) + ? timeToMinutes(selectedTime) === timeToMinutes(normalizedTime) : false; }; @@ -293,7 +293,7 @@ export const isEqualToTime = ( const inputMinutes = timeToMinutes(input); const conditionMinutes = timeToMinutes(time); - const result = inputMinutes == conditionMinutes; + const result = inputMinutes === conditionMinutes; return result; };