From 60f907138f10f802a25054f7c8aa037f5ad71995 Mon Sep 17 00:00:00 2001 From: farmerpaul Date: Fri, 30 Aug 2024 10:42:19 -0300 Subject: [PATCH] feat: per-subject activity progress/submission Made widespread change to incorporate `targetSubjectId` into activity progres tracking, when assignments feature is enabled. Anytime `eventId` is used, now `targetSubjectid` also needs to be passed to identify which activity assignment is the active assessment. This value is also passed to the `answers` POST endpoint to ensure the submission is also properly associated with the `targetSubjectId`. For self-reports, and when assignments feature is disabled, `targetSubjectId` is set to `null`, effectively inheriting existing functionality. For `PublicSurvey` and `PublicAutoCompletion`, `targetSubjectId` stays `null` since public assessments do not support activity assignments. --- src/abstract/lib/GroupBuilder/GroupUtility.ts | 5 +- .../lib/GroupBuilder/activityGroups.types.ts | 4 +- src/abstract/lib/GroupBuilder/types.ts | 2 +- src/abstract/lib/getProgressId.ts | 18 ++- src/abstract/lib/types/entityProgress.ts | 8 +- .../applet/model/hooks/useActivityProgress.ts | 10 +- .../applet/model/hooks/useEntityComplete.ts | 36 ++++- .../applet/model/hooks/useEntityStart.ts | 32 +++-- .../model/hooks/useGroupProgressRecord.ts | 9 +- .../hooks/useGroupProgressStateManager.ts | 5 +- .../applet/model/hooks/useItemTimerState.ts | 19 ++- .../model/hooks/useSaveActivityItemAnswer.ts | 9 +- .../applet/model/hooks/useUserEvents.ts | 14 +- src/entities/applet/model/selectors.ts | 9 +- src/entities/applet/model/slice.ts | 131 +++++++++--------- src/entities/applet/model/types.ts | 16 +++ src/entities/subject/api/useSubjectQuery.ts | 4 +- .../model/hooks/useAutoCompletionRecord.ts | 9 +- .../hooks/useAutoCompletionStateManager.ts | 3 +- src/features/AutoCompletion/model/slice.ts | 13 +- src/features/PassSurvey/hooks/useAnswers.ts | 8 +- src/features/PassSurvey/hooks/useItemTimer.ts | 3 + .../PassSurvey/hooks/useStartSurvey.ts | 23 ++- .../PassSurvey/hooks/useSummaryData.ts | 4 +- .../PassSurvey/hooks/useSurveyDataQuery.ts | 30 +++- src/features/PassSurvey/lib/SurveyContext.ts | 2 + src/features/PassSurvey/lib/mappers.ts | 5 +- src/features/PassSurvey/ui/EntityTimer.tsx | 2 +- src/pages/AutoCompletion/index.tsx | 2 + src/pages/PublicAutoCompletion/index.tsx | 1 + src/pages/PublicSurvey/index.tsx | 1 + src/pages/Survey/index.tsx | 9 +- src/shared/api/types/activity.ts | 1 + src/shared/constants/routes.ts | 28 +++- src/shared/utils/hooks/index.ts | 1 + .../lib/AppletDetailsContext.ts | 4 +- .../model/hooks/useActivityGroups.ts | 2 +- .../model/hooks/useEntitiesSync.ts | 4 + .../services/ActivityGroupsBuildManager.ts | 11 +- .../ActivityGroups/ui/ActivityCard/index.tsx | 8 +- src/widgets/ActivityGroups/ui/index.tsx | 4 +- .../AutoCompletion/lib/useAutoCompletion.ts | 4 + .../ui/AutoCompletionScreen.tsx | 4 + src/widgets/AutoCompletion/ui/index.tsx | 23 ++- .../Survey/model/hooks/useEntityTimer.ts | 3 +- src/widgets/Survey/ui/PassingScreen.tsx | 47 ++++++- src/widgets/Survey/ui/ScreenManager.tsx | 4 +- src/widgets/Survey/ui/SummaryScreen/index.tsx | 4 +- src/widgets/Survey/ui/WelcomeScreen.tsx | 14 +- src/widgets/Survey/ui/index.tsx | 22 ++- 50 files changed, 456 insertions(+), 178 deletions(-) diff --git a/src/abstract/lib/GroupBuilder/GroupUtility.ts b/src/abstract/lib/GroupBuilder/GroupUtility.ts index 57e5bd4d0..6f651cf57 100644 --- a/src/abstract/lib/GroupBuilder/GroupUtility.ts +++ b/src/abstract/lib/GroupBuilder/GroupUtility.ts @@ -118,7 +118,10 @@ export class GroupUtility { } public getProgressRecord(eventEntity: EventEntity): GroupProgress | null { - const record = this.progress[getProgressId(eventEntity.entity.id, eventEntity.event.id)]; + const record = + this.progress[ + getProgressId(eventEntity.entity.id, eventEntity.event.id, eventEntity.targetSubject?.id) + ]; return record ?? null; } diff --git a/src/abstract/lib/GroupBuilder/activityGroups.types.ts b/src/abstract/lib/GroupBuilder/activityGroups.types.ts index 55a4410c1..943fc4133 100644 --- a/src/abstract/lib/GroupBuilder/activityGroups.types.ts +++ b/src/abstract/lib/GroupBuilder/activityGroups.types.ts @@ -29,8 +29,8 @@ export type Entity = Activity | ActivityFlow; export type EventEntity = { entity: Entity; event: ScheduleEvent; - /** Target subject of assignment if not self-report, else undefined */ - targetSubject?: SubjectDTO; + /** Target subject of assignment if not self-report, else null */ + targetSubject: SubjectDTO | null; }; export type EntityType = 'regular' | 'flow'; diff --git a/src/abstract/lib/GroupBuilder/types.ts b/src/abstract/lib/GroupBuilder/types.ts index ba873e5ba..8551a473c 100644 --- a/src/abstract/lib/GroupBuilder/types.ts +++ b/src/abstract/lib/GroupBuilder/types.ts @@ -6,7 +6,7 @@ export type ActivityListItem = { activityId: string; flowId: string | null; eventId: string; - targetSubject?: SubjectDTO; + targetSubject: SubjectDTO | null; name: string; description: string; diff --git a/src/abstract/lib/getProgressId.ts b/src/abstract/lib/getProgressId.ts index 7fe5c6ea9..f05acd2c2 100644 --- a/src/abstract/lib/getProgressId.ts +++ b/src/abstract/lib/getProgressId.ts @@ -1,11 +1,17 @@ -export const getProgressId = (entityId: string, eventId: string): string => { - return `${entityId}/${eventId}`; +import { GroupProgressId } from './types'; + +export const getProgressId = ( + entityId: string, + eventId: string, + targetSubjectId?: string | null, +): GroupProgressId => { + return targetSubjectId ? `${entityId}/${eventId}/${targetSubjectId}` : `${entityId}/${eventId}`; }; export const getDataFromProgressId = ( - progressId: string, -): { entityId: string; eventId: string } => { - const [entityId, eventId] = progressId.split('/'); + progressId: GroupProgressId, +): { entityId: string; eventId: string; targetSubjectId: string | null } => { + const [entityId, eventId, targetSubjectId = null] = progressId.split('/'); - return { entityId, eventId }; + return { entityId, eventId, targetSubjectId }; }; diff --git a/src/abstract/lib/types/entityProgress.ts b/src/abstract/lib/types/entityProgress.ts index 58634ba22..3a0554d6e 100644 --- a/src/abstract/lib/types/entityProgress.ts +++ b/src/abstract/lib/types/entityProgress.ts @@ -55,6 +55,12 @@ type EventProgressTimestampState = { export type GroupProgress = ActivityOrFlowProgress & EventProgressTimestampState; -type GroupProgressId = string; // Group progress id is a combination of entityId and eventId (EntityId = ActivityId or FlowId) +/** + * Combination of: + * - entityId (= activityId/flowId), + * - eventId + * - targetSubjectId (optional; only if not self-report) + */ +export type GroupProgressId = `${string}/${string}` | `${string}/${string}/${string}`; export type GroupProgressState = Record; diff --git a/src/entities/applet/model/hooks/useActivityProgress.ts b/src/entities/applet/model/hooks/useActivityProgress.ts index 1ae971a67..3735f0883 100644 --- a/src/entities/applet/model/hooks/useActivityProgress.ts +++ b/src/entities/applet/model/hooks/useActivityProgress.ts @@ -12,11 +12,13 @@ import { useAppDispatch, useAppSelector } from '~/shared/utils'; type SaveProgressProps = { activity: ActivityDTO; eventId: string; + targetSubjectId: string | null; }; type DefaultProps = { activityId: string; eventId: string; + targetSubjectId: string | null; }; export const useActivityProgress = () => { @@ -26,7 +28,10 @@ export const useActivityProgress = () => { const getActivityProgress = useCallback( (props: DefaultProps): ActivityProgress | null => { - const progress = activitiesProgressState[getProgressId(props.activityId, props.eventId)]; + const progress = + activitiesProgressState[ + getProgressId(props.activityId, props.eventId, props.targetSubjectId) + ]; return progress ?? null; }, @@ -61,6 +66,7 @@ export const useActivityProgress = () => { actions.saveActivityProgress({ activityId: props.activity.id, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, progress: { items: preparedActivityItemProgressRecords, step: initialStep, @@ -81,6 +87,7 @@ export const useActivityProgress = () => { actions.changeSummaryScreenVisibility({ activityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, isSummaryScreenOpen: true, }), ); @@ -94,6 +101,7 @@ export const useActivityProgress = () => { actions.changeSummaryScreenVisibility({ activityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, isSummaryScreenOpen: false, }), ); diff --git a/src/entities/applet/model/hooks/useEntityComplete.ts b/src/entities/applet/model/hooks/useEntityComplete.ts index cd33dd01d..175ac6227 100644 --- a/src/entities/applet/model/hooks/useEntityComplete.ts +++ b/src/entities/applet/model/hooks/useEntityComplete.ts @@ -13,6 +13,7 @@ type CompletionType = 'regular' | 'autoCompletion'; type Props = { activityId: string; eventId: string; + targetSubjectId: string | null; flowId: string | null; publicAppletKey: string | null; @@ -40,6 +41,7 @@ export const useEntityComplete = (props: Props) => { entityCompleted({ entityId: props.flowId ? props.flowId : props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, }); const isAutoCompletion = completionType === 'autoCompletion'; @@ -73,6 +75,7 @@ export const useEntityComplete = (props: Props) => { props.eventId, props.flowId, props.publicAppletKey, + props.targetSubjectId, ], ); @@ -97,13 +100,21 @@ export const useEntityComplete = (props: Props) => { appletId: props.appletId, activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, entityType: 'flow', flowId: props.flowId, }), { replace: true }, ); }, - [navigator, props.appletId, props.eventId, props.flowId, props.publicAppletKey], + [ + navigator, + props.appletId, + props.eventId, + props.flowId, + props.publicAppletKey, + props.targetSubjectId, + ], ); const completeFlow = useCallback( @@ -113,6 +124,7 @@ export const useEntityComplete = (props: Props) => { const groupProgress = getGroupProgress({ entityId: props.flowId ? props.flowId : props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, }); if (!groupProgress) { @@ -137,10 +149,15 @@ export const useEntityComplete = (props: Props) => { activityId: nextActivityId ? nextActivityId : props.flow.activityIds[0], flowId: props.flow.id, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, pipelineActivityOrder: nextActivityId ? currentPipelineActivityOrder + 1 : 0, }); - removeActivityProgress({ activityId: props.activityId, eventId: props.eventId }); + removeActivityProgress({ + activityId: props.activityId, + eventId: props.eventId, + targetSubjectId: props.targetSubjectId, + }); if (nextActivityId && !isAutoCompletion) { return redirectToNextActivity(nextActivityId); @@ -158,6 +175,7 @@ export const useEntityComplete = (props: Props) => { props.eventId, props.flow, props.flowId, + props.targetSubjectId, redirectToNextActivity, removeActivityProgress, ], @@ -165,11 +183,21 @@ export const useEntityComplete = (props: Props) => { const completeActivity = useCallback( (input?: CompleteOptions) => { - removeActivityProgress({ activityId: props.activityId, eventId: props.eventId }); + removeActivityProgress({ + activityId: props.activityId, + eventId: props.eventId, + targetSubjectId: props.targetSubjectId, + }); return completeEntityAndRedirect(input?.type || 'regular'); }, - [completeEntityAndRedirect, props.activityId, props.eventId, removeActivityProgress], + [ + completeEntityAndRedirect, + props.activityId, + props.eventId, + props.targetSubjectId, + removeActivityProgress, + ], ); return { diff --git a/src/entities/applet/model/hooks/useEntityStart.ts b/src/entities/applet/model/hooks/useEntityStart.ts index d4f6902a0..ea6f9d8cc 100644 --- a/src/entities/applet/model/hooks/useEntityStart.ts +++ b/src/entities/applet/model/hooks/useEntityStart.ts @@ -9,16 +9,24 @@ export const useEntityStart = () => { const dispatch = useAppDispatch(); const groupProgress = useAppSelector(groupProgressSelector); - const getProgress = (entityId: string, eventId: string): GroupProgress => - groupProgress[getProgressId(entityId, eventId)]; + const getProgress = ( + entityId: string, + eventId: string, + targetSubjectId: string | null, + ): GroupProgress => groupProgress[getProgressId(entityId, eventId, targetSubjectId)]; const isInProgress = (payload: GroupProgress): boolean => payload && !payload.endAt; - function activityStarted(activityId: string, eventId: string): void { + function activityStarted( + activityId: string, + eventId: string, + targetSubjectId: string | null, + ): void { dispatch( actions.activityStarted({ activityId, eventId, + targetSubjectId, }), ); } @@ -27,6 +35,7 @@ export const useEntityStart = () => { flowId: string, activityId: string, eventId: string, + targetSubjectId: string | null, pipelineActivityOrder: number, ): void { dispatch( @@ -34,25 +43,30 @@ export const useEntityStart = () => { flowId, activityId, eventId, + targetSubjectId, pipelineActivityOrder, }), ); } - function startActivity(activityId: string, eventId: string): void { - const isActivityInProgress = isInProgress(getProgress(activityId, eventId)); + function startActivity( + activityId: string, + eventId: string, + targetSubjectId: string | null, + ): void { + const isActivityInProgress = isInProgress(getProgress(activityId, eventId, targetSubjectId)); if (isActivityInProgress) { return; } - return activityStarted(activityId, eventId); + return activityStarted(activityId, eventId, targetSubjectId); } - function startFlow(eventId: string, flow: ActivityFlowDTO): void { + function startFlow(eventId: string, flow: ActivityFlowDTO, targetSubjectId: string | null): void { const flowId = flow.id; - const isFlowInProgress = isInProgress(getProgress(flowId, eventId)); + const isFlowInProgress = isInProgress(getProgress(flowId, eventId, targetSubjectId)); if (isFlowInProgress) { return; @@ -68,7 +82,7 @@ export const useEntityStart = () => { const firstActivityId: string = flowActivities[0]; - return flowStarted(flowId, firstActivityId, eventId, 0); + return flowStarted(flowId, firstActivityId, eventId, targetSubjectId, 0); } return { startActivity, startFlow }; diff --git a/src/entities/applet/model/hooks/useGroupProgressRecord.ts b/src/entities/applet/model/hooks/useGroupProgressRecord.ts index 7a76bbddf..aabdf99c6 100644 --- a/src/entities/applet/model/hooks/useGroupProgressRecord.ts +++ b/src/entities/applet/model/hooks/useGroupProgressRecord.ts @@ -6,11 +6,16 @@ import { useAppSelector } from '~/shared/utils'; type Props = { entityId: string; eventId: string; + targetSubjectId: string | null; }; -export const useGroupProgressRecord = ({ entityId, eventId }: Props): GroupProgress | null => { +export const useGroupProgressRecord = ({ + entityId, + eventId, + targetSubjectId, +}: Props): GroupProgress | null => { const record = useAppSelector((state) => - selectGroupProgress(state, getProgressId(entityId, eventId)), + selectGroupProgress(state, getProgressId(entityId, eventId, targetSubjectId)), ); return record ?? null; diff --git a/src/entities/applet/model/hooks/useGroupProgressStateManager.ts b/src/entities/applet/model/hooks/useGroupProgressStateManager.ts index 86c186f3f..49f5e30fa 100644 --- a/src/entities/applet/model/hooks/useGroupProgressStateManager.ts +++ b/src/entities/applet/model/hooks/useGroupProgressStateManager.ts @@ -33,7 +33,10 @@ export const useGroupProgressStateManager = (): Return => { return null; } - return groupProgresses[getProgressId(params.entityId, params.eventId)] ?? null; + return ( + groupProgresses[getProgressId(params.entityId, params.eventId, params.targetSubjectId)] ?? + null + ); }, [groupProgresses], ); diff --git a/src/entities/applet/model/hooks/useItemTimerState.ts b/src/entities/applet/model/hooks/useItemTimerState.ts index ed2677572..8e1c788dc 100644 --- a/src/entities/applet/model/hooks/useItemTimerState.ts +++ b/src/entities/applet/model/hooks/useItemTimerState.ts @@ -10,6 +10,7 @@ import { useAppDispatch, useAppSelector } from '~/shared/utils'; type Props = { activityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; }; @@ -24,11 +25,16 @@ type InitializeTimerProps = { duration: number; }; -export const useItemTimerState = ({ activityId, eventId, itemId }: Props): Return => { +export const useItemTimerState = ({ + activityId, + eventId, + targetSubjectId, + itemId, +}: Props): Return => { const dispatch = useAppDispatch(); const timerSettingsState = useAppSelector((state) => - selectActivityProgress(state, getProgressId(activityId, eventId)), + selectActivityProgress(state, getProgressId(activityId, eventId, targetSubjectId)), ); const timerSettings = timerSettingsState?.itemTimer[itemId] ?? null; @@ -39,6 +45,7 @@ export const useItemTimerState = ({ activityId, eventId, itemId }: Props): Retur actions.setItemTimerStatus({ activityId, eventId, + targetSubjectId, itemId, timerStatus: { duration, @@ -47,7 +54,7 @@ export const useItemTimerState = ({ activityId, eventId, itemId }: Props): Retur }), ); }, - [activityId, dispatch, eventId, itemId], + [activityId, dispatch, eventId, itemId, targetSubjectId], ); const removeTimer = useCallback(() => { @@ -55,20 +62,22 @@ export const useItemTimerState = ({ activityId, eventId, itemId }: Props): Retur actions.removeItemTimerStatus({ activityId, eventId, + targetSubjectId, itemId, }), ); - }, [activityId, dispatch, eventId, itemId]); + }, [activityId, dispatch, eventId, itemId, targetSubjectId]); const timerTick = useCallback(() => { return dispatch( actions.itemTimerTick({ activityId, eventId, + targetSubjectId, itemId, }), ); - }, [activityId, dispatch, eventId, itemId]); + }, [activityId, dispatch, eventId, itemId, targetSubjectId]); return { timerSettings, diff --git a/src/entities/applet/model/hooks/useSaveActivityItemAnswer.ts b/src/entities/applet/model/hooks/useSaveActivityItemAnswer.ts index 2de0447b0..f29096e53 100644 --- a/src/entities/applet/model/hooks/useSaveActivityItemAnswer.ts +++ b/src/entities/applet/model/hooks/useSaveActivityItemAnswer.ts @@ -8,9 +8,10 @@ import { useAppDispatch } from '~/shared/utils'; type Props = { activityId: string; eventId: string; + targetSubjectId: string | null; }; -export const useSaveItemAnswer = ({ activityId, eventId }: Props) => { +export const useSaveItemAnswer = ({ activityId, eventId, targetSubjectId }: Props) => { const dispatch = useAppDispatch(); const saveItemAnswer = useCallback( @@ -19,12 +20,13 @@ export const useSaveItemAnswer = ({ activityId, eventId }: Props) => { actions.saveItemAnswer({ entityId: activityId, eventId, + targetSubjectId, itemId, answer, }), ); }, - [dispatch, activityId, eventId], + [dispatch, activityId, eventId, targetSubjectId], ); const saveItemAdditionalText = useCallback( @@ -33,12 +35,13 @@ export const useSaveItemAnswer = ({ activityId, eventId }: Props) => { actions.saveAdditionalText({ entityId: activityId, eventId, + targetSubjectId, itemId, additionalText, }), ); }, - [dispatch, activityId, eventId], + [dispatch, activityId, eventId, targetSubjectId], ); const removeItemAnswer = useCallback( diff --git a/src/entities/applet/model/hooks/useUserEvents.ts b/src/entities/applet/model/hooks/useUserEvents.ts index 4190e7347..5603c58bf 100644 --- a/src/entities/applet/model/hooks/useUserEvents.ts +++ b/src/entities/applet/model/hooks/useUserEvents.ts @@ -12,12 +12,13 @@ import { useAppDispatch, useAppSelector } from '~/shared/utils'; type Props = { activityId: string; eventId: string; + targetSubjectId: string | null; }; export const useUserEvents = (props: Props) => { const dispatch = useAppDispatch(); - const progressId = getProgressId(props.activityId, props.eventId); + const progressId = getProgressId(props.activityId, props.eventId, props.targetSubjectId); const activityProgress = useAppSelector((state) => selectActivityProgress(state, progressId)); @@ -35,6 +36,7 @@ export const useUserEvents = (props: Props) => { actions.saveUserEvent({ entityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, itemId: item.id, userEvent: newUserEvent, }), @@ -42,7 +44,7 @@ export const useUserEvents = (props: Props) => { return newUserEvent; }, - [dispatch, props.activityId, props.eventId], + [dispatch, props.activityId, props.eventId, props.targetSubjectId], ); const saveSetAnswerUserEvent = useCallback( @@ -65,6 +67,7 @@ export const useUserEvents = (props: Props) => { actions.updateUserEventByIndex({ entityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, userEventIndex: userEvents.length - 1, userEvent: { type: 'SET_ANSWER', @@ -83,6 +86,7 @@ export const useUserEvents = (props: Props) => { actions.saveUserEvent({ entityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, itemId: item.id, userEvent: { type: 'SET_ANSWER', @@ -93,7 +97,7 @@ export const useUserEvents = (props: Props) => { }), ); }, - [activityProgress, dispatch, props.activityId, props.eventId], + [activityProgress, dispatch, props.activityId, props.eventId, props.targetSubjectId], ); const saveSetAdditionalTextUserEvent = useCallback( @@ -128,6 +132,7 @@ export const useUserEvents = (props: Props) => { actions.updateUserEventByIndex({ entityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, userEventIndex: userEvents.length - 1, userEvent: { type: 'SET_ANSWER', @@ -148,6 +153,7 @@ export const useUserEvents = (props: Props) => { actions.saveUserEvent({ entityId: props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, itemId: item.id, userEvent: { type: 'SET_ANSWER', @@ -158,7 +164,7 @@ export const useUserEvents = (props: Props) => { }), ); }, - [activityProgress, dispatch, props.activityId, props.eventId], + [activityProgress, dispatch, props.activityId, props.eventId, props.targetSubjectId], ); return { saveUserEventByType, saveSetAnswerUserEvent, saveSetAdditionalTextUserEvent }; diff --git a/src/entities/applet/model/selectors.ts b/src/entities/applet/model/selectors.ts index a533a55f2..6b6717ea2 100644 --- a/src/entities/applet/model/selectors.ts +++ b/src/entities/applet/model/selectors.ts @@ -1,8 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; +import { GroupProgressId } from '~/abstract/lib'; import { RootState } from '~/shared/utils'; -const selectEntityId = (_: unknown, entityId: string) => entityId; +const selectGroupProgressId = (_: unknown, groupProgressId: GroupProgressId) => groupProgressId; export const appletsSelector = (state: RootState) => state.applets; @@ -12,8 +13,8 @@ export const groupProgressSelector = createSelector( ); export const selectGroupProgress = createSelector( - [groupProgressSelector, selectEntityId], - (groupProgress, entityId) => groupProgress[entityId], + [groupProgressSelector, selectGroupProgressId], + (groupProgress, groupProgressId) => groupProgress[groupProgressId], ); export const activityProgressSelector = createSelector( @@ -22,7 +23,7 @@ export const activityProgressSelector = createSelector( ); export const selectActivityProgress = createSelector( - [activityProgressSelector, selectEntityId], + [activityProgressSelector, selectGroupProgressId], (progress, entityId) => progress[entityId], ); diff --git a/src/entities/applet/model/slice.ts b/src/entities/applet/model/slice.ts index 81fe1c67c..d9acf721b 100644 --- a/src/entities/applet/model/slice.ts +++ b/src/entities/applet/model/slice.ts @@ -60,60 +60,63 @@ const appletsSlice = createSlice({ return initialState; }, - removeActivityProgress: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + removeActivityProgress: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); delete state.progress[id]; }, - saveGroupProgress: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + saveGroupProgress: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const groupProgress = state.groupProgress[id] ?? {}; const updatedProgress = { ...groupProgress, - ...action.payload.progressPayload, + ...payload.progressPayload, }; state.groupProgress[id] = updatedProgress; }, - removeGroupProgress: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + removeGroupProgress: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); delete state.groupProgress[id]; }, - saveSummaryDataInGroupContext: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + saveSummaryDataInGroupContext: ( + state, + { payload }: PayloadAction, + ) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); const groupProgress = state.groupProgress[id] ?? {}; const groupContext = groupProgress.context ?? {}; - groupContext.summaryData[action.payload.activityId] = action.payload.summaryData; + groupContext.summaryData[payload.activityId] = payload.summaryData; }, - saveActivityProgress: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + saveActivityProgress: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); - state.progress[id] = action.payload.progress; + state.progress[id] = payload.progress; }, changeSummaryScreenVisibility: ( state, - action: PayloadAction, + { payload }: PayloadAction, ) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; - activityProgress.isSummaryScreenOpen = action.payload.isSummaryScreenOpen; + activityProgress.isSummaryScreenOpen = payload.isSummaryScreenOpen; }, - setItemTimerStatus: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + setItemTimerStatus: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const progress = state.progress[id]; @@ -121,31 +124,31 @@ const appletsSlice = createSlice({ progress.itemTimer = {}; } - progress.itemTimer[action.payload.itemId] = action.payload.timerStatus; + progress.itemTimer[payload.itemId] = payload.timerStatus; }, - removeItemTimerStatus: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + removeItemTimerStatus: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const progress = state.progress[id]; if (!progress) return state; - const timer = progress.itemTimer[action.payload.itemId]; + const timer = progress.itemTimer[payload.itemId]; if (timer) { - delete progress.itemTimer[action.payload.itemId]; + delete progress.itemTimer[payload.itemId]; } }, - itemTimerTick: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + itemTimerTick: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const progress = state.progress[id]; if (!progress) return state; - const timer = progress.itemTimer[action.payload.itemId]; + const timer = progress.itemTimer[payload.itemId]; if (!timer) return state; @@ -154,21 +157,21 @@ const appletsSlice = createSlice({ } }, - saveItemAnswer: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + saveItemAnswer: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; if (!activityProgress) { return state; } - const itemIndex = activityProgress.items.findIndex(({ id }) => id === action.payload.itemId); + const itemIndex = activityProgress.items.findIndex(({ id }) => id === payload.itemId); if (itemIndex === -1) { return state; } - activityProgress.items[itemIndex].answer = action.payload.answer; + activityProgress.items[itemIndex].answer = payload.answer; }, /** @@ -177,66 +180,66 @@ const appletsSlice = createSlice({ * @param action * @returns */ - saveAdditionalText: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + saveAdditionalText: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; if (!activityProgress) { return state; } - const itemIndex = activityProgress.items.findIndex(({ id }) => id === action.payload.itemId); + const itemIndex = activityProgress.items.findIndex(({ id }) => id === payload.itemId); if (itemIndex === -1) { return state; } - activityProgress.items[itemIndex].additionalText = action.payload.additionalText; + activityProgress.items[itemIndex].additionalText = payload.additionalText; }, - saveUserEvent: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + saveUserEvent: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; if (!activityProgress) { return state; } - activityProgress.userEvents.push(action.payload.userEvent); + activityProgress.userEvents.push(payload.userEvent); }, - updateUserEventByIndex: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + updateUserEventByIndex: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; if (!activityProgress) { return state; } - if (!activityProgress.userEvents[action.payload.userEventIndex]) { + if (!activityProgress.userEvents[payload.userEventIndex]) { return state; } - activityProgress.userEvents[action.payload.userEventIndex] = action.payload.userEvent; + activityProgress.userEvents[payload.userEventIndex] = payload.userEvent; }, - incrementStep: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + incrementStep: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; activityProgress.step += 1; }, - decrementStep: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + decrementStep: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const activityProgress = state.progress[id]; activityProgress.step -= 1; }, - activityStarted: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.activityId, action.payload.eventId); + activityStarted: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.activityId, payload.eventId, payload.targetSubjectId); const activityEvent: GroupProgress = { type: ActivityPipelineType.Regular, @@ -250,17 +253,17 @@ const appletsSlice = createSlice({ state.groupProgress[id] = activityEvent; }, - flowStarted: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.flowId, action.payload.eventId); + flowStarted: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.flowId, payload.eventId, payload.targetSubjectId); const flowEvent: GroupProgress = { type: ActivityPipelineType.Flow, - currentActivityId: action.payload.activityId, + currentActivityId: payload.activityId, startAt: new Date().getTime(), currentActivityStartAt: new Date().getTime(), endAt: null, executionGroupKey: uuidV4(), - pipelineActivityOrder: action.payload.pipelineActivityOrder, + pipelineActivityOrder: payload.pipelineActivityOrder, context: { summaryData: {}, }, @@ -269,18 +272,18 @@ const appletsSlice = createSlice({ state.groupProgress[id] = flowEvent; }, - flowUpdated: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.flowId, action.payload.eventId); + flowUpdated: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.flowId, payload.eventId, payload.targetSubjectId); const groupProgress = state.groupProgress[id] as FlowProgress; - groupProgress.currentActivityId = action.payload.activityId; - groupProgress.pipelineActivityOrder = action.payload.pipelineActivityOrder; + groupProgress.currentActivityId = payload.activityId; + groupProgress.pipelineActivityOrder = payload.pipelineActivityOrder; groupProgress.currentActivityStartAt = new Date().getTime(); }, - entityCompleted: (state, action: PayloadAction) => { - const id = getProgressId(action.payload.entityId, action.payload.eventId); + entityCompleted: (state, { payload }: PayloadAction) => { + const id = getProgressId(payload.entityId, payload.eventId, payload.targetSubjectId); state.groupProgress[id].endAt = new Date().getTime(); @@ -290,18 +293,18 @@ const appletsSlice = createSlice({ const now = new Date().getTime(); - completedEntities[action.payload.entityId] = now; + completedEntities[payload.entityId] = now; - if (!completions[action.payload.entityId]) { - completions[action.payload.entityId] = {}; + if (!completions[payload.entityId]) { + completions[payload.entityId] = {}; } - const entityCompletions = completions[action.payload.entityId]; + const entityCompletions = completions[payload.entityId]; - if (!entityCompletions[action.payload.eventId]) { - entityCompletions[action.payload.eventId] = []; + if (!entityCompletions[payload.eventId]) { + entityCompletions[payload.eventId] = []; } - entityCompletions[action.payload.eventId].push(now); + entityCompletions[payload.eventId].push(now); }, initiateTakeNow: (state, action: PayloadAction) => { diff --git a/src/entities/applet/model/types.ts b/src/entities/applet/model/types.ts index 17222a2e5..395d96bb6 100644 --- a/src/entities/applet/model/types.ts +++ b/src/entities/applet/model/types.ts @@ -118,18 +118,21 @@ export type ProgressState = Record; export type SaveActivityProgressPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; progress: ActivityProgress; }; export type ChangeSummaryScreenVisibilityPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; isSummaryScreenOpen: boolean; }; export type SetItemTimerPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; timerStatus: ItemTimerProgress; }; @@ -137,28 +140,33 @@ export type SetItemTimerPayload = { export type ItemTimerTickPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; }; export type RemoveActivityProgressPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; }; export type RemoveGroupProgressPayload = { entityId: string; eventId: string; + targetSubjectId: string | null; }; export type SaveGroupProgressPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; progressPayload: GroupProgress; }; export type SaveSummaryDataInContext = { entityId: string; eventId: string; + targetSubjectId: string | null; activityId: string; summaryData: FlowSummaryData; @@ -167,6 +175,7 @@ export type SaveSummaryDataInContext = { export type SaveItemAnswerPayload = { entityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; answer: Answer; }; @@ -174,6 +183,7 @@ export type SaveItemAnswerPayload = { export type SaveItemAdditionalTextPayload = { entityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; additionalText: string; }; @@ -181,11 +191,13 @@ export type SaveItemAdditionalTextPayload = { export type UpdateStepPayload = { activityId: string; eventId: string; + targetSubjectId: string | null; }; export type SaveUserEventPayload = { entityId: string; eventId: string; + targetSubjectId: string | null; itemId: string; userEvent: UserEvent; }; @@ -193,6 +205,7 @@ export type SaveUserEventPayload = { export type UpdateUserEventByIndexPayload = { entityId: string; eventId: string; + targetSubjectId: string | null; userEventIndex: number; userEvent: UserEvent; }; @@ -208,17 +221,20 @@ export type CompletedEventEntities = Record; export type InProgressEntity = { entityId: string; eventId: string; + targetSubjectId: string | null; }; export type InProgressActivity = { activityId: string; eventId: string; + targetSubjectId: string | null; }; export type InProgressFlow = { flowId: string; activityId: string; eventId: string; + targetSubjectId: string | null; pipelineActivityOrder: number; }; diff --git a/src/entities/subject/api/useSubjectQuery.ts b/src/entities/subject/api/useSubjectQuery.ts index dda864f24..63d0d8db5 100644 --- a/src/entities/subject/api/useSubjectQuery.ts +++ b/src/entities/subject/api/useSubjectQuery.ts @@ -4,12 +4,12 @@ type FetchFn = typeof subjectService.getSubjectById; type Options = QueryOptions; export const useSubjectQuery = >( - subjectId: string, + subjectId: string | null, options?: Options, ) => { return useBaseQuery( ['subjectDetails', { subjectId }], - () => subjectService.getSubjectById({ subjectId }), + () => subjectService.getSubjectById({ subjectId: String(subjectId) }), options, ); }; diff --git a/src/features/AutoCompletion/model/hooks/useAutoCompletionRecord.ts b/src/features/AutoCompletion/model/hooks/useAutoCompletionRecord.ts index c81d3a0d3..0eba10f6d 100644 --- a/src/features/AutoCompletion/model/hooks/useAutoCompletionRecord.ts +++ b/src/features/AutoCompletion/model/hooks/useAutoCompletionRecord.ts @@ -7,11 +7,16 @@ import { useAppSelector } from '~/shared/utils'; type Props = { entityId: string; eventId: string; + targetSubjectId: string | null; }; -export const useAutoCompletionRecord = ({ entityId, eventId }: Props): AutoCompletion | null => { +export const useAutoCompletionRecord = ({ + entityId, + eventId, + targetSubjectId, +}: Props): AutoCompletion | null => { const state = useAppSelector((state) => - selectAutoCompletionRecord(state, getProgressId(entityId, eventId)), + selectAutoCompletionRecord(state, getProgressId(entityId, eventId, targetSubjectId)), ); return state ?? null; diff --git a/src/features/AutoCompletion/model/hooks/useAutoCompletionStateManager.ts b/src/features/AutoCompletion/model/hooks/useAutoCompletionStateManager.ts index 3c1301bfb..0adbe0414 100644 --- a/src/features/AutoCompletion/model/hooks/useAutoCompletionStateManager.ts +++ b/src/features/AutoCompletion/model/hooks/useAutoCompletionStateManager.ts @@ -7,6 +7,7 @@ import { useAppDispatch } from '~/shared/utils'; export type ActivitySuccessfullySubmitted = { entityId: string; eventId: string; + targetSubjectId: string | null; activityId: string; }; @@ -26,7 +27,7 @@ export const useAutoCompletionStateManager = () => { ); const removeAutoCompletion = useCallback( - (payload: { entityId: string; eventId: string }) => { + (payload: { entityId: string; eventId: string; targetSubjectId: string | null }) => { dispatch(actions.removeAutoCompletion(payload)); }, [dispatch], diff --git a/src/features/AutoCompletion/model/slice.ts b/src/features/AutoCompletion/model/slice.ts index 123d19c82..7beb700f8 100644 --- a/src/features/AutoCompletion/model/slice.ts +++ b/src/features/AutoCompletion/model/slice.ts @@ -5,6 +5,7 @@ import { getProgressId } from '~/abstract/lib'; type DefaultProps = { entityId: string; eventId: string; + targetSubjectId: string | null; }; export type SetAutoCompletionPayload = DefaultProps & { @@ -32,8 +33,8 @@ const slice = createSlice({ }, setAutoCompletion(state, action: PayloadAction) { - const { entityId, eventId, autoCompletion } = action.payload; - const progressId = getProgressId(entityId, eventId); + const { entityId, eventId, targetSubjectId, autoCompletion } = action.payload; + const progressId = getProgressId(entityId, eventId, targetSubjectId); const record = state[progressId]; @@ -43,8 +44,8 @@ const slice = createSlice({ }, removeAutoCompletion(state, action: PayloadAction) { - const { entityId, eventId } = action.payload; - const progressId = getProgressId(entityId, eventId); + const { entityId, eventId, targetSubjectId } = action.payload; + const progressId = getProgressId(entityId, eventId, targetSubjectId); delete state[progressId]; }, @@ -53,8 +54,8 @@ const slice = createSlice({ state, action: PayloadAction, ) { - const { entityId, eventId, activityId } = action.payload; - const progressId = getProgressId(entityId, eventId); + const { entityId, eventId, activityId, targetSubjectId } = action.payload; + const progressId = getProgressId(entityId, eventId, targetSubjectId); state[progressId].successfullySubmittedActivityIds.push(activityId); }, diff --git a/src/features/PassSurvey/hooks/useAnswers.ts b/src/features/PassSurvey/hooks/useAnswers.ts index 355b8ff28..46cbb68f0 100644 --- a/src/features/PassSurvey/hooks/useAnswers.ts +++ b/src/features/PassSurvey/hooks/useAnswers.ts @@ -31,6 +31,7 @@ export const useAnswerBuilder = (): AnswerBuilder => { const groupProgress = appletModel.hooks.useGroupProgressRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); const { getMultiInformantState, isInMultiInformantFlow } = @@ -73,21 +74,26 @@ export const useAnswerBuilder = (): AnswerBuilder => { const multiInformantState = getMultiInformantState(); if (isInMultiInformantFlow()) { + // Take Now flow answer.sourceSubjectId = multiInformantState?.sourceSubject?.id; answer.targetSubjectId = multiInformantState?.targetSubject?.id; + } else if (context.targetSubject) { + // Activity assignment + answer.targetSubjectId = context.targetSubject.id; } return answer; }, [ groupProgress, + context.encryption, context.event, context.appletId, context.appletVersion, context.flow, - context.encryption, context.publicAppletKey, context.integrations, + context.targetSubject, getMultiInformantState, isInMultiInformantFlow, ], diff --git a/src/features/PassSurvey/hooks/useItemTimer.ts b/src/features/PassSurvey/hooks/useItemTimer.ts index 369208b47..4742b70e9 100644 --- a/src/features/PassSurvey/hooks/useItemTimer.ts +++ b/src/features/PassSurvey/hooks/useItemTimer.ts @@ -11,6 +11,7 @@ const ONE_SEC = 1000; type Props = { activityId: string; eventId: string; + targetSubjectId: string | null; item: appletModel.ItemRecord; isSubmitModalOpen: boolean; onTimerEnd: () => void; @@ -27,6 +28,7 @@ export const useItemTimer = ({ onTimerEnd, activityId, eventId, + targetSubjectId, isSubmitModalOpen, }: Props): TimerSettings => { const prevItem = usePrevious(item); @@ -35,6 +37,7 @@ export const useItemTimer = ({ appletModel.hooks.useItemTimerState({ activityId, eventId, + targetSubjectId, itemId: item.id, }); diff --git a/src/features/PassSurvey/hooks/useStartSurvey.ts b/src/features/PassSurvey/hooks/useStartSurvey.ts index 66332f929..be8747547 100644 --- a/src/features/PassSurvey/hooks/useStartSurvey.ts +++ b/src/features/PassSurvey/hooks/useStartSurvey.ts @@ -13,6 +13,7 @@ import { type NavigateToEntityProps = { flowId: string | null; activityId: string; + targetSubjectId: string | null; entityType: EntityType; eventId: string; }; @@ -20,6 +21,7 @@ type NavigateToEntityProps = { type OnActivityCardClickProps = { activityId: string; eventId: string; + targetSubjectId: string | null; flowId: string | null; status: ActivityStatus; shouldRestart: boolean; @@ -45,7 +47,7 @@ export const useStartSurvey = (props: Props) => { appletModel.hooks.useMultiInformantState(); function navigateToEntity(params: NavigateToEntityProps) { - const { activityId, flowId, eventId, entityType } = params; + const { activityId, flowId, eventId, targetSubjectId, entityType } = params; if (props.isPublic && props.publicAppletKey) { return navigator.navigate( @@ -65,13 +67,20 @@ export const useStartSurvey = (props: Props) => { appletId, activityId, eventId, + targetSubjectId, entityType, flowId, }), ); } - function startSurvey({ activityId, flowId, eventId, shouldRestart }: OnActivityCardClickProps) { + function startSurvey({ + activityId, + flowId, + eventId, + targetSubjectId, + shouldRestart, + }: OnActivityCardClickProps) { const analyticsPayload: MixpanelPayload = { [MixpanelProps.AppletId]: props.applet.id, [MixpanelProps.ActivityId]: activityId, @@ -103,8 +112,8 @@ export const useStartSurvey = (props: Props) => { } if (shouldRestart) { - removeActivityProgress({ activityId, eventId }); - removeGroupProgress({ entityId: flowId, eventId }); + removeActivityProgress({ activityId, eventId, targetSubjectId }); + removeGroupProgress({ entityId: flowId, eventId, targetSubjectId }); } const activityIdToNavigate = shouldRestart ? firstActivityId : activityId; @@ -114,12 +123,13 @@ export const useStartSurvey = (props: Props) => { entityType: 'flow', eventId, flowId, + targetSubjectId, }); } if (shouldRestart) { - removeActivityProgress({ activityId, eventId }); - removeGroupProgress({ entityId: activityId, eventId }); + removeActivityProgress({ activityId, eventId, targetSubjectId }); + removeGroupProgress({ entityId: activityId, eventId, targetSubjectId }); } return navigateToEntity({ @@ -127,6 +137,7 @@ export const useStartSurvey = (props: Props) => { entityType: 'regular', eventId, flowId: null, + targetSubjectId, }); } diff --git a/src/features/PassSurvey/hooks/useSummaryData.ts b/src/features/PassSurvey/hooks/useSummaryData.ts index f97c6a47f..9d211563c 100644 --- a/src/features/PassSurvey/hooks/useSummaryData.ts +++ b/src/features/PassSurvey/hooks/useSummaryData.ts @@ -12,6 +12,7 @@ type Props = { activityName: string; eventId: string; flowId: string | null; + targetSubjectId: string | null; scoresAndReports?: ScoreAndReports; }; @@ -36,9 +37,10 @@ export const useSummaryData = (props: Props) => { const groupProgress = appletModel.hooks.useGroupProgressRecord({ entityId: props.flowId ? props.flowId : props.activityId, eventId: props.eventId, + targetSubjectId: props.targetSubjectId, }); - const progressId = getProgressId(props.activityId, props.eventId); + const progressId = getProgressId(props.activityId, props.eventId, props.targetSubjectId); const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress(state, progressId), diff --git a/src/features/PassSurvey/hooks/useSurveyDataQuery.ts b/src/features/PassSurvey/hooks/useSurveyDataQuery.ts index 58c96b494..8b11e7c29 100644 --- a/src/features/PassSurvey/hooks/useSurveyDataQuery.ts +++ b/src/features/PassSurvey/hooks/useSurveyDataQuery.ts @@ -1,6 +1,9 @@ +import { UseQueryResult } from '@tanstack/react-query'; + import { useActivityByIdQuery } from '~/entities/activity'; import { useAppletByIdQuery } from '~/entities/applet'; import { useEventsbyAppletIdQuery } from '~/entities/event'; +import { useSubjectQuery } from '~/entities/subject'; import { ActivityDTO, AppletDTO, @@ -8,12 +11,15 @@ import { BaseError, RespondentMetaDTO, } from '~/shared/api'; +import { SubjectDTO } from '~/shared/api/types/subject'; +import { useFeatureFlags } from '~/shared/utils'; type Return = { appletDTO: AppletDTO | null; respondentMeta?: RespondentMetaDTO; activityDTO: ActivityDTO | null; eventsDTO: AppletEventsResponse | null; + targetSubject: SubjectDTO | null; isError: boolean; isLoading: boolean; error: BaseError | null; @@ -23,10 +29,13 @@ type Props = { publicAppletKey: string | null; appletId: string; activityId: string; + targetSubjectId: string | null; }; export const useSurveyDataQuery = (props: Props): Return => { - const { appletId, activityId, publicAppletKey } = props; + const { appletId, activityId, publicAppletKey, targetSubjectId } = props; + const { featureFlags } = useFeatureFlags(); + const isAssignmentsEnabled = !!featureFlags?.enableActivityAssign && !!targetSubjectId; const { data: appletById, @@ -58,13 +67,26 @@ export const useSurveyDataQuery = (props: Props): Return => { { select: (data) => data?.data?.result }, ); + const subjectQueryResult = useSubjectQuery(targetSubjectId, { + select: (data) => data.data.result, + enabled: isAssignmentsEnabled, + }); + + const { + data: targetSubject, + isError: isSubjectError, + isLoading: isSubjectLoading, + error: subjectError, + } = isAssignmentsEnabled ? subjectQueryResult : ({} as UseQueryResult); + return { appletDTO: appletById?.result ?? null, respondentMeta: appletById?.respondentMeta, activityDTO: activityById ?? null, eventsDTO: eventsByIdData ?? null, - isError: isAppletError || isActivityError || isEventsError, - isLoading: isAppletLoading || isActivityLoading || isEventsLoading, - error: appletError ?? activityError ?? eventsError, + targetSubject: targetSubject ?? null, + isError: isAppletError || isActivityError || isEventsError || isSubjectError, + isLoading: isAppletLoading || isActivityLoading || isEventsLoading || isSubjectLoading, + error: appletError ?? activityError ?? eventsError ?? subjectError, }; }; diff --git a/src/features/PassSurvey/lib/SurveyContext.ts b/src/features/PassSurvey/lib/SurveyContext.ts index 5b72041bb..9511d3a71 100644 --- a/src/features/PassSurvey/lib/SurveyContext.ts +++ b/src/features/PassSurvey/lib/SurveyContext.ts @@ -7,6 +7,7 @@ import { RespondentMetaDTO, ScheduleEventDto, } from '~/shared/api'; +import { SubjectDTO } from '~/shared/api/types/subject'; export type SurveyContext = { appletId: string; @@ -15,6 +16,7 @@ export type SurveyContext = { activityId: string; eventId: string; + targetSubject: SubjectDTO | null; entityId: string; diff --git a/src/features/PassSurvey/lib/mappers.ts b/src/features/PassSurvey/lib/mappers.ts index 4fe869b74..e598f6295 100644 --- a/src/features/PassSurvey/lib/mappers.ts +++ b/src/features/PassSurvey/lib/mappers.ts @@ -7,12 +7,14 @@ import { AppletEventsResponse, RespondentMetaDTO, } from '~/shared/api'; +import { SubjectDTO } from '~/shared/api/types/subject'; type Props = { appletDTO: AppletDTO | null; eventsDTO: AppletEventsResponse | null; respondentMeta?: RespondentMetaDTO; activityDTO: ActivityDTO | null; + targetSubject: SubjectDTO | null; currentEventId: string; flowId: string | null; @@ -20,7 +22,7 @@ type Props = { }; export const mapRawDataToSurveyContext = (props: Props): SurveyContext => { - const { appletDTO, eventsDTO, activityDTO, currentEventId, flowId } = props; + const { appletDTO, eventsDTO, activityDTO, currentEventId, flowId, targetSubject } = props; if (!appletDTO || !eventsDTO || !activityDTO) { throw new Error('[MapRawDataToSurveyContext] Missing required data'); @@ -51,6 +53,7 @@ export const mapRawDataToSurveyContext = (props: Props): SurveyContext => { activity: activityDTO, event, + targetSubject, respondentMeta: props.respondentMeta, encryption: appletDTO.encryption, diff --git a/src/features/PassSurvey/ui/EntityTimer.tsx b/src/features/PassSurvey/ui/EntityTimer.tsx index 0fe939a40..b3c426e44 100644 --- a/src/features/PassSurvey/ui/EntityTimer.tsx +++ b/src/features/PassSurvey/ui/EntityTimer.tsx @@ -29,7 +29,7 @@ export const EntityTimer = ({ entityTimerSettings }: Props) => { const group = useAppSelector((state) => appletModel.selectors.selectGroupProgress( state, - getProgressId(context.entityId, context.eventId), + getProgressId(context.entityId, context.eventId, context.targetSubject?.id), ), ); diff --git a/src/pages/AutoCompletion/index.tsx b/src/pages/AutoCompletion/index.tsx index 348c29e76..b93be0caa 100644 --- a/src/pages/AutoCompletion/index.tsx +++ b/src/pages/AutoCompletion/index.tsx @@ -9,6 +9,7 @@ function AutoCompletion() { const activityId = searchParams.get('activityId'); const eventId = searchParams.get('eventId'); const flowId = searchParams.get('flowId'); + const targetSubjectId = searchParams.get('targetSubjectId'); if (!appletId || !activityId || !eventId) { return
Invalid URL
; @@ -20,6 +21,7 @@ function AutoCompletion() { activityId={activityId} eventId={eventId} flowId={flowId} + targetSubjectId={targetSubjectId} publicAppletKey={null} /> ); diff --git a/src/pages/PublicAutoCompletion/index.tsx b/src/pages/PublicAutoCompletion/index.tsx index ae636747b..c39eb8ba9 100644 --- a/src/pages/PublicAutoCompletion/index.tsx +++ b/src/pages/PublicAutoCompletion/index.tsx @@ -21,6 +21,7 @@ function PublicAutoCompletion() { activityId={activityId} eventId={eventId} flowId={flowId} + targetSubjectId={null} publicAppletKey={publicAppletKey} /> ); diff --git a/src/pages/PublicSurvey/index.tsx b/src/pages/PublicSurvey/index.tsx index 26155f77a..ed9391b56 100644 --- a/src/pages/PublicSurvey/index.tsx +++ b/src/pages/PublicSurvey/index.tsx @@ -30,6 +30,7 @@ function PublicSurvey() { activityId={activityId} eventId={eventId} flowId={flowId} + targetSubjectId={null} publicAppletKey={publicAppletKey} /> diff --git a/src/pages/Survey/index.tsx b/src/pages/Survey/index.tsx index 831bc2f1a..461d6e58a 100644 --- a/src/pages/Survey/index.tsx +++ b/src/pages/Survey/index.tsx @@ -12,12 +12,8 @@ function SurveyPage() { const [searchParams] = useSearchParams(); const isFlow = entityType === 'flow'; - - let flowId: string | null = null; - - if (isFlow) { - flowId = searchParams.get('flowId'); - } + const flowId = isFlow ? searchParams.get('flowId') : null; + const targetSubjectId = searchParams.get('targetSubjectId'); if (!appletId || !activityId || !eventId) { return
{t('wrondLinkParametrError')}
; @@ -30,6 +26,7 @@ function SurveyPage() { activityId={activityId} eventId={eventId} flowId={flowId} + targetSubjectId={targetSubjectId} publicAppletKey={null} /> diff --git a/src/shared/api/types/activity.ts b/src/shared/api/types/activity.ts index 32354f88c..67d711bb8 100644 --- a/src/shared/api/types/activity.ts +++ b/src/shared/api/types/activity.ts @@ -222,6 +222,7 @@ export type CompletedEntityDTO = { id: string; answerId: string; submitId: string; + targetSubjectId: string | null; scheduledEventId: string; localEndDate: string; localEndTime: string; diff --git a/src/shared/constants/routes.ts b/src/shared/constants/routes.ts index 0af64c084..dec08e833 100644 --- a/src/shared/constants/routes.ts +++ b/src/shared/constants/routes.ts @@ -86,16 +86,21 @@ const ROUTES = { entityType, eventId, flowId, + targetSubjectId, }: { appletId: string; activityId: string; eventId: string; entityType: 'regular' | 'flow'; flowId: string | null; - }) => - `/protected/applets/${appletId}/activityId/${activityId}/event/${eventId}/entityType/${entityType}?${ - flowId ? `flowId=${flowId}` : '' - }`, + targetSubjectId: string | null; + }) => { + const params = new URLSearchParams(); + if (flowId) params.append('flowId', flowId); + if (targetSubjectId) params.append('targetSubjectId', targetSubjectId); + + return `/protected/applets/${appletId}/activityId/${activityId}/event/${eventId}/entityType/${entityType}?${params.toString()}`; + }, }, invitationAccept: { path: '/protected/invite/accepted', @@ -112,14 +117,25 @@ const ROUTES = { activityId, flowId, publicAppletKey, + targetSubjectId, }: { appletId: string; activityId: string; eventId: string; flowId: string | null; publicAppletKey: string | null; - }) => - `${ROUTES.autoCompletion.path}?appletId=${appletId}&eventId=${eventId}&activityId=${activityId}${flowId ? `&flowId=${flowId}` : ''}${publicAppletKey ? `&publicAppletKey=${publicAppletKey}` : ''}`, + targetSubjectId: string | null; + }) => { + const params = new URLSearchParams(); + params.append('appletId', appletId); + params.append('eventId', eventId); + params.append('activityId', activityId); + if (flowId) params.append('flowId', flowId); + if (publicAppletKey) params.append('publicAppletKey', publicAppletKey); + if (targetSubjectId) params.append('targetSubjectId', targetSubjectId); + + return `${ROUTES.autoCompletion.path}?${params.toString()}`; + }, }, }; diff --git a/src/shared/utils/hooks/index.ts b/src/shared/utils/hooks/index.ts index c2d91d36a..d1ca4c2f3 100644 --- a/src/shared/utils/hooks/index.ts +++ b/src/shared/utils/hooks/index.ts @@ -12,3 +12,4 @@ export * from './useForceUpdate'; export * from './usePrevious'; export * from './useIntersectionObserver'; export * from './useWindowFocus'; +export * from './useFeatureFlags'; diff --git a/src/widgets/ActivityGroups/lib/AppletDetailsContext.ts b/src/widgets/ActivityGroups/lib/AppletDetailsContext.ts index 45ecd79f7..47494eb33 100644 --- a/src/widgets/ActivityGroups/lib/AppletDetailsContext.ts +++ b/src/widgets/ActivityGroups/lib/AppletDetailsContext.ts @@ -5,8 +5,8 @@ import { AppletBaseDTO, AppletEventsResponse, MyAssignmentsDTO } from '~/shared/ type AppletDetailsContextProps = { applet: AppletBaseDTO; events: AppletEventsResponse; - /** Activity assignments array - undefined if activity assign is disabled */ - assignments?: MyAssignmentsDTO['assignments']; + /** Activity assignments array - null if activity assign is disabled */ + assignments: MyAssignmentsDTO['assignments'] | null; }; type Public = { diff --git a/src/widgets/ActivityGroups/model/hooks/useActivityGroups.ts b/src/widgets/ActivityGroups/model/hooks/useActivityGroups.ts index a800c9f63..d543a079a 100644 --- a/src/widgets/ActivityGroups/model/hooks/useActivityGroups.ts +++ b/src/widgets/ActivityGroups/model/hooks/useActivityGroups.ts @@ -8,7 +8,7 @@ import { useAppSelector } from '~/shared/utils'; type Props = { applet: AppletBaseDTO; events: AppletEventsResponse; - assignments?: HydratedAssignmentDTO[]; + assignments: HydratedAssignmentDTO[] | null; }; type Return = { diff --git a/src/widgets/ActivityGroups/model/hooks/useEntitiesSync.ts b/src/widgets/ActivityGroups/model/hooks/useEntitiesSync.ts index 644f16dcd..a8ab2c511 100644 --- a/src/widgets/ActivityGroups/model/hooks/useEntitiesSync.ts +++ b/src/widgets/ActivityGroups/model/hooks/useEntitiesSync.ts @@ -21,16 +21,19 @@ export const useEntitiesSync = (props: FilterCompletedEntitiesProps) => { const entityId = entity.id; const eventId = entity.scheduledEventId; + const targetSubjectId = entity.targetSubjectId; const groupProgress = getGroupProgress({ entityId, eventId, + targetSubjectId, }); if (!groupProgress) { return saveGroupProgress({ activityId: entityId, eventId, + targetSubjectId, progressPayload: { type: ActivityPipelineType.Regular, startAt: null, @@ -52,6 +55,7 @@ export const useEntitiesSync = (props: FilterCompletedEntitiesProps) => { return saveGroupProgress({ activityId: entityId, eventId, + targetSubjectId, progressPayload: { ...groupProgress, endAt: new Date(endAtDate).getTime(), diff --git a/src/widgets/ActivityGroups/model/services/ActivityGroupsBuildManager.ts b/src/widgets/ActivityGroups/model/services/ActivityGroupsBuildManager.ts index 0c9b49af4..2f3eab915 100644 --- a/src/widgets/ActivityGroups/model/services/ActivityGroupsBuildManager.ts +++ b/src/widgets/ActivityGroups/model/services/ActivityGroupsBuildManager.ts @@ -24,7 +24,7 @@ type BuildResult = { type ProcessParams = { activities: ActivityBaseDTO[]; flows: ActivityFlowDTO[]; - assignments: HydratedAssignmentDTO[] | undefined; + assignments: HydratedAssignmentDTO[] | null; events: AppletEventsResponse; entityProgress: GroupProgressState; }; @@ -116,7 +116,7 @@ const createActivityGroupsBuildManager = () => { // Add auto-assignment if enabled for this activity/flow if (entity.autoAssign) { - eventEntities.push({ entity, event }); + eventEntities.push({ entity, event, targetSubject: null }); } // Add any additional assignments (without duplicating possible auto-assignment) @@ -124,13 +124,18 @@ const createActivityGroupsBuildManager = () => { const isSelfAssign = respondentSubject.id === targetSubject.id; if (entity.autoAssign && isSelfAssign) continue; - eventEntities.push(isSelfAssign ? { entity, event } : { entity, event, targetSubject }); + eventEntities.push({ + entity, + event, + targetSubject: isSelfAssign ? null : targetSubject, + }); } } else { // Assignments disabled eventEntities.push({ entity, event, + targetSubject: null, }); } } diff --git a/src/widgets/ActivityGroups/ui/ActivityCard/index.tsx b/src/widgets/ActivityGroups/ui/ActivityCard/index.tsx index 64c5bba85..428a27a3e 100644 --- a/src/widgets/ActivityGroups/ui/ActivityCard/index.tsx +++ b/src/widgets/ActivityGroups/ui/ActivityCard/index.tsx @@ -51,7 +51,11 @@ export const ActivityCard = ({ activityListItem }: Props) => { applet: context.applet, }); - const activityEventId = getProgressId(activityListItem.activityId, activityListItem.eventId); + const activityEventId = getProgressId( + activityListItem.activityId, + activityListItem.eventId, + activityListItem.targetSubject?.id, + ); const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress(state, activityEventId), @@ -60,6 +64,7 @@ export const ActivityCard = ({ activityListItem }: Props) => { const autoCompletionRecord = useAutoCompletionRecord({ entityId: activityListItem.flowId ?? activityListItem.activityId, eventId: activityListItem.eventId, + targetSubjectId: activityListItem.targetSubject?.id ?? null, }); const step = activityProgress?.step || 0; @@ -101,6 +106,7 @@ export const ActivityCard = ({ activityListItem }: Props) => { return startSurvey({ activityId: activityListItem.activityId, eventId: activityListItem.eventId, + targetSubjectId: activityListItem.targetSubject?.id ?? null, status: activityListItem.status, flowId: activityListItem.flowId, shouldRestart, diff --git a/src/widgets/ActivityGroups/ui/index.tsx b/src/widgets/ActivityGroups/ui/index.tsx index 7e6faa558..38355db71 100644 --- a/src/widgets/ActivityGroups/ui/index.tsx +++ b/src/widgets/ActivityGroups/ui/index.tsx @@ -13,7 +13,7 @@ import { TakeNowSuccessModal } from '~/features/TakeNow/ui/TakeNowSuccessModal'; import Box from '~/shared/ui/Box'; import Loader from '~/shared/ui/Loader'; import { useCustomTranslation, useOnceEffect } from '~/shared/utils'; -import { useFeatureFlags } from '~/shared/utils/hooks/useFeatureFlags'; +import { useFeatureFlags } from '~/shared/utils/hooks'; type PublicAppletDetails = { isPublic: true; @@ -57,7 +57,7 @@ export const ActivityGroups = (props: Props) => { const { isError: isAssignmentsError, isLoading: isAssignmentsLoading, - data: assignments, + data: assignments = null, } = useMyAssignmentsQuery(isAssignmentsEnabled ? props.appletId : undefined, { select: (data) => data.data.result.assignments, enabled: isAssignmentsEnabled, diff --git a/src/widgets/AutoCompletion/lib/useAutoCompletion.ts b/src/widgets/AutoCompletion/lib/useAutoCompletion.ts index 0244d9cbb..3ac9bce38 100644 --- a/src/widgets/AutoCompletion/lib/useAutoCompletion.ts +++ b/src/widgets/AutoCompletion/lib/useAutoCompletion.ts @@ -15,6 +15,7 @@ export const useAutoCompletion = () => { const completionState = AutoCompletionModel.useAutoCompletionRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); const { activitySuccessfullySubmitted } = AutoCompletionModel.useAutoCompletionStateManager(); @@ -50,6 +51,7 @@ export const useAutoCompletion = () => { activityProgress: getActivityProgress({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }), answerBuilder, }, @@ -72,6 +74,7 @@ export const useAutoCompletion = () => { activitySuccessfullySubmitted({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, activityId, }); } @@ -88,6 +91,7 @@ export const useAutoCompletion = () => { context.entityId, context.eventId, context.publicAppletKey, + context.targetSubject?.id, getActivityProgress, submitAnswersAsync, ]); diff --git a/src/widgets/AutoCompletion/ui/AutoCompletionScreen.tsx b/src/widgets/AutoCompletion/ui/AutoCompletionScreen.tsx index ad774e7a6..ad9ef3d33 100644 --- a/src/widgets/AutoCompletion/ui/AutoCompletionScreen.tsx +++ b/src/widgets/AutoCompletion/ui/AutoCompletionScreen.tsx @@ -45,16 +45,19 @@ export const AutoCompletionScreen = () => { removeAutoCompletion({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); entityCompleted({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); removeActivityProgress({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); if (context.publicAppletKey) { @@ -74,6 +77,7 @@ export const AutoCompletionScreen = () => { context.entityId, context.eventId, context.publicAppletKey, + context.targetSubject?.id, entityCompleted, navigator, removeActivityProgress, diff --git a/src/widgets/AutoCompletion/ui/index.tsx b/src/widgets/AutoCompletion/ui/index.tsx index 3693f5d7a..0ff6e319f 100644 --- a/src/widgets/AutoCompletion/ui/index.tsx +++ b/src/widgets/AutoCompletion/ui/index.tsx @@ -11,18 +11,28 @@ type Props = { appletId: string; activityId: string; eventId: string; + targetSubjectId: string | null; flowId: string | null; publicAppletKey: string | null; }; function SurveyAutoCompletionWidget(props: Props) { - const { appletDTO, activityDTO, eventsDTO, isLoading, isError, error, respondentMeta } = - useSurveyDataQuery({ - appletId: props.appletId, - activityId: props.activityId, - publicAppletKey: props.publicAppletKey, - }); + const { + appletDTO, + respondentMeta, + activityDTO, + eventsDTO, + targetSubject, + isError, + isLoading, + error, + } = useSurveyDataQuery({ + appletId: props.appletId, + activityId: props.activityId, + publicAppletKey: props.publicAppletKey, + targetSubjectId: props.targetSubjectId, + }); if (isLoading) { return ; @@ -40,6 +50,7 @@ function SurveyAutoCompletionWidget(props: Props) { eventsDTO, currentEventId: props.eventId, flowId: props.flowId, + targetSubject, publicAppletKey: props.publicAppletKey, respondentMeta, })} diff --git a/src/widgets/Survey/model/hooks/useEntityTimer.ts b/src/widgets/Survey/model/hooks/useEntityTimer.ts index 1e98e3eb9..351ae22a5 100644 --- a/src/widgets/Survey/model/hooks/useEntityTimer.ts +++ b/src/widgets/Survey/model/hooks/useEntityTimer.ts @@ -15,12 +15,13 @@ export const useEntityTimer = ({ onFinish }: Props) => { const groupProgress = appletModel.hooks.useGroupProgressRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, }); const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress( state, - getProgressId(context.activityId, context.eventId), + getProgressId(context.activityId, context.eventId, context.targetSubject?.id), ), ); diff --git a/src/widgets/Survey/ui/PassingScreen.tsx b/src/widgets/Survey/ui/PassingScreen.tsx index f3b2c60ff..855f78417 100644 --- a/src/widgets/Survey/ui/PassingScreen.tsx +++ b/src/widgets/Survey/ui/PassingScreen.tsx @@ -36,21 +36,25 @@ const PassingScreen = (props: Props) => { const context = useContext(SurveyContext); + const targetSubjectId = context.targetSubject?.id ?? null; + const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress( state, - getProgressId(context.activityId, context.eventId), + getProgressId(context.activityId, context.eventId, context.targetSubject?.id), ), ); const groupProgress = appletModel.hooks.useGroupProgressRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId, }); const autoCompletionState = AutoCompletionModel.useAutoCompletionRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId, }); const { saveSummaryDataInContext } = appletModel.hooks.useGroupProgressStateManager(); @@ -66,18 +70,21 @@ const PassingScreen = (props: Props) => { appletModel.hooks.useUserEvents({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId, }); const { saveItemAnswer, saveItemAdditionalText, removeItemAnswer } = appletModel.hooks.useSaveItemAnswer({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId, }); const { getSummaryForCurrentActivity } = useSummaryData({ activityId: context.activityId, activityName: context.activity.name, eventId: context.eventId, + targetSubjectId, scoresAndReports: context.activity.scoresAndReports, flowId: null, }); @@ -92,6 +99,7 @@ const PassingScreen = (props: Props) => { const { completeActivity, completeFlow } = appletModel.hooks.useEntityComplete({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId, publicAppletKey: context.publicAppletKey, flowId: context.flow?.id ?? null, appletId: context.appletId, @@ -141,6 +149,7 @@ const PassingScreen = (props: Props) => { saveSummaryDataInContext({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId, activityId: context.activityId, summaryData: summaryDataContext, @@ -160,14 +169,22 @@ const PassingScreen = (props: Props) => { if (isFlowGroup && context.flow) { if (isLastActivity && hasAnySummaryScreenResults) { - return openSummaryScreen({ activityId: context.activityId, eventId: context.eventId }); + return openSummaryScreen({ + activityId: context.activityId, + eventId: context.eventId, + targetSubjectId, + }); } return completeFlow(); } if (isSummaryScreenOn && isSummaryDataExist) { - return openSummaryScreen({ activityId: context.activityId, eventId: context.eventId }); + return openSummaryScreen({ + activityId: context.activityId, + eventId: context.eventId, + targetSubjectId, + }); } return completeActivity(); @@ -202,13 +219,18 @@ const PassingScreen = (props: Props) => { saveUserEventByType('NEXT', item); } - return incrementStep({ activityId: context.activityId, eventId: context.eventId }); + return incrementStep({ + activityId: context.activityId, + eventId: context.eventId, + targetSubjectId, + }); }, [ item, context.activity.isSkippable, context.activityId, context.eventId, incrementStep, + targetSubjectId, saveUserEventByType, ]); @@ -219,8 +241,20 @@ const PassingScreen = (props: Props) => { return; } - return decrementStep({ activityId: context.activityId, eventId: context.eventId }); - }, [context.activityId, context.eventId, decrementStep, hasPrevStep, item, saveUserEventByType]); + return decrementStep({ + activityId: context.activityId, + eventId: context.eventId, + targetSubjectId, + }); + }, [ + context.activityId, + context.eventId, + decrementStep, + hasPrevStep, + item, + saveUserEventByType, + targetSubjectId, + ]); const onMoveForward = useCallback(() => { if (!item) { @@ -285,6 +319,7 @@ const PassingScreen = (props: Props) => { item, activityId: context.activityId, eventId: context.eventId, + targetSubjectId, isSubmitModalOpen, onTimerEnd: hasNextStep ? onNext : openSubmitModal, }); diff --git a/src/widgets/Survey/ui/ScreenManager.tsx b/src/widgets/Survey/ui/ScreenManager.tsx index a7981cfd0..34eb8226c 100644 --- a/src/widgets/Survey/ui/ScreenManager.tsx +++ b/src/widgets/Survey/ui/ScreenManager.tsx @@ -21,7 +21,7 @@ export const ScreenManager = ({ openTimesUpModal }: Props) => { const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress( state, - getProgressId(context.activityId, context.eventId), + getProgressId(context.activityId, context.eventId, context.targetSubject?.id), ), ); @@ -38,6 +38,7 @@ export const ScreenManager = ({ openTimesUpModal }: Props) => { saveAutoCompletion({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, autoCompletion: { activityIdsToSubmit: activitiesToSubmit, successfullySubmittedActivityIds: [], @@ -51,6 +52,7 @@ export const ScreenManager = ({ openTimesUpModal }: Props) => { context.entityId, context.eventId, context.flow, + context.targetSubject?.id, openTimesUpModal, saveAutoCompletion, ]); diff --git a/src/widgets/Survey/ui/SummaryScreen/index.tsx b/src/widgets/Survey/ui/SummaryScreen/index.tsx index 42eeecadb..15a9e2adb 100644 --- a/src/widgets/Survey/ui/SummaryScreen/index.tsx +++ b/src/widgets/Survey/ui/SummaryScreen/index.tsx @@ -27,13 +27,14 @@ const SummaryScreen = () => { const activityProgress = useAppSelector((state) => appletModel.selectors.selectActivityProgress( state, - getProgressId(context.activityId, context.eventId), + getProgressId(context.activityId, context.eventId, context.targetSubject?.id), ), ); const { completeActivity, completeFlow } = appletModel.hooks.useEntityComplete({ eventId: context.eventId, activityId: context.activityId, + targetSubjectId: context.targetSubject?.id ?? null, publicAppletKey: context.publicAppletKey, flowId: context.flow?.id ?? null, appletId: context.appletId, @@ -47,6 +48,7 @@ const SummaryScreen = () => { const { summaryData } = useSummaryData({ activityId: context.activityId, eventId: context.eventId, + targetSubjectId: context.targetSubject?.id ?? null, activityName: context.activity.name, scoresAndReports: activityProgress.scoreSettings, flowId: context.flow?.id ?? null, diff --git a/src/widgets/Survey/ui/WelcomeScreen.tsx b/src/widgets/Survey/ui/WelcomeScreen.tsx index 6599b9d36..f76597632 100644 --- a/src/widgets/Survey/ui/WelcomeScreen.tsx +++ b/src/widgets/Survey/ui/WelcomeScreen.tsx @@ -23,12 +23,13 @@ const WelcomeScreen = () => { const { setInitialProgress } = appletModel.hooks.useActivityProgress(); const isFlow = !!context.flow; - const isTimedActivity = !!context.event?.timers?.timer; + const targetSubjectId = context.targetSubject?.id ?? null; const groupProgress = appletModel.hooks.useGroupProgressRecord({ entityId: context.entityId, eventId: context.eventId, + targetSubjectId, }); const startAssessment = useCallback(() => { @@ -37,19 +38,24 @@ const WelcomeScreen = () => { const isGroupStarted = isGroupDefined && groupProgress.startAt && !groupProgress.endAt; if (context.flow && !isGroupStarted) { - startFlow(context.eventId, context.flow); + startFlow(context.eventId, context.flow, targetSubjectId); } if (!context.flow) { - startActivity(context.activityId, context.eventId); + startActivity(context.activityId, context.eventId, targetSubjectId); } - return setInitialProgress({ activity: context.activity, eventId: context.eventId }); + return setInitialProgress({ + activity: context.activity, + eventId: context.eventId, + targetSubjectId, + }); }, [ context.activity, context.activityId, context.eventId, context.flow, + targetSubjectId, groupProgress, setInitialProgress, startActivity, diff --git a/src/widgets/Survey/ui/index.tsx b/src/widgets/Survey/ui/index.tsx index 79dda728c..ff9dbc668 100644 --- a/src/widgets/Survey/ui/index.tsx +++ b/src/widgets/Survey/ui/index.tsx @@ -21,12 +21,13 @@ type Props = { appletId: string; activityId: string; eventId: string; + targetSubjectId: string | null; flowId: string | null; }; export const SurveyWidget = (props: Props) => { - const { publicAppletKey, appletId, activityId, eventId, flowId } = props; + const { publicAppletKey, appletId, activityId, eventId, flowId, targetSubjectId } = props; const { t } = useCustomTranslation(); const navigator = useCustomNavigation(); @@ -36,6 +37,7 @@ export const SurveyWidget = (props: Props) => { const autoCompletionState = AutoCompletionModel.useAutoCompletionRecord({ entityId: props.flowId ?? props.activityId, eventId: props.eventId, + targetSubjectId, }); const isTimesUpModalModalByDefault: boolean = @@ -60,6 +62,7 @@ export const SurveyWidget = (props: Props) => { activityId, flowId, eventId, + targetSubjectId, publicAppletKey, }; @@ -68,10 +71,18 @@ export const SurveyWidget = (props: Props) => { : ROUTES.autoCompletion.navigateTo(navigateToProps); return navigator.navigate(screenToNavigate); - }, [closeTimesUpModal, navigator, props]); - - const { activityDTO, appletDTO, eventsDTO, respondentMeta, isLoading, isError, error } = - useSurveyDataQuery({ publicAppletKey, appletId, activityId }); + }, [closeTimesUpModal, navigator, props, targetSubjectId]); + + const { + activityDTO, + appletDTO, + eventsDTO, + respondentMeta, + targetSubject, + isLoading, + isError, + error, + } = useSurveyDataQuery({ publicAppletKey, appletId, activityId, targetSubjectId }); if (isLoading) { return ; @@ -108,6 +119,7 @@ export const SurveyWidget = (props: Props) => { respondentMeta, currentEventId: eventId, flowId, + targetSubject, publicAppletKey, })} >