import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { storeMutations } from "@/constants/store-mutations.js"; import { navigationScenarios } from "../router/router-constants/navigation-scenarios"; import { storeActions } from "@/constants/store-actions"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import baseMixin from "@/mixins/base-mixin.js"; import store from "@/store"; export default { methods: { hasPartQuestions(partsOrQuestions) { return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0); }, hasGlassLocationWithMultipleParts(partsOrQuestions) { return partsOrQuestions?.some((pq) => pq.parts?.length > 1); }, hasChildPartQuestions(partsOrQuestions) { return partsOrQuestions?.some((pq) => { return pq.parts?.some((part) => part.childPartQuestions?.length > 0); }); }, hasCapabilityQuestions(partsOrQuestions) { return partsOrQuestions?.some((pq) => { return pq.parts?.some((part) => part.requiresCapabilityQuestions === true); }); }, // method to only include keys listed for lineItems.glassParts in // https://safelite.atlassian.net/wiki/spaces/DC/pages/17137665/Catalog+Front-End+State#glassParts reducedGlassPartsArray(glassParts) { const reducedGlassParts = []; glassParts.forEach((glass) => { if (Array.isArray(glass.parts) && glass.parts.length === 1) { const singlePart = glass.parts[0]; reducedGlassParts.push({ partNumber: singlePart.partNumber, description: singlePart.description, color: singlePart.color, partType: singlePart.partType, canSafeliteRecalibrate: singlePart.canSafeliteRecalibrate, requiresRecalibration: singlePart.requiresRecalibration, requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions, recalibrationType: singlePart.recalibrationType, childParts: singlePart.childParts, kitPrice: singlePart.kitPrice, sellingPrice: singlePart.sellingPrice, laborAmount: singlePart.laborAmount, }); } }); return reducedGlassParts; }, comparePageIndices(currentPage, fmgPage) { const orderedVehicleQuestionPages = [ fmgPageValues.PART_QUESTIONS, fmgPageValues.VEHICLE_PARTS, fmgPageValues.MOLDING_QUESTIONS, fmgPageValues.CAPABILITY_QUESTIONS, fmgPageValues.QUOTE, ]; return ( orderedVehicleQuestionPages.indexOf(currentPage) - orderedVehicleQuestionPages.indexOf(fmgPage) ); }, currentPageComesBeforePage(currentPage = this.$route.query.fmgPage, fmgPage) { return this.comparePageIndices(currentPage, fmgPage) < 0; }, currentPageComesAfterPage(currentPage = this.$route.query.fmgPage, fmgPage) { return this.comparePageIndices(currentPage, fmgPage) > 0; }, setupInitialData(glass, index, alreadyAnsweredQuestions, vm) { const self = vm ?? this; // clear answerData if no questions are already answered if (!alreadyAnsweredQuestions) { glass.answerData = null; } alreadyAnsweredQuestions?.forEach((answeredGlass) => { // if answeredGlass lacks any of these properties then exit if ( !answeredGlass.glassLocation || !answeredGlass.glassName || !answeredGlass.answeredQuestions || (!answeredGlass.result && !answeredGlass.partNum) ) { return; } // test if glass parts match if ( glass.glassLocation === answeredGlass.glassLocation && glass.glassName === answeredGlass.glassName ) { let answerString = ""; // loop through answeredQuestions for matches answeredGlass.answeredQuestions.forEach((answeredQuestion) => { if (!answeredQuestion.questionNum || !answeredQuestion.selectedAnswerText) { return; } // determine which answer was previously chosen const chosenAns = glass.questions[ answeredQuestion.questionNum - 1 ].answers.find((a) => { return ( a.answerText.toUpperCase() === answeredQuestion.selectedAnswerText.toUpperCase() ); }); // set the answerString to use for answerSelected if (chosenAns.nextQuestionSequence) { answerString = `${answeredQuestion.questionNum}|nextQuestion|${chosenAns.nextQuestionSequence}|${chosenAns.answerText}`; } else { answerString = `${answeredQuestion.questionNum}|answer|${chosenAns.answerResult}|${chosenAns.answerText}`; } // mark this question as answered (question-chain will read this) glass.questions[answeredQuestion.questionNum - 1].answerSelected = answerString; // mark this question as suppressed if needed (question-chain uses this) if (answeredQuestion.suppressThisQuestion) { glass.questions[ answeredQuestion.questionNum - 1 ].suppressThisQuestion = true; } }); // advance the currentGlassIndex self.currentGlassIndex = index; const answerResult = answeredGlass.partNum ? answeredGlass.partNum : answeredGlass.result; // the above logic for answerResult covers all 3 ___-questions page scenarios // add answerData to current glass glass.answerData = { answerResult: answerResult, answeredQuestions: answeredGlass.answeredQuestions, }; } }); return glass; }, handleCompletedQuestionChainAnswers(answer, glassKey, vm) { // only runs when all questions in a question-chain have been answered // when selectedAnswers updates, user has completed this part's question chain and has a final answer // (does not get run for each invididual question's answer, only when // all relevant questions for the current part have been answered) /* answer example format: { "answerResult": "DD11132", "answeredQuestions": [ { "questionText": "Is your Grand Cherokee the Laredo model?", "selectedAnswerText": "Yes", "questionNum": 1, } ], "index": 0 } */ const self = vm ?? this; // clear out any preloaded answers self.selectedAnswers = {}; // loop through every answered question on the currently answered glass part answer.answeredQuestions?.forEach((answeredQuestion, answeredQuestionIndex) => { /* answeredQuestion example format: { "questionText": "Is your Grand Cherokee the Laredo model?", "selectedAnswer": "1|nextQuestion|3|Yes", "selectedAnswerText": "Yes", "questionNum": 1, } */ const answeredQuestionText = answeredQuestion.questionText.toUpperCase(); const answeredQuestionAnswer = answeredQuestion.selectedAnswer.toUpperCase(); const answeredQuestionAnswerText = answeredQuestion.selectedAnswerText.toUpperCase(); const answeredQuestionNum = answeredQuestion.questionNum; // HANDLE DUPLICATE QUESTIONS // loop through all glass data self.questionsData.forEach((glass, glassIndex) => { if (glassIndex === answer.index) { glass.questions.forEach((question, questionIndex) => { // clear out any previously set answers on first pass with first answered question if (answeredQuestionIndex === 0) question.answerSelected = null; if (answeredQuestionNum - 1 === questionIndex) { // on the right question question.answerSelected = answeredQuestionAnswer; } }); } // limit duplicate search to glass pieces that are in or follow after the currently being answered glass piece if (glassIndex > answer.index) { let indexToSuppressTo; // reset this glass piece, in case user is changing their previous answers glass.answerData = null; glass.isSuppressedPart = null; // loop through this glass piece's questions, looking for a questionText match glass.questions.forEach((question, questionIndex) => { // clear out any previously set answers on first pass with first answer if (answeredQuestionIndex === 0) question.answerSelected = null; // clear or set suppressThisQuestion property for each question if (indexToSuppressTo && questionIndex + 1 < indexToSuppressTo) { question.suppressThisQuestion = true; } else { question.suppressThisQuestion = null; } // handle duplicate questions if (question.questionText.toUpperCase() === answeredQuestionText) { let matchedAnswer; // handle matching answer in duplicated question question.answers.forEach((ans) => { if ( ans.answerText.toUpperCase() === answeredQuestionAnswerText ) { matchedAnswer = ans; ans.selected = true; } else { ans.selected = null; } }); // handle duplicate's nextQuestion logic on other related questions if (matchedAnswer.nextQuestionSequence) { // clear any suppression on the nextQuestion glass.questions[ matchedAnswer.nextQuestionSequence - 1 ].suppressThisQuestion = null; // if the duplicate is 1ST question in array, set indexToSuppressTo if (questionIndex === 0) { if ( !indexToSuppressTo || matchedAnswer.nextQuestionSequence < indexToSuppressTo ) { indexToSuppressTo = matchedAnswer.nextQuestionSequence; } } } // update answers in this glass piece's questions with duplication logic modifications glass.questions.forEach((q) => { q.answers.forEach((a) => { // revert any previously set nextQuestion logic modifications if ( a.originalNextQuestionSequence === question.questionSequence ) { // restore original nextQuestionSequence a.nextQuestionSequence = a.originalNextQuestionSequence; self.originalNextQuestionSequence = null; // restore original answerResult if (a.originalAnswerResult) { a.answerResult = a.originalAnswerResult; a.originalAnswerResult = null; } } // modify logic on any related questions if (a.nextQuestionSequence === question.questionSequence) { // update either nextQuestionSequence or answerResult // update questions that lead to duplicated question if (matchedAnswer.nextQuestionSequence) { a.originalNextQuestionSequence = a.nextQuestionSequence; a.nextQuestionSequence = matchedAnswer.nextQuestionSequence; } else { a.originalNextQuestionSequence = a.nextQuestionSequence; a.nextQuestionSequence = null; a.originalAnswerResult = a.originalAnswerResult || a.answerResult; a.answerResult = matchedAnswer.answerResult; } } }); }); // suppress current question question.suppressThisQuestion = true; // are there any questions left that are not suppressed? const remainingQuestions = glass.questions.filter((q) => { return !q.suppressThisQuestion; }); if (remainingQuestions.length < 1) { // this is the final answer for this glass piece // mark this glass piece as completely answered by adding answerData const answeredQuestionObj = { questionText: question.questionText, selectedAnswerText: matchedAnswer.answerText, questionNum: question.questionSequence, suppressThisQuestion: question.suppressThisQuestion, }; // set the answerData (used as indicator that it has been already answered) glass.answerData = { answerResult: matchedAnswer.nextQuestionSequence ? matchedAnswer.nextQuestionSequence : matchedAnswer.answerResult, answeredQuestions: [answeredQuestionObj], }; // suppress this glass piece because it has an answer glass.isSuppressedPart = true; } } }); // DONE looping through glass.questions // Update key to force re-render of glass piece with duplicate question in case user changes previous related answer in the chain self.questionsData[glassIndex].key = self.questionsData[glassIndex].key + Date.now().toString(); } }); }); // set final answer data for the current answered glass part self.questionsData[answer.index].answerData = { answerResult: answer.answerResult, answeredQuestions: answer.answeredQuestions, }; // this part has been fully answered, so advance to next part's question chain for (let i = answer.index + 1; i < this.questionsData.length; i++) { // if this part has not yet been fully answered, then make it the current part if (!this.questionsData[i].answerData?.answerResult) { this.currentGlassIndex = i; break; } } }, // Can't use `this` because navigateForward is also called from vin-pages-mixin async navigateForward(partsOrQuestions, vm) { const self = vm ?? this; const currentPage = self.$route.query.fmgPage; const hasPartQuestions = this.hasPartQuestions(partsOrQuestions); const hasGlassLocationWithMultipleParts = this.hasGlassLocationWithMultipleParts(partsOrQuestions); const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions); const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions); if ( hasPartQuestions && this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS) ) { self.$router.navigateWithSaving( self.navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS, self.$route, {}, {}, { partsOrQuestions: partsOrQuestions } ); } else if ( hasGlassLocationWithMultipleParts && this.currentPageComesBeforePage(currentPage, fmgPageValues.VEHICLE_PARTS) ) { // if multiple parts on any glass // go to vehicle-parts page and pass the partsData self.$router.navigateWithSaving( self.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE, self.$route, {}, {}, { partsOrQuestions: partsOrQuestions } ); } else if ( hasChildPartQuestions && this.currentPageComesBeforePage(currentPage, fmgPageValues.MOLDING_QUESTIONS) ) { // if any childpart questions // go to molding-questions page and pass the partsData self.$router.navigateWithSaving( self.navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS, self.$route, {}, {}, { partsOrQuestions: partsOrQuestions } ); } else if ( hasCapabilityQuestions && this.currentPageComesBeforePage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS) ) { // if has capability questions // go to capability-questions page and pass the partsData // mimic part-questions page data for consistency for (let partOrQuestion of partsOrQuestions) { if (this.hasCapabilityQuestions([partOrQuestion])) { let capabilityQuestionsForGlassLocation = ( await baseMixin.methods.dispatchStoreAction( storeActions.GET_CAPABILITY_QUESTIONS, { carId: store.getters.vehicle.carId, partNumber: partOrQuestion.parts[0].partNumber, } ) ).data; capabilityQuestionsForGlassLocation.forEach((question) => { question.answers = question.answers.map((answer) => { return { ...answer, answerResult: answer.answerResult1, }; }); }); partOrQuestion.capabilityQuestions = capabilityQuestionsForGlassLocation; } } self.$router.navigateWithSaving( self.navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS, self.$route, {}, {}, { partsOrQuestions } ); } else { // if single parts only const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions); // save to store lineItems.glassParts self.dispatchStoreAction(storeActions.SAVE_GLASS_PARTS, collectedGlassParts, false); const payment = store.getters.payment; if (store.getters.order.referralNumber?.length === 6) { navigateToHeritageFunnel({ shouldSaveSession: true, loadingModal: self.$refs.loadingModal, }); } else if (payment.isInsurance && payment.insuranceCoverage.isVerified) { navigateToHeritageFunnel({ shouldSaveSession: true, loadingModal: self.$refs.loadingModal, }); } else { self.$router.navigateWithSaving( self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, self.$route ); } } }, // Can't use `this` because navigateForward is also called from quote async navigateBack(vm) { const self = vm ?? this; const currentPage = self.$route.query.fmgPage; const pageDataCapabilityQuestions = self.$store.getters.pageData( fmgPageValues.CAPABILITY_QUESTIONS ); const pageDataMoldingQuestions = self.$store.getters.pageData( fmgPageValues.MOLDING_QUESTIONS ); const pageDataVehicleParts = self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS); const pageDataPartQuestions = self.$store.getters.pageData( fmgPageValues.PART_QUESTIONS ); const allPartsOrQuestions = [ ...(pageDataCapabilityQuestions?.partsOrQuestions || []), ...(pageDataMoldingQuestions?.partsOrQuestions || []), ...(pageDataVehicleParts?.partsOrQuestions || []), ...(pageDataPartQuestions?.partsOrQuestions || []), ]; const hasPartQuestions = this.hasPartQuestions(allPartsOrQuestions); const hasGlassLocationWithMultipleParts = this.hasGlassLocationWithMultipleParts(allPartsOrQuestions); const hasChildPartQuestions = this.hasChildPartQuestions(allPartsOrQuestions); const hasCapabilityQuestions = this.hasCapabilityQuestions(allPartsOrQuestions); const vin = self.$store.getters.vehicle.vin; let backNavigationScenario = !vin || vin === "" ? navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS : navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS; if ( hasCapabilityQuestions && this.currentPageComesAfterPage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS) ) { backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS; } else if ( hasChildPartQuestions && this.currentPageComesAfterPage(currentPage, fmgPageValues.MOLDING_QUESTIONS) ) { backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS; } else if ( hasGlassLocationWithMultipleParts && this.currentPageComesAfterPage(currentPage, fmgPageValues.VEHICLE_PARTS) ) { backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE; } else if ( hasPartQuestions && this.currentPageComesAfterPage(currentPage, fmgPageValues.PART_QUESTIONS) ) { backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS; } self.$router.navigateWithoutSaving(backNavigationScenario, self.$route); }, }, };