diff options
| author | Max Nanis | 2025-06-02 16:45:21 +0700 |
|---|---|---|
| committer | Max Nanis | 2025-06-02 16:45:21 +0700 |
| commit | e6037b430935720ce60245ae36ecd3622e8a22bf (patch) | |
| tree | 13919b0f73729ad47a31e06bdd710f774d70cac0 /src/models | |
| parent | 8caa77413ea372e5cbd2980a9922d701af359c04 (diff) | |
| download | panel-ui-e6037b430935720ce60245ae36ecd3622e8a22bf.tar.gz panel-ui-e6037b430935720ce60245ae36ecd3622e8a22bf.zip | |
Updated openapi generator to use the latest version (camelCase to underscore - which respects API models). Updating views to use the new swagger definitions. AnswerSlice as a datastore alongside Questions TS interface
Diffstat (limited to 'src/models')
| -rw-r--r-- | src/models/CashoutMethod.ts | 33 | ||||
| -rw-r--r-- | src/models/answer.ts | 9 | ||||
| -rw-r--r-- | src/models/answerSlice.ts | 241 | ||||
| -rw-r--r-- | src/models/bucket.ts | 32 | ||||
| -rw-r--r-- | src/models/bucketSlice.ts | 8 | ||||
| -rw-r--r-- | src/models/question.ts | 272 | ||||
| -rw-r--r-- | src/models/questionSlice.ts | 3 |
7 files changed, 253 insertions, 345 deletions
diff --git a/src/models/CashoutMethod.ts b/src/models/CashoutMethod.ts deleted file mode 100644 index 8da88ac..0000000 --- a/src/models/CashoutMethod.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {CashoutMethodOut} from "@/api/models/cashout-method-out.ts" - -export class CashoutMethod implements CashoutMethodOut { - id: string; // uuid - currency: string; // 3-letter all caps - originalCurrency: string; // 3-letter all caps - data: any; - description: string; // html description - imageUrl: string; // cloudfront cdn - maxValue: number; // pixels int - minValue: number; // pixels int - name: string; - type: string; // TANGO - extId: string; // U179271 - productId?: any; - productUserId?: any; - - constructor(data) { - this.originalCurrency = data.original_currency; - this.currency = data.currency; - this.data = data.data; - this.description = data.description; - this.extId = data.ext_id; - this.id = data.id; - this.imageUrl = data.image_url; - this.maxValue = data.max_value; - this.minValue = data.min_value; - this.name = data.name; - this.type = data.type; - - } - -}
\ No newline at end of file diff --git a/src/models/answer.ts b/src/models/answer.ts new file mode 100644 index 0000000..ffdb200 --- /dev/null +++ b/src/models/answer.ts @@ -0,0 +1,9 @@ + +export interface Answer { + questionId: string; + values: string[]; + error_msg: string; + + _complete: boolean; + _processing: boolean; +}
\ No newline at end of file diff --git a/src/models/answerSlice.ts b/src/models/answerSlice.ts new file mode 100644 index 0000000..1255854 --- /dev/null +++ b/src/models/answerSlice.ts @@ -0,0 +1,241 @@ +import {createSlice, PayloadAction} from '@reduxjs/toolkit' +import type {RootState} from '@/store' +import {selectQuestionById} from "@/models/questionSlice.ts" +// import {Answer} from "@/models/answer.ts"; +// import {stringify} from "querystring"; +// import {PatternType} from "@/types.ts"; + +// import { enableMapSet } from 'immer' +// enableMapSet() + +// const initialState = new Map<string, Answer>(); +const initialState = {}; + +const answerSlice = createSlice({ + name: 'answers', + initialState, + reducers: { + addAnswer(state, action: PayloadAction<{questionId: string, val: string}>) { + let question = selectQuestionById(state, action.payload.questionId) + let val = action.payload.val.trim(); + + console.log("addAnswer:", question, val) + + // switch (question.questionType) { + // case "TE": + // let answer: Answer = { + // questionId: question.questionId, + // values: [val] + // } as Answer + // + // break + // // return {question.questionId: answer} + // + // case "MC": + // let answer = selectAnswerByQuestionId(questionId) + // + // if (answer) { + // let current_values: string[] = answer.values + // } else { + // let current_values: string[] = [] + // } + // + // current_values.push(val) + // let new_answer = new Anwer(question.questionId, val); + // + // return {question.questionId: new_answer} + // + // default: + // throw new Error("Incorrect Question Type provided"); + // } + + // this.validate() + }, + + setAnswer(state, action: PayloadAction<{ questionId: string, val: string }>) { + const {questionId, val} = action.payload + console.log(questionId, val) + const existingQuestion = state.find(q => q.questionId === action.payload.questionId) + if (existingQuestion) { + // existingQuestion.addAnswer(action.payload.val) + // existingQuestion.error_msg = "yess" + } + } + + // removeAnswer(val: string): null { + // switch (this.getType()) { + // + // // You can only remove a value from a MultiChoice + // case "MC": + // // TODO: implement this + // // let current_values: string[] = this._answer?.values + // // current_values.push(val) + // // this._answer = new ProfilingAnswer(this.questionId, current_values); + // break + // + // default: + // throw new Error("Incorrect Question Type provided"); + // } + // + // this.validate() + // } + + + // validate(): boolean { + // /* + // If the question is MC, validate: + // - validate selector SA vs MA (1 selected vs >1 selected) + // - the answers match actual codes in the choices + // - validate configuration.max_select + // - validate choices.exclusive + // + // If the question is TE, validate that: + // - configuration.max_length + // - validation.patterns + // */ + // + // if (this._answer == null) { + // this.error_msg = "An answer is required" + // return false + // } + // + // let qa: ProfilingAnswer = this._answer; + // + // switch (this.getType()) { + // case "TE": + // if (qa.values.length == 0) { + // this.error_msg = "An answer is required" + // return false + // } + // + // if (qa.values.length > 1) { + // this.error_msg = "Only one answer allowed" + // return false + // } + // + // let answer: string = qa.values[0] + // + // if (answer.length <= 0) { + // this.error_msg = "Must provide answer" + // return false + // } + // + // const max_length: number = (this.configuration ?? {})["max_length"] ?? 100000 + // + // if (answer.length > max_length) { + // this.error_msg = "Answer longer than allowed" + // return false + // } + // + // const patterns: PatternType[] = (this.validation ?? {})["patterns"] ?? [] + // + // patterns.forEach((pattern) => { + // let re = new RegExp(pattern["pattern"]) + // if (answer.search(re) == -1) { + // this.error_msg = pattern["message"] + // return false + // } + // }) + // + // this.error_msg = "" + // return true + // + // case "MC": + // // if (qa.values.length == 0) { + // // this.error_msg = "MC question with no selected answers" + // // return false + // // } + // // + // // const choice_codes = map(this.getChoices().toJSON(), "choice_id") + // // + // // switch (this.getSelector()) { + // // case "SA": + // // if (qa.values.length > 1) { + // // this.error_msg = "Single Answer MC question with >1 selected answers" + // // return false + // // } + // // break + // // case "MA": + // // if (qa.values.length > choice_codes.length) { + // // this.error_msg = "More options selected than allowed" + // // return false + // // } + // // break + // // } + // // + // // if (!every(qa.values, (v) => { + // // return includes(choice_codes, v["value"]) + // // })) { + // // this.error_msg = "Invalid Options Selected" + // // return false + // // } + // // + // // const max_select: number = (this.configuration ?? {})["max_select"] ?? choice_codes.length + // // if (qa.values.length > max_select) { + // // this.error_msg = "More options selected than allowed" + // // return false + // // } + // + // /* + // exclusive_choice = next((x for x in question["choices"] if x.get("exclusive")), None) + // if exclusive_choice: + // exclusive_choice_id = exclusive_choice["choice_id"] + // assert answer == [exclusive_choice_id] or \ + // exclusive_choice_id not in answer, "Invalid exclusive selection" + // */ + // + // this.error_msg = "" + // return true + // + // default: + // throw new Error("Incorrect Question Type provided"); + // } + // + // } + + + + // save() { + // let question: ProfilingQuestion = this; + // // @ts-ignore + // let answer: ProfilingAnswer = question._answer; + // + // if (this._complete || this._processing) { + // return + // } + // this._processing = true + // + // let res = JSON.stringify({ + // "answers": [{ + // "question_id": answer.get('question_id'), + // "answer": map(answer.get("values"), "value") + // }] + // }); + // + // $.ajax({ + // url: ["https://fsb.generalresearch.com", questions.BPID, "profiling-questions", ""].join("/") + "?" + stringify({"bpuid": questions.BPUID}), + // xhrFields: {withCredentials: false}, + // processData: false, + // type: "POST", + // contentType: "application/json; charset=utf-8", + // data: res, + // success: function (data) { + // channel.trigger("ProfilingQuestions:start"); + // }, + // error: function (data) { + // channel.trigger("ProfilingQuestions:start"); + // Sentry.captureMessage("Profiling Question submission failed."); + // } + // }); + // } + + + } +}) + +// Export the generated reducer function +export const {setAnswer, setQuestions, questionAdded, questionUpdated} = answerSlice.actions; +export default answerSlice.reducer + +export const selectAnswerCount = (state: RootState) => state.answers.size +// export const selectAnswerByQuestionId = (state: RootState, questionId: string) => state.answers.find(a => q.questionId === questionId)
\ No newline at end of file diff --git a/src/models/bucket.ts b/src/models/bucket.ts deleted file mode 100644 index a9419a6..0000000 --- a/src/models/bucket.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {SoftPairBucket} from "@/api/models/soft-pair-bucket.ts" - -export class SoftPairBucketClass implements SoftPairBucket { - readonly id: string; - readonly uri: string; - readonly category: any | null; - readonly contents: any; - readonly eligibility: any; - readonly missingQuestions: any | null; - readonly loi: any; - readonly payout: any; - - constructor(data: { - id: string, - uri: string, - category: any | null, - contents: any, - eligibility: any, - missingQuestions: any | null, - loi: any, - payout: any - }) { - this.id = data.id; - this.uri = data.uri; - this.category = data.category; - this.contents = data.contents; - this.eligibility = data.eligibility; - this.missingQuestions = data.missingQuestions; - this.loi = data.loi; - this.payout = data.payout; - } -}
\ No newline at end of file diff --git a/src/models/bucketSlice.ts b/src/models/bucketSlice.ts index 9c6aee8..3a2c0d1 100644 --- a/src/models/bucketSlice.ts +++ b/src/models/bucketSlice.ts @@ -1,13 +1,10 @@ import {createSlice, PayloadAction} from '@reduxjs/toolkit' -import {SoftPairBucketClass} from "@/models/bucket.ts" -// import {ProfilingQuestion} from "@/models/question.ts"; import type {RootState} from '@/store' -import {SoftPairBucket} from "@/api/models/soft-pair-bucket.ts" +import {SoftPairBucket} from "@/api"; + -// Create an initial state value for the reducer, with that type const initialState: SoftPairBucket[] = [] -// Create the slice and pass in the initial state const bucketSlice = createSlice({ name: 'buckets', initialState, @@ -21,7 +18,6 @@ const bucketSlice = createSlice({ } }) -// Export the generated reducer function export const {setBuckets, bucketAdded} = bucketSlice.actions; export default bucketSlice.reducer diff --git a/src/models/question.ts b/src/models/question.ts deleted file mode 100644 index 996589b..0000000 --- a/src/models/question.ts +++ /dev/null @@ -1,272 +0,0 @@ -import {stringify} from "querystring"; -import {ChoiceType, ConfigurationType, PatternType, QuestionType, SelectorType, ValidationType} from "@/types.ts" -import {UpkQuestion} from "@/api/models/upk-question.ts" -import {UpkQuestionType} from "@/api"; - - -export class ProfilingAnswer { - // let values: Array<{ value: string }> = question_answer.values || [] - - questionId: string; - values: string[] = []; - - constructor(qid: string, values: string[]) { - this.questionId = qid; - this.values = values - } -} - -export class ProfilingQuestion implements UpkQuestion { - questionId: string; // It's a UUID - countryIso: string; // 2-letter lower - languageIso: string; // 3-letter lower - - questionType: UpkQuestionType; - selector: SelectorType; - questionText: string; // "title" of the question - - choices?: ChoiceType[]; - - extQuestionId?: any; - configuration: ConfigurationType | null = null; - validation: ValidationType | null = null; - - importance?: any; - task_count: number; - task_score: number; - - private _complete: boolean = false; - private _processing: boolean = false; - private _answer: ProfilingAnswer | null = null; - - error_msg: string = ""; - - constructor(data) { - this.questionId = data.question_id; - this.countryIso = data.country_iso; - this.languageIso = data.language_iso; - - this.questionType = data.question_type; - this.selector = data.selector; - this.questionText = data.question_text; - - this.choices = data.choices - - this.extQuestionId = data.ext_question_id; - this.configuration = data.configuration; - this.validation = data.validation; - } - - // --- Properties --- - - getType(): QuestionType { - return this.questionType as QuestionType - } - - getSelector(): SelectorType { - return this.selector as SelectorType - } - - getChoices(): ChoiceType[] | null { - const choices: ChoiceType[] = this.choices; - if (choices.length > 0) { - return choices; - } else { - return null; - } - } - - // --- Methods --- - - addAnswer(val: string): null { - val = val.trim(); - - switch (this.getType()) { - - case "TE": - this._answer = new ProfilingAnswer(this.questionId, [val]); - break - - case "MC": - let current_values: string[] = this._answer?.values - current_values.push(val) - this._answer = new ProfilingAnswer(this.questionId, current_values); - break - - default: - throw new Error("Incorrect Question Type provided"); - } - - this.validate() - } - - removeAnswer(val: string): null { - switch (this.getType()) { - - // You can only remove a value from a MultiChoice - case "MC": - // TODO: implement this - // let current_values: string[] = this._answer?.values - // current_values.push(val) - // this._answer = new ProfilingAnswer(this.questionId, current_values); - break - - default: - throw new Error("Incorrect Question Type provided"); - } - - this.validate() - } - - validate(): boolean { - /* - If the question is MC, validate: - - validate selector SA vs MA (1 selected vs >1 selected) - - the answers match actual codes in the choices - - validate configuration.max_select - - validate choices.exclusive - - If the question is TE, validate that: - - configuration.max_length - - validation.patterns - */ - - if (this._answer == null) { - this.error_msg = "An answer is required" - return false - } - - let qa: ProfilingAnswer = this._answer; - - switch (this.getType()) { - case "TE": - if (qa.values.length == 0) { - this.error_msg = "An answer is required" - return false - } - - if (qa.values.length > 1) { - this.error_msg = "Only one answer allowed" - return false - } - - let answer: string = qa.values[0] - - if (answer.length <= 0) { - this.error_msg = "Must provide answer" - return false - } - - const max_length: number = (this.configuration ?? {})["max_length"] ?? 100000 - - if (answer.length > max_length) { - this.error_msg = "Answer longer than allowed" - return false - } - - const patterns: PatternType[] = (this.validation ?? {})["patterns"] ?? [] - - patterns.forEach((pattern) => { - let re = new RegExp(pattern["pattern"]) - if (answer.search(re) == -1) { - this.error_msg = pattern["message"] - return false - } - }) - - this.error_msg = "" - return true - - case "MC": - // if (qa.values.length == 0) { - // this.error_msg = "MC question with no selected answers" - // return false - // } - // - // const choice_codes = map(this.getChoices().toJSON(), "choice_id") - // - // switch (this.getSelector()) { - // case "SA": - // if (qa.values.length > 1) { - // this.error_msg = "Single Answer MC question with >1 selected answers" - // return false - // } - // break - // case "MA": - // if (qa.values.length > choice_codes.length) { - // this.error_msg = "More options selected than allowed" - // return false - // } - // break - // } - // - // if (!every(qa.values, (v) => { - // return includes(choice_codes, v["value"]) - // })) { - // this.error_msg = "Invalid Options Selected" - // return false - // } - // - // const max_select: number = (this.configuration ?? {})["max_select"] ?? choice_codes.length - // if (qa.values.length > max_select) { - // this.error_msg = "More options selected than allowed" - // return false - // } - - /* - exclusive_choice = next((x for x in question["choices"] if x.get("exclusive")), None) - if exclusive_choice: - exclusive_choice_id = exclusive_choice["choice_id"] - assert answer == [exclusive_choice_id] or \ - exclusive_choice_id not in answer, "Invalid exclusive selection" - */ - - this.error_msg = "" - return true - - default: - throw new Error("Incorrect Question Type provided"); - } - - } - - // -- Database / Format -- - static fromJson(json: any): ProfilingQuestion { - return new ProfilingQuestion(json); - } - - save() { - let question: ProfilingQuestion = this; - // @ts-ignore - let answer: ProfilingAnswer = question._answer; - - if (this._complete || this._processing) { - return - } - this._processing = true - - let res = JSON.stringify({ - "answers": [{ - "question_id": answer.get('question_id'), - "answer": map(answer.get("values"), "value") - }] - }); - - $.ajax({ - url: ["https://fsb.generalresearch.com", questions.BPID, "profiling-questions", ""].join("/") + "?" + stringify({"bpuid": questions.BPUID}), - xhrFields: {withCredentials: false}, - processData: false, - type: "POST", - contentType: "application/json; charset=utf-8", - data: res, - success: function (data) { - channel.trigger("ProfilingQuestions:start"); - }, - error: function (data) { - channel.trigger("ProfilingQuestions:start"); - Sentry.captureMessage("Profiling Question submission failed."); - } - }); - } - -}
\ No newline at end of file diff --git a/src/models/questionSlice.ts b/src/models/questionSlice.ts index 6a4da6f..e3674be 100644 --- a/src/models/questionSlice.ts +++ b/src/models/questionSlice.ts @@ -1,10 +1,9 @@ import {createSlice, PayloadAction} from '@reduxjs/toolkit' import type {RootState} from '@/store' -import {UpkQuestion} from "@/api/models/upk-question.ts" +import {UpkQuestion} from "@/api"; const initialState: UpkQuestion[] = [] -// Create the slice and pass in the initial state const questionSlice = createSlice({ name: 'questions', initialState, |
