aboutsummaryrefslogtreecommitdiff
path: root/src/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/models')
-rw-r--r--src/models/answerSlice.ts48
-rw-r--r--src/models/appSlice.ts6
-rw-r--r--src/models/questionSlice.ts72
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(