diff options
Diffstat (limited to 'src/models')
| -rw-r--r-- | src/models/answerSlice.ts | 48 | ||||
| -rw-r--r-- | src/models/appSlice.ts | 6 | ||||
| -rw-r--r-- | src/models/questionSlice.ts | 72 |
3 files changed, 108 insertions, 18 deletions
diff --git a/src/models/answerSlice.ts b/src/models/answerSlice.ts index e0f9931..aada48c 100644 --- a/src/models/answerSlice.ts +++ b/src/models/answerSlice.ts @@ -2,7 +2,9 @@ import {createSelector, createSlice, PayloadAction} from '@reduxjs/toolkit' // import {Answer} from "@/models/answer.ts"; // import {stringify} from "querystring"; import {RootState} from '@/store'; // your root state type -import {PatternValidation, UpkQuestion} from "@/api"; +import {PatternValidation} from "@/api"; +import {assert} from "@/lib/utils.ts" +import {ProfileQuestion} from "@/models/questionSlice.ts" export interface Answer { values: string[]; @@ -23,7 +25,7 @@ const answerSlice = createSlice({ name: 'answers', initialState, reducers: { - addAnswer(state, action: PayloadAction<{ question: UpkQuestion, val: string }>) { + addAnswer(state, action: PayloadAction<{ question: ProfileQuestion, val: string }>) { /* If the question is MC, validate: - validate selector SA vs MA (1 selected vs >1 selected) - the answers match actual codes in the choices @@ -34,7 +36,7 @@ const answerSlice = createSlice({ - configuration.max_length - validation.patterns */ - let question: UpkQuestion = action.payload.question; + let question: ProfileQuestion = action.payload.question; let val: string = action.payload.val.trim(); let answer: Answer = state[question.question_id] ?? { values: [], @@ -140,31 +142,55 @@ const answerSlice = createSlice({ state[question.question_id] = answer }, - saveAnswer(state, action: PayloadAction<{ question: UpkQuestion }>) { - let question: UpkQuestion = action.payload.question; - let answer: Answer = state[question.question_id] + submitAnswer(state, action: PayloadAction<{ question: ProfileQuestion }>) { + const question: ProfileQuestion = action.payload.question; + const answer: Answer = state[question.question_id] + + assert(!answer.complete, "Can't submit completed Answer") + assert(!answer.processing, "Can't submit processing Answer") + assert(answer.error_msg.length == 0, "Can't submit Answer with error message") state[question.question_id] = { 'values': answer.values, - 'error_msg': "", - 'processing': false, + 'error_msg': answer.error_msg, + 'processing': true, 'complete': false } as Answer + }, + + saveAnswer(state, action: PayloadAction<{ question: ProfileQuestion }>) { + const question: ProfileQuestion = action.payload.question; + const answer: Answer = state[question.question_id] + + assert(!answer.complete, "Can't submit completed Answer") + assert(answer.processing, "Answer must be processing") + console.assert(answer.error_msg.length == 0, "Can't submit Answer with error message") + + state[question.question_id] = { + 'values': answer.values, + 'error_msg': answer.error_msg, + 'processing': false, + 'complete': true + } as Answer } } }) -export const {addAnswer, saveAnswer} = answerSlice.actions; +export const { + addAnswer, + saveAnswer, + submitAnswer +} = answerSlice.actions; export default answerSlice.reducer -export const answerForQuestion = (state: RootState, question: UpkQuestion) => state.answers[question.question_id] ?? { +export const answerForQuestion = (state: RootState, question: ProfileQuestion) => state.answers[question.question_id] ?? { values: [], error_msg: "", complete: false, processing: false } as Answer; -export const makeSelectChoicesByQuestion = (question: UpkQuestion) => +export const makeSelectChoicesByQuestion = (question: ProfileQuestion) => createSelector( (state: RootState) => state.answers, (answers) => { diff --git a/src/models/appSlice.ts b/src/models/appSlice.ts index d7e4a0b..3adf316 100644 --- a/src/models/appSlice.ts +++ b/src/models/appSlice.ts @@ -13,11 +13,13 @@ const appSlice = createSlice({ return action.payload; }, setPage(state, action: PayloadAction<Page>) { - console.log("setPage.state", state.currentPage, action.payload) state.currentPage = action.payload; } } }) -export const {setApp, setPage} = appSlice.actions; +export const { + setApp, + setPage +} = appSlice.actions; export default appSlice.reducer
\ No newline at end of file diff --git a/src/models/questionSlice.ts b/src/models/questionSlice.ts index 814caf9..ec9f84a 100644 --- a/src/models/questionSlice.ts +++ b/src/models/questionSlice.ts @@ -1,26 +1,88 @@ import {createSelector, createSlice, PayloadAction} from '@reduxjs/toolkit' import type {RootState} from '@/store' import {UpkQuestion} from "@/api"; +import {Answer} from "@/models/answerSlice.ts"; -const initialState: UpkQuestion[] = [] +export interface ProfileQuestion extends UpkQuestion { + active: false +} + +const initialState: ProfileQuestion[] = [] const questionSlice = createSlice({ name: 'questions', initialState, reducers: { - setQuestions(state, action: PayloadAction<UpkQuestion[]>) { + setQuestions(state, action: PayloadAction<ProfileQuestion[]>) { return action.payload; }, - questionAdded(state, action: PayloadAction<UpkQuestion>) { + questionAdded(state, action: PayloadAction<ProfileQuestion>) { state.push(action.payload); }, + setNextQuestion(state) { + const item = state.find((i) => i.active) + + const index = state.findIndex(q => q.question_id === item.question_id) + const nextQuestion = index !== -1 ? state[index + 1] ?? null : null + + state.forEach((q) => { + q.active = q.question_id === nextQuestion.question_id + }) + + }, + setQuestionActive(state, action: PayloadAction<ProfileQuestion>) { + state.forEach((q) => { + q.active = q.question_id === action.payload.question_id + }) + }, + updateQuestion(state, action: PayloadAction<{ question_id: string, updates: Partial<ProfileQuestion> }>) { + const item = state.find((i) => i.question_id === action.payload.question_id) + if (item) { + Object.assign(item, action.payload.updates) + } + } } }) -export const {setAnswer, setQuestions, questionAdded, questionUpdated} = questionSlice.actions; +export const { + setQuestions, + setQuestionActive, + setNextQuestion, + questionAdded, + updateQuestion +} = questionSlice.actions; export default questionSlice.reducer -// export const selectAllQuestions = (state: RootState) => state.questions +// We need to fetch the next available Question that either doesn't have an Answer, or the Answer +// isn't Answer.completed +export const selectQuestions = (state: RootState) => state.questions +export const selectActiveQuestion = (state: RootState) => state.questions.find(i => i.active) +export const selectAnswers = (state: RootState) => state.answers + +export const selectNextAvailableQuestion = createSelector( + [selectQuestions, selectAnswers], + (questions, answers) => { + + // -- Check if there are any questions marked as active + const active = questions.find(q => q.active) + if (active) { + return active + } + + let res = questions.filter(q => { + const a: Answer | undefined = answers[q.question_id] + return !a || a.complete === false + }) + + // return res.reduce((min, q) => + // !min || q.order < min.order ? q : min, + // null as typeof res[0] | null + // ) + + return res[0] || null + } +) + export const selectQuestionById = (questionId: string) => createSelector( |
