Merge branch 'develop' into feature/Digital/SSR-136
This commit is contained in:
commit
c69b4a6d32
15 changed files with 1592 additions and 23 deletions
BIN
src/assets/img/loader.gif
Normal file
BIN
src/assets/img/loader.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
src/assets/img/windshield.png
Normal file
BIN
src/assets/img/windshield.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 KiB |
30
src/common-components/loading-modal/loading-modal.spec.js
Normal file
30
src/common-components/loading-modal/loading-modal.spec.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import loadingModal from "./loading-modal";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
|
||||||
|
jest.mock("@/assets/img/loader.gif", () => "loader.gif");
|
||||||
|
jest.mock("@/assets/img/windshield.png", () => "windshield.png");
|
||||||
|
|
||||||
|
describe("loadingModal", () => {
|
||||||
|
test("showModal sets modal visible", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
wrapper.vm.isModalVisible = false;
|
||||||
|
|
||||||
|
//Act
|
||||||
|
wrapper.vm.showModal();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.isModalVisible).toEqual(true);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks() {
|
||||||
|
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = shallowMount(loadingModal, mountOptions);
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
250
src/common-components/loading-modal/loading-modal.vue
Normal file
250
src/common-components/loading-modal/loading-modal.vue
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="isModalVisible" class="container-fluid modal-loader">
|
||||||
|
<div class="row g-2 h-100 d-flex align-items-center">
|
||||||
|
<div class="container-fluid overflow-hidden">
|
||||||
|
<div class="row h-100">
|
||||||
|
<div class="col text-center tagbg mb-4">
|
||||||
|
<div class="spinner-border text-danger" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="text-container slide">
|
||||||
|
<p>
|
||||||
|
Finding shops near you
|
||||||
|
<span class="dot-1">.</span>
|
||||||
|
<span class="dot-2">.</span>
|
||||||
|
<span class="dot-3">.</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Looking for dates
|
||||||
|
<span class="dot-1">.</span>
|
||||||
|
<span class="dot-2">.</span>
|
||||||
|
<span class="dot-3">.</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Searching for times
|
||||||
|
<span class="dot-1">.</span>
|
||||||
|
<span class="dot-2">.</span>
|
||||||
|
<span class="dot-3">.</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nearly there
|
||||||
|
<span class="dot-1">.</span>
|
||||||
|
<span class="dot-2">.</span>
|
||||||
|
<span class="dot-3">.</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Finishing up
|
||||||
|
<span class="dot-1">.</span>
|
||||||
|
<span class="dot-2">.</span>
|
||||||
|
<span class="dot-3">.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Modal",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isModalVisible: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showModal() {
|
||||||
|
//Display modal
|
||||||
|
this.isModalVisible = true;
|
||||||
|
//Force page reload on back button
|
||||||
|
window.addEventListener(
|
||||||
|
"pageshow",
|
||||||
|
function (evt) {
|
||||||
|
if (evt.persisted) {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.reload();
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.modal-loader {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1057;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
outline: 0;
|
||||||
|
background: $gray-100;
|
||||||
|
|
||||||
|
.tagbg {
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 96px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border {
|
||||||
|
width: 6.5rem;
|
||||||
|
height: 6.5rem;
|
||||||
|
border: 0.45em solid currentColor;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
display: flex;
|
||||||
|
width: 350px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
background: rgb(245, 245, 245);
|
||||||
|
background: linear-gradient(
|
||||||
|
270deg,
|
||||||
|
rgba(245, 245, 245, 1) 0%,
|
||||||
|
rgba(245, 245, 245, 0) 5%,
|
||||||
|
rgba(245, 245, 245, 0) 95%,
|
||||||
|
rgba(245, 245, 245, 1) 100%
|
||||||
|
);
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.slide p {
|
||||||
|
animation: slide-in 20s ease-in-out 1s forwards 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 175px;
|
||||||
|
transform: translateX(180px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in {
|
||||||
|
0% {
|
||||||
|
transform: translateX(180px);
|
||||||
|
}
|
||||||
|
11% {
|
||||||
|
transform: translateX(-80px);
|
||||||
|
}
|
||||||
|
22% {
|
||||||
|
transform: translateX(-80px);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translateX(-600px);
|
||||||
|
}
|
||||||
|
44% {
|
||||||
|
transform: translateX(-600px);
|
||||||
|
}
|
||||||
|
55% {
|
||||||
|
transform: translateX(-1110px);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translateX(-1110px);
|
||||||
|
}
|
||||||
|
77% {
|
||||||
|
transform: translateX(-1600px);
|
||||||
|
}
|
||||||
|
88% {
|
||||||
|
transform: translateX(-1600px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-2050px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-1 {
|
||||||
|
animation: blinking-elipsis 0.75s linear infinite;
|
||||||
|
@keyframes blinking-elipsis {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-2 {
|
||||||
|
animation: blinking-elipsis-2 0.75s linear infinite;
|
||||||
|
@keyframes blinking-elipsis-2 {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
21% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
22% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-3 {
|
||||||
|
animation: blinking-elipsis-3 0.75s linear infinite;
|
||||||
|
@keyframes blinking-elipsis-3 {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
43% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
44% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
// Components
|
||||||
|
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout";
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import baseMixin from "../../mixins/base-mixin";
|
||||||
|
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||||
|
|
||||||
|
// Mock our module for promises.
|
||||||
|
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
|
settleAllPromises: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock fetchCmsContentForPage
|
||||||
|
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
|
fetchCmsContentForPage: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
describe("questionsPageLayout.vue", () => {
|
||||||
|
describe("method showThisQuestionChain...", () => {
|
||||||
|
test("Should return true if index prop and passed index match", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const testIndex = 0;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return true if answerResult exists", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerData: {
|
||||||
|
answerResult: "test",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const testIndex = 1;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return false if indexes don't match and answerResult is missing", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const testIndex = 1;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return false if no questions", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const testGlassPiece = {};
|
||||||
|
const testIndex = 0;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return false if suppressed", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
isSuppressedPart: true,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerData: {
|
||||||
|
answerResult: "test",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const testIndex = 0;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("method handleForwardButtonAction...", () => {
|
||||||
|
test("Should emit forwardButtonAction", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.handleForwardButtonAction();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(wrapper.emitted()).toHaveProperty("forwardButtonAction");
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("method handleBackButtonAction...", () => {
|
||||||
|
test("Should emit backButtonAction", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.handleBackButtonAction();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(wrapper.emitted()).toHaveProperty("back-click");
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks() {
|
||||||
|
const baseStoreGettersPageData = () => {
|
||||||
|
return {
|
||||||
|
partsOrQuestions: [
|
||||||
|
{
|
||||||
|
parts: null,
|
||||||
|
partQuestions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: "",
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
glassLocation: "Windshield",
|
||||||
|
glassName: "Single",
|
||||||
|
answerKey: "Windshield-Single",
|
||||||
|
answerData: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const baseStoreGettersDamage = () => {
|
||||||
|
return {
|
||||||
|
partsQuestionAnswers: [
|
||||||
|
{
|
||||||
|
glassLocation: "Windshield",
|
||||||
|
glassName: "Single",
|
||||||
|
result: "FW04848",
|
||||||
|
answeredQuestions: [
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
|
||||||
|
selectedAnswerText: "Yes",
|
||||||
|
questionNum: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
|
||||||
|
selectedAnswerText: "Yes",
|
||||||
|
questionNum: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
mixins: [baseMixin, vehicleQuestionsMixin],
|
||||||
|
});
|
||||||
|
mountOptions["attachTo"] = document.body;
|
||||||
|
const wrapper = shallowMount(questionsPageLayout, mountOptions);
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<template>
|
||||||
|
<div :class="`page-container-grouped-styles questions-page`">
|
||||||
|
<loadingModal ref="loadingModal" />
|
||||||
|
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||||
|
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||||
|
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
|
||||||
|
<div class="fade-on-route-transition sub-container make-tall">
|
||||||
|
<alert
|
||||||
|
ref="alertFewMoreQuestions"
|
||||||
|
cmsWidgetName="alertWidget"
|
||||||
|
class="my-5"
|
||||||
|
alertClass="alert-warning"
|
||||||
|
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||||
|
:manualCopy="alertFewMoreQuestionsCopy"
|
||||||
|
v-bind:isDismissible="false" />
|
||||||
|
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||||
|
<questionChain
|
||||||
|
ref="questionChain"
|
||||||
|
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||||
|
:questionData="questionsDatum.questions"
|
||||||
|
:index="i"
|
||||||
|
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||||
|
:answerKey="questionsDatum.answerKey"
|
||||||
|
:validationRules="validationRules" />
|
||||||
|
</div>
|
||||||
|
<site-footer
|
||||||
|
ref="siteFooter"
|
||||||
|
cmsWidgetName="SiteFooterWidget"
|
||||||
|
:isForwardActionDisabled="!isMetaValid"
|
||||||
|
@back-clicked="handleBackButtonClicked"
|
||||||
|
@ForwardClicked="handleForwardButtonAction" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import siteHeader from "@/common-components/site-header/site-header";
|
||||||
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
|
import alert from "@/ux-components/alert/alert";
|
||||||
|
import questionChain from "@/common-components/question-chain/question-chain";
|
||||||
|
import siteSubHeader from "@/common-components/site-sub-header/site-sub-header";
|
||||||
|
import siteFooter from "@/common-components/site-footer/site-footer";
|
||||||
|
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "questions-page",
|
||||||
|
props: {
|
||||||
|
isMetaValid: Boolean,
|
||||||
|
alertFewMoreQuestionsHeader: String,
|
||||||
|
alertFewMoreQuestionsCopy: String,
|
||||||
|
questionsData: Array,
|
||||||
|
validationRules: String,
|
||||||
|
modelValue: Object,
|
||||||
|
index: Number,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedAnswers: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showThisQuestionChain(glass, i) {
|
||||||
|
// return false if no questions or if suppressed
|
||||||
|
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.index === i || glass.answerData?.answerResult?.length > 0;
|
||||||
|
},
|
||||||
|
handleForwardButtonAction() {
|
||||||
|
this.$emit("forwardButtonAction");
|
||||||
|
},
|
||||||
|
handleBackButtonAction() {
|
||||||
|
this.$emit("back-click");
|
||||||
|
},
|
||||||
|
showLoadingModal() {
|
||||||
|
this.$refs.loadingModal.showModal();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
siteHeader,
|
||||||
|
vehicleBanner,
|
||||||
|
alert,
|
||||||
|
questionChain,
|
||||||
|
siteSubHeader,
|
||||||
|
siteFooter,
|
||||||
|
loadingModal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.questions-page {
|
||||||
|
.question-text {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -31,6 +31,14 @@ const endpoints = {
|
||||||
url: "/parts/api/v1/parts/damage-options",
|
url: "/parts/api/v1/parts/damage-options",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
GetParts: {
|
||||||
|
url: "/parts/api/v1/parts/parts",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
GetCapabilityQuestions: {
|
||||||
|
url: "/parts/api/v1/parts/capability-questions",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
GetVehicle: {
|
GetVehicle: {
|
||||||
url: "/vehicle/api/v1/vehicle/lookup",
|
url: "/vehicle/api/v1/vehicle/lookup",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
|
||||||
146
src/layouts/part-questions/part-questions.vue
Normal file
146
src/layouts/part-questions/part-questions.vue
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
<template>
|
||||||
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||||
|
<questions-page-layout
|
||||||
|
ref="questionsPageLayout"
|
||||||
|
:isMetaValid="meta.valid"
|
||||||
|
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||||
|
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||||
|
:questionsData="questionsData"
|
||||||
|
validationRules="questions-required"
|
||||||
|
v-model="selectedAnswers"
|
||||||
|
@forwardButtonAction="forwardButtonAction"
|
||||||
|
@back-click="navigateBack"
|
||||||
|
:index="currentGlassIndex" />
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout";
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
|
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||||
|
import { Form, defineRule } from "vee-validate";
|
||||||
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||||
|
|
||||||
|
// DEFINE VALIDATION RULES
|
||||||
|
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "part-questions",
|
||||||
|
mixins: [vehicleQuestionsMixin],
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
|
// Call the "next" function to complete the transition to this page.
|
||||||
|
next((vm) => {
|
||||||
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
questionsData: [],
|
||||||
|
selectedAnswers: {},
|
||||||
|
currentGlassIndex: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
AlertFewMoreQuestionsHeader() {
|
||||||
|
return this.getCmsContent("AlertPartsQuestions", "HeadlineText");
|
||||||
|
},
|
||||||
|
AlertFewMoreQuestionsCopy() {
|
||||||
|
return this.getCmsContent("AlertPartsQuestions", "BodyText");
|
||||||
|
},
|
||||||
|
pageData() {
|
||||||
|
return useMainStore().pageData(issPageValues.PART_QUESTIONS);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// are there alreadyAnsweredQuestions?
|
||||||
|
const alreadyAnsweredQuestions = useMainStore().damage.partQuestionAnswers;
|
||||||
|
|
||||||
|
this.questionsData = this.pageData.partsOrQuestions
|
||||||
|
.filter((x) => x.partQuestions)
|
||||||
|
.map((glass, index) => {
|
||||||
|
// NOTE: questions for property "questions" can differ between layouts
|
||||||
|
glass.questions = glass.partQuestions;
|
||||||
|
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
|
||||||
|
// reset selectedAnswers for this glass
|
||||||
|
this.selectedAnswers[glass.answerKey] = [];
|
||||||
|
|
||||||
|
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
|
||||||
|
|
||||||
|
// Set up watch for each set of glass questions
|
||||||
|
this.$watch(
|
||||||
|
"selectedAnswers." + glass.answerKey,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue && Object.keys(newValue).length > 0) {
|
||||||
|
this.handleAnswerUpdates(newValue, glass.answerKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedGlass;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
const partQuestionsFromPageData = useMainStore().pageData(issPageValues.PART_QUESTIONS);
|
||||||
|
return (
|
||||||
|
partQuestionsFromPageData &&
|
||||||
|
Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async forwardButtonAction() {
|
||||||
|
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||||
|
return {
|
||||||
|
glassLocation: glass.glassLocation,
|
||||||
|
glassName: glass.glassName,
|
||||||
|
result: (glass.answerData && glass.answerData.answerResult) || "",
|
||||||
|
answeredQuestions: glass.answerData?.answeredQuestions,
|
||||||
|
isSuppressedPart: glass.isSuppressedPart,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// clear out answerData for future page loads; must occur prior to store save
|
||||||
|
this.questionsData.forEach((glass) => {
|
||||||
|
if (glass.answerData) {
|
||||||
|
glass.answerData = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// save to vuex store as order.damage.partQuestionAnswers (array)
|
||||||
|
await this.mainStore.savePartQuestionAnswers(questionAnswersArray);
|
||||||
|
|
||||||
|
// call API parts method
|
||||||
|
const partsLookup = await this.mainStore.getParts();
|
||||||
|
|
||||||
|
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
||||||
|
|
||||||
|
// TODO KO delete for quote mvp
|
||||||
|
this.navigateForward(glassPartsForStore, null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Form,
|
||||||
|
questionsPageLayout,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -24,6 +24,9 @@ export default {
|
||||||
const footerInfoBox = document.querySelector(".footer#infoBox");
|
const footerInfoBox = document.querySelector(".footer#infoBox");
|
||||||
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
||||||
},
|
},
|
||||||
|
savePageDataToStore(page, data) {
|
||||||
|
useMainStore().updatePageData({ page: page, data: data});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// store will be accessible globally as its id + 'Store'
|
// store will be accessible globally as its id + 'Store'
|
||||||
|
|
|
||||||
487
src/mixins/vehicle-questions-mixin.js
Normal file
487
src/mixins/vehicle-questions-mixin.js
Normal file
|
|
@ -0,0 +1,487 @@
|
||||||
|
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||||
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||||
|
import { useMainStore } 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
|
||||||
|
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,
|
||||||
|
price: singlePart.price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reducedGlassParts;
|
||||||
|
},
|
||||||
|
comparePageIndices(currentPage, issPage) {
|
||||||
|
const orderedVehicleQuestionPages = [
|
||||||
|
issPageValues.PART_QUESTIONS,
|
||||||
|
issPageValues.VEHICLE_PARTS,
|
||||||
|
issPageValues.MOLDING_QUESTIONS,
|
||||||
|
issPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
issPageValues.QUOTE,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
orderedVehicleQuestionPages.indexOf(currentPage) -
|
||||||
|
orderedVehicleQuestionPages.indexOf(issPage)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
currentPageComesBeforePage(currentPage = this.$route.query.issPage, issPage) {
|
||||||
|
return this.comparePageIndices(currentPage, issPage) < 0;
|
||||||
|
},
|
||||||
|
currentPageComesAfterPage(currentPage = this.$route.query.issPage, issPage) {
|
||||||
|
return this.comparePageIndices(currentPage, issPage) > 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;
|
||||||
|
},
|
||||||
|
handleAnswerUpdates(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) => {
|
||||||
|
/* answeredQuestion example format:
|
||||||
|
{
|
||||||
|
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||||
|
"selectedAnswerText": "Yes",
|
||||||
|
"questionNum": 1,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
|
||||||
|
const answeredQuestionAnswer = answeredQuestion.selectedAnswerText.toUpperCase();
|
||||||
|
|
||||||
|
// HANDLE DUPLICATE QUESTIONS
|
||||||
|
|
||||||
|
// loop through all glass data
|
||||||
|
self.questionsData.forEach((glass, glassIndex) => {
|
||||||
|
/* glass example format:
|
||||||
|
{
|
||||||
|
"glassName": "Single",
|
||||||
|
"glassLocation": "Windshield",
|
||||||
|
"parts": null,
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"questionSequence": 1,
|
||||||
|
"questionText": "Is your vehicle equipped with a black dotted pattern behind the rear view mirror, known as a third visor frit?",
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"answerResult": "DW01537",
|
||||||
|
"answerText": "Yes",
|
||||||
|
"nextQuestionSequence": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answerResult": "DW01537b",
|
||||||
|
"answerText": "No",
|
||||||
|
"nextQuestionSequence": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answerKey": "Windshield-Single",
|
||||||
|
"answerData": null
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// limit duplicate search to glass pieces that 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
|
||||||
|
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() === answeredQuestionAnswer) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.issPage;
|
||||||
|
|
||||||
|
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, issPageValues.PART_QUESTIONS))
|
||||||
|
{
|
||||||
|
self.$router.navigate(self.navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS, self.$route,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{ partsOrQuestions: partsOrQuestions }
|
||||||
|
);
|
||||||
|
} else if (hasGlassLocationWithMultipleParts && this.currentPageComesBeforePage(currentPage, issPageValues.VEHICLE_PARTS))
|
||||||
|
{
|
||||||
|
// if multiple parts on any glass
|
||||||
|
// go to vehicle-parts page and pass the partsData
|
||||||
|
self.$router.navigate(
|
||||||
|
self.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
|
||||||
|
self.$route,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{ partsOrQuestions: partsOrQuestions }
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
hasChildPartQuestions &&
|
||||||
|
this.currentPageComesBeforePage(currentPage, issPageValues.MOLDING_QUESTIONS)
|
||||||
|
) {
|
||||||
|
// if any childpart questions
|
||||||
|
// go to molding-questions page and pass the partsData
|
||||||
|
self.$router.navigate(
|
||||||
|
self.navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
|
||||||
|
self.$route,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{ partsOrQuestions: partsOrQuestions }
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
hasCapabilityQuestions &&
|
||||||
|
this.currentPageComesBeforePage(currentPage, issPageValues.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 self.mainStore.getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
capabilityQuestionsForGlassLocation.forEach((question) => {
|
||||||
|
question.answers = question.answers.map((answer) => {
|
||||||
|
return {
|
||||||
|
...answer,
|
||||||
|
answerResult: answer.answerResult1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
partOrQuestion.capabilityQuestions = capabilityQuestionsForGlassLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$router.navigate(
|
||||||
|
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.mainStore.updateGlassParts(collectedGlassParts);
|
||||||
|
|
||||||
|
self.$router.navigate(self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,self.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateBack(vm) {
|
||||||
|
const self = vm ?? this;
|
||||||
|
const partsOrQuestions = (
|
||||||
|
self.mainStore.pageData(issPageValues.CAPABILITY_QUESTIONS) ??
|
||||||
|
self.mainStore.pageData(issPageValues.MOLDING_QUESTIONS) ??
|
||||||
|
self.mainStore.pageData(issPageValues.VEHICLE_PARTS) ??
|
||||||
|
self.mainStore.pageData(issPageValues.PART_QUESTIONS)
|
||||||
|
)?.partsOrQuestions;
|
||||||
|
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions);
|
||||||
|
const hasGlassLocationWithMultipleParts =
|
||||||
|
this.hasGlassLocationWithMultipleParts(partsOrQuestions);
|
||||||
|
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions);
|
||||||
|
const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions);
|
||||||
|
let backNavigationScenario = self.mainStore.vehicle.vin
|
||||||
|
? navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS
|
||||||
|
: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS;
|
||||||
|
|
||||||
|
const currentPage = self.$route.query.issPage;
|
||||||
|
if (
|
||||||
|
hasCapabilityQuestions &&
|
||||||
|
this.currentPageComesAfterPage(currentPage, issPageValues.CAPABILITY_QUESTIONS)
|
||||||
|
) {
|
||||||
|
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS;
|
||||||
|
} else if (
|
||||||
|
hasChildPartQuestions &&
|
||||||
|
this.currentPageComesAfterPage(currentPage, issPageValues.MOLDING_QUESTIONS)
|
||||||
|
) {
|
||||||
|
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS;
|
||||||
|
} else if (
|
||||||
|
hasGlassLocationWithMultipleParts &&
|
||||||
|
this.currentPageComesAfterPage(currentPage, issPageValues.VEHICLE_PARTS)
|
||||||
|
) {
|
||||||
|
backNavigationScenario =
|
||||||
|
navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE;
|
||||||
|
} else if (
|
||||||
|
hasPartQuestions &&
|
||||||
|
this.currentPageComesAfterPage(currentPage, issPageValues.PART_QUESTIONS)
|
||||||
|
) {
|
||||||
|
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$router.navigate(backNavigationScenario, self.$route);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import { routingTable } from "@/router/router-constants/routing-table";
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
import eventBus from "@/helpers/event-bus/event-bus";
|
import eventBus from "@/helpers/event-bus/event-bus";
|
||||||
import { globalEvents, globalEventTypes } from "@/constants/events";
|
import { globalEvents, globalEventTypes } from "@/constants/events";
|
||||||
|
import baseMixin from "@/mixins/base-mixin";
|
||||||
import {
|
import {
|
||||||
getDeviceIdValue,
|
getDeviceIdValue,
|
||||||
updateOrCreateISSCookie,
|
updateOrCreateISSCookie,
|
||||||
|
|
@ -105,14 +106,14 @@ async function GetRouteInfoFromPageName(pageName) {
|
||||||
return routeData;
|
return routeData;
|
||||||
};
|
};
|
||||||
|
|
||||||
router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}) => {
|
router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) => {
|
||||||
|
|
||||||
navigate(scenario, currentRoute, false, optionalQuery, optionalParams);
|
navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to the next route, depending on the scenario.
|
// Navigate to the next route, depending on the scenario.
|
||||||
function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = {}) {
|
function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) {
|
||||||
if (!scenario) {
|
if (!scenario) {
|
||||||
console.error("No scenario provided. Please review the routing table.");
|
console.error("No scenario provided. Please review the routing table.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -127,6 +128,16 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams =
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingScenarioMap.destinationIssPageValue) {
|
if (matchingScenarioMap.destinationIssPageValue) {
|
||||||
|
|
||||||
|
// Update page data to the store for next page if provided. Otherwise, keep existing page data or set to empty object
|
||||||
|
const existingPageDataForPage = useMainStore().pageData(matchingScenarioMap.destinationIssPageValue);
|
||||||
|
baseMixin.methods.savePageDataToStore(
|
||||||
|
matchingScenarioMap.destinationIssPageValue,
|
||||||
|
Object.keys(optionalPageData).length > 0
|
||||||
|
? optionalPageData
|
||||||
|
: existingPageDataForPage ?? {}
|
||||||
|
);
|
||||||
|
|
||||||
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our issPage one.
|
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our issPage one.
|
||||||
router.push({
|
router.push({
|
||||||
name: "root",
|
name: "root",
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,29 @@ const navigationScenarios = {
|
||||||
SELECTED_MODEL: "SELECTED_MODEL",
|
SELECTED_MODEL: "SELECTED_MODEL",
|
||||||
SELECTED_STYLE: "SELECTED_STYLE",
|
SELECTED_STYLE: "SELECTED_STYLE",
|
||||||
|
|
||||||
// TESTING
|
// Vin selection
|
||||||
CLICKED_TEST: "CLICKED_TEST"
|
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
|
||||||
|
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
|
||||||
|
CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN",
|
||||||
|
CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES: "CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES",
|
||||||
|
SELECTED_VIN_WITH_MISMATCHED_GLASS: "SELECTED_VIN_WITH_MISMATCHED_GLASS",
|
||||||
|
SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN",
|
||||||
|
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",
|
||||||
|
SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS",
|
||||||
|
CLICKED_FORWARD_WITH_NO_QUESTIONS: "CLICKED_FORWARD_WITH_NO_QUESTIONS",
|
||||||
|
|
||||||
|
// Part selection
|
||||||
|
CLICKED_FORWARD_WITH_PART_QUESTIONS: "CLICKED_FORWARD_WITH_PART_QUESTIONS",
|
||||||
|
CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE: "CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE",
|
||||||
|
CLICKED_FORWARD_WITH_MOLDING_QUESTIONS: "CLICKED_FORWARD_WITH_MOLDING_QUESTIONS",
|
||||||
|
CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS: "CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS",
|
||||||
|
CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS: "CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS",
|
||||||
|
CLICKED_BACK_WITH_PART_QUESTIONS: "CLICKED_BACK_WITH_PART_QUESTIONS",
|
||||||
|
CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE: "CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE",
|
||||||
|
CLICKED_BACK_WITH_MOLDING_QUESTIONS: "CLICKED_BACK_WITH_MOLDING_QUESTIONS",
|
||||||
|
CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: "CLICKED_BACK_WITH_CAPABILITY_QUESTIONS",
|
||||||
|
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
|
||||||
|
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { navigationScenarios };
|
export { navigationScenarios };
|
||||||
|
|
@ -73,6 +73,126 @@ const routingTable = function(store) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
issPageValue: issPageValues.PART_QUESTIONS,
|
||||||
|
maps: [
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.VIN_LOOKUP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.ESTIMATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
|
||||||
|
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.QUOTE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issPageValue: issPageValues.VEHICLE_PARTS,
|
||||||
|
maps: [
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||||
|
destinationIssPageValue: issPageValues.REVEAL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.PART_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.VIN_LOOKUP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.ESTIMATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.QUOTE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issPageValue: issPageValues.MOLDING_QUESTIONS,
|
||||||
|
maps: [
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.VIN_LOOKUP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.ESTIMATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.PART_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
|
||||||
|
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.QUOTE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
issPageValue: issPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
maps: [
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.VIN_LOOKUP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.ESTIMATE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.PART_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
|
||||||
|
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||||
|
destinationIssPageValue: issPageValues.QUOTE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
issPageValue: issPageValues.WELCOME_PAGE,
|
issPageValue: issPageValues.WELCOME_PAGE,
|
||||||
maps: [
|
maps: [
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,19 @@
|
||||||
import router from "@/router/";
|
import router from "@/router/";
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
|
|
||||||
const originalLocation = global.location
|
|
||||||
|
|
||||||
describe("Router", () => {
|
describe("Router", () => {
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const vueApp = createApp(App);
|
const vueApp = createApp(App);
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
vueApp.use(pinia);
|
vueApp.use(pinia);
|
||||||
})
|
|
||||||
|
|
||||||
it("Should change the location when navigating to external site", () => {
|
|
||||||
delete global.location
|
|
||||||
global.location = { assign: jest.fn() }
|
|
||||||
|
|
||||||
let scenario = navigationScenarios.CLICKED_TEST;
|
|
||||||
let currentRoute = { query: { issPage: issPageValues.WELCOME_PAGE }};
|
|
||||||
|
|
||||||
router.navigate(scenario, currentRoute);
|
|
||||||
|
|
||||||
expect(global.location.assign).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
global.location = originalLocation;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("Should push next view when navigating locally", () => {
|
it("Should push next view when navigating locally", () => {
|
||||||
let scenario = navigationScenarios.CLICKED_FORWARD;
|
let scenario = navigationScenarios.CLICKED_FORWARD;
|
||||||
let currentRoute = { query: { issPage: issPageValues.WELCOME_PAGE }};
|
let currentRoute = { query: { issPage: issPageValues.WELCOME_PAGE }};
|
||||||
|
|
@ -37,7 +21,7 @@ describe("Router", () => {
|
||||||
router.push = jest.fn();
|
router.push = jest.fn();
|
||||||
|
|
||||||
router.navigate(scenario, currentRoute);
|
router.navigate(scenario, currentRoute);
|
||||||
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.VEHICLE_YEAR)
|
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.VEHICLE_YEAR);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,15 @@ const getDefaultState = () => {
|
||||||
moldingQuestionAnswers: null,
|
moldingQuestionAnswers: null,
|
||||||
capabilityQuestionAnswers: null,
|
capabilityQuestionAnswers: null,
|
||||||
},
|
},
|
||||||
|
serviceLocation: {
|
||||||
|
address: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCode: null,
|
||||||
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: null,
|
glassParts: null,
|
||||||
|
otherParts: null
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
isInsurance: true,
|
isInsurance: true,
|
||||||
|
|
@ -224,6 +231,49 @@ export const useMainStore = defineStore({
|
||||||
payload: {},
|
payload: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getParts() {
|
||||||
|
const vehicle = this.getters.vehicle;
|
||||||
|
const damage = this.getters.damage;
|
||||||
|
const order = this.order;
|
||||||
|
|
||||||
|
const carId = vehicle.carId;
|
||||||
|
const glassArray = damage.glassToReplace;
|
||||||
|
const resultsArray = damage.partQuestionAnswers;
|
||||||
|
const zipCode = order.serviceLocation.zipCode;
|
||||||
|
const vin = vehicle.vin;
|
||||||
|
|
||||||
|
// create a new array to avoid mutating state
|
||||||
|
const glassArrayForPayload = convertGlassPieceNamingForApi(glassArray);
|
||||||
|
const resultsArrayForPayload = convertResultsForApi(resultsArray);
|
||||||
|
|
||||||
|
const response = await globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetParts.method,
|
||||||
|
endpoint: endpoints.GetParts.url,
|
||||||
|
payload: {
|
||||||
|
carId: carId,
|
||||||
|
glassPieces: glassArrayForPayload,
|
||||||
|
answerResults: resultsArrayForPayload,
|
||||||
|
zip: zipCode,
|
||||||
|
vin: vin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flatten location and name properties
|
||||||
|
response.data.glassPieceParts = convertGlassPieceNamingFromApi(
|
||||||
|
response.data.glassPieceParts
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
getCapabilityQuestions(carId, partNumber) {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetCapabilityQuestions.method,
|
||||||
|
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setVehicle() {
|
setVehicle() {
|
||||||
return globalMethods
|
return globalMethods
|
||||||
.callHttpClient({
|
.callHttpClient({
|
||||||
|
|
@ -377,6 +427,55 @@ export const useMainStore = defineStore({
|
||||||
this.order.damage.glassToReplace = glassToReplace;
|
this.order.damage.glassToReplace = glassToReplace;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateGlassParts(partsData) {
|
||||||
|
this.order.lineItems.glassParts = partsData;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMoldingQuestionAnswers(answersArray) {
|
||||||
|
this.order.damage.moldingQuestionAnswers = answersArray;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCapabilityQuestionAnswers(answersArray) {
|
||||||
|
this.order.damage.capabilityQuestionAnswers = answersArray;
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePartQuestionAnswers(answersArray) {
|
||||||
|
this.order.damage.partQuestionAnswers = answersArray;
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePageData(pageData) {
|
||||||
|
this.applicationUser.pageData[pageData.page] = pageData.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
savePartQuestionAnswers(partQuestionAnswersArray) {
|
||||||
|
// if part question answers have changed, reset subsequent question answers
|
||||||
|
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||||
|
context.getters.damage.partQuestionAnswers,
|
||||||
|
"result"
|
||||||
|
);
|
||||||
|
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
|
||||||
|
partQuestionAnswersArray,
|
||||||
|
"result"
|
||||||
|
);
|
||||||
|
const havePartQuestionAnswersChanged =
|
||||||
|
sortedPreviousResultsArray?.length !== sortedPartQuestionAnswersArray.length ||
|
||||||
|
!sortedPreviousResultsArray?.every(
|
||||||
|
(x, i) => x.result === sortedPartQuestionAnswersArray[i].result
|
||||||
|
);
|
||||||
|
|
||||||
|
if (havePartQuestionAnswersChanged) {
|
||||||
|
this.updateGlassParts(null);
|
||||||
|
this.updateMoldingQuestionAnswers(null);
|
||||||
|
this.updateCapabilityQuestionAnswers(null);
|
||||||
|
this.updatePageData({ page: issPageValues.VEHICLE_PARTS, data: null});
|
||||||
|
this.updatePageData({ page: issPageValues.MOLDING_QUESTIONS, data: null});
|
||||||
|
this.updatePageData({ page: issPageValues.CAPABILITY_QUESTIONS, data: null});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save new values
|
||||||
|
this.updatePartQuestionAnswers(partQuestionAnswersArray);
|
||||||
|
},
|
||||||
|
|
||||||
addEventToBus (event) {
|
addEventToBus (event) {
|
||||||
this.applicationUser.eventBus.push(event);
|
this.applicationUser.eventBus.push(event);
|
||||||
},
|
},
|
||||||
|
|
@ -574,3 +673,56 @@ function getHasRecalibrationPart(state) {
|
||||||
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||||
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
|
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
||||||
|
if (!arrayOfObjects) return null;
|
||||||
|
|
||||||
|
return arrayOfObjects.sort((a, b) => {
|
||||||
|
if (a[propertyName] < b[propertyName]) return -1;
|
||||||
|
else if (a[propertyName] > b[propertyName]) return 1;
|
||||||
|
else return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertGlassPieceNamingForApi(glassArray) {
|
||||||
|
if (!glassArray || glassArray.length === 0) return [];
|
||||||
|
|
||||||
|
// check if array already converted. (likely when a session has been saved previously and then reloaded)
|
||||||
|
if (glassArray[0].location !== undefined) {
|
||||||
|
return glassArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
const converted = [];
|
||||||
|
glassArray.forEach((glass) => {
|
||||||
|
converted.push({
|
||||||
|
location: glass.glassLocation,
|
||||||
|
name: glass.glassName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertResultsForApi(resultsArray) {
|
||||||
|
if (!resultsArray) return [];
|
||||||
|
const converted = [];
|
||||||
|
resultsArray.forEach((answer) => {
|
||||||
|
converted.push({
|
||||||
|
location: answer.glassLocation,
|
||||||
|
name: answer.glassName,
|
||||||
|
result: answer.result,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertGlassPieceNamingFromApi(glassArray) {
|
||||||
|
glassArray.forEach((glass) => {
|
||||||
|
glass.glassLocation = glass.glassPiece.location;
|
||||||
|
glass.glassName = glass.glassPiece.name;
|
||||||
|
delete glass.glassPiece;
|
||||||
|
return glass;
|
||||||
|
});
|
||||||
|
return glassArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue