CSR-731 | Working version before refactor

This commit is contained in:
Scott Kiener 2022-10-12 10:53:12 -04:00
parent a8d9060b22
commit a60398a55b
10 changed files with 488 additions and 116 deletions

View file

@ -52,7 +52,8 @@
:selectOnKeypress="selectOnKeypress"
@blur="handleBlur"
@focus="handleFocus"
@buttonClicked="handleAnswerChange" />
@buttonClicked="handleAnswerChange"
:additionalData="additionalData" />
<!-- For nested questions -->
<transition name="fade" mode="out-in">
<div
@ -121,6 +122,7 @@ export default {
type: Boolean,
default: true,
},
additionalData: null
},
data() {
return {
@ -157,7 +159,7 @@ export default {
classes = "ui-radio d-flex";
break;
case "servicePackageRadio":
classes = "ui-radio d-flex service-package";
classes = "package-main";
break;
}
return classes;
@ -165,10 +167,12 @@ export default {
getComponentWrapperClasses() {
let classes = "";
classes += this.isWid ? "col-12" : "col";
classes += this.isWide ? "col-12" : "col";
if (this.buttonType == "radio") {
classes += " radio-button-container";
} else if (this.buttonType == "servicePackageRadio") {
classes = "package-wrapper";
}
return classes;

View file

@ -1,7 +1,8 @@
const dynamicStrings = {
GLOBAL_STATE: "globalState",
CUSTOM: "custom",
ROUTER_LINK: "routerLink:"
ROUTER_LINK: "routerLink:",
TEXT_LINK: "textLink:",
};
export { dynamicStrings };

View file

@ -46,22 +46,16 @@ function mapStringToState(str) {
return match[1] === dynamicStrings.GLOBAL_STATE;
})
// Our final string value that will be built from the matches.
let stringBuilder = "";
for (const match of globalStateMatches) {
// Reset store state for each match.
let storeState = store.state;
for (const s of match[2].split(".")) {
if (storeState[s] != undefined) {
storeState = storeState[s];
} else {
return ""; // if we can't map our string to state data, return an empty string.
}
const valueFromStore = getStoreValueFromString(match[2]);
if (!valueFromStore) {
return ""; // if we can't map our string to state data, return an empty string.
}
const stringWithReplacement = str.replace(match[0], storeState);
const stringWithReplacement = str.replace(match[0], valueFromStore);
// If we still have values we need to substitute, call this function again.
if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
@ -71,7 +65,6 @@ function mapStringToState(str) {
// Concatenate the string.
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
}
return stringBuilder.trimStart();
}
@ -100,10 +93,11 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
function processWidgetItemForReplacement(widgetModel, key) {
// If we have a string, and it needs to be replaced.
if (typeof widgetModel[key] === "string") {
widgetModel[key] = checkForIfStatements(widgetModel[key], dynamicStrings.GLOBAL_STATE, getStoreValueFromString);
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
widgetModel[key] = mapStringToState(widgetModel[key]);
}
return widgetModel[key];
return reconstructPageLevelIfStatements(widgetModel[key]);
}
// If we have an object. array, etc
@ -122,6 +116,89 @@ function processWidgetItemForReplacement(widgetModel, key) {
return widgetModel[key];
}
export function checkForIfStatements(str, ifConditionString, replaceStringCallback) {
// Pattern to split off each {if:...}, {else}, {end} and their trailing strings
const regexExp = new RegExp("(?<cleanString>^.+?)(?=(?:{if))|(?<ifOperator>{if:)(?<ifConditionType>.*?:)(?<ifCondition>.*?)}(?<ifTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)}))|(?<elseOperator>{else})(?<elseTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)}))|(?<endOperator>{end})(?<endTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)})|$)", "g");
const regexMatches = [...str.matchAll(regexExp)];
// Find and evaluate full globalState if statements
if (regexMatches.length) {
regexMatches.forEach((match, index) => {
if(match.groups.ifOperator) {
if(regexMatches[index+1].groups.endOperator) {
processIfStatement(regexMatches.slice(index, index+2), ifConditionString, replaceStringCallback) + regexMatches[index+1].groups.endTrailingString;
}
else if(regexMatches[index+2].groups.endOperator) {
processIfStatement(regexMatches.slice(index, index+3), ifConditionString, replaceStringCallback)+ regexMatches[index+2].groups.endTrailingString;
}
}
});
const processedString = joinProcessedRegexArray(regexMatches);
return checkForIfStatements(processedString, ifConditionString, replaceStringCallback);
} else {
return str;
}
}
function reconstructPageLevelIfStatements(str) {
return str.replaceAll("(((", "{").replaceAll(")))","}");
}
function joinProcessedRegexArray(regexMatches) {
let processedString = "";
regexMatches.forEach((match) => {
processedString += match.groups.cleanString ?? match[0];
});
return processedString;
}
function processIfStatement(ifStatementArray, ifConditionString, replaceStringCallback) {
if(ifStatementArray[0].groups.ifConditionType.includes(ifConditionString)) {
//replace the if condition with a real value
const ifCondition = replaceStringCallback(ifStatementArray[0].groups.ifCondition);
//convert the entire if statement
let processedIfStatementString;
if (ifCondition) {
processedIfStatementString = ifStatementArray[0].groups.ifTrailingString;
} else {
processedIfStatementString = ifStatementArray[1].groups.elseTrailingString ?? "";
}
ifStatementArray.forEach((entry) => {
if (entry.groups.ifOperator) {
entry.groups.cleanString = processedIfStatementString;
} else if (entry.groups.endOperator) {
entry.groups.cleanString = entry.groups.endTrailingString;
} else {
entry.groups.cleanString = "";
}
})
} else {
// stow the if statement to reconstruct later when passing to the page
ifStatementArray.forEach((entry) => {
const groups = entry.groups;
if (groups.ifOperator)
groups.cleanString = "(((if:" + groups.ifConditionType + groups.ifCondition + ")))" + groups.ifTrailingString;
else if (groups.elseOperator)
groups.cleanString = "(((else)))" + groups.elseTrailingString;
else
groups.cleanString = "(((end)))" + groups.endTrailingString;
});
}
}
function getStoreValueFromString(str) {
let storeOrStateObject = str.includes('getters') ? store :store.state;
for (const s of str.split(".")) {
if (storeOrStateObject[s] != undefined) {
storeOrStateObject = storeOrStateObject[s];
} else {
return ""; // if we can't map our string to state data, return an empty string.
}
}
return storeOrStateObject;
}
export function doesCopyContainRouterLink(copy) {
return copy.includes(this.dynamicStrings.ROUTER_LINK);

View file

@ -64,6 +64,7 @@
<alert
v-if="displayNonServiceableZipAlert"
class="my-3"
cmsWidgetName="AlertNonServiceableZipWidget"
:manualHeadline="AlertNonServiceableZipHeader"
:manualCopy="AlertNonServiceableZipBody"
alertClass="alert-danger"

View file

@ -20,16 +20,17 @@
<cashOrInsuranceQuestion
ref="cashOrInsurance"
cmsWidgetName="CashOrInsuranceQuestionWidget"
v-model="isInsurance"
v-model="isInsuranceSelected"
groupName="CashOrInsuranceQuestion"
/>
<radioServicePackage/>
<servicePackageQuestion
ref="servicePackage"
cashCmsWidgetName="CashServicePackageQuestionWidget"
insuranceCmsWidgetName="InsuranceServicePackageWidget"
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
v-model="selectedPackage"
groupName="ServicePackageQuestion"
:lineItems="lineItems"
:isInsuranceSelected="isInsuranceSelected"
/>
<textBlock
cmsWidgetName="quoteDisclaimer"
@ -125,7 +126,6 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import radioServicePackage from "@/ux-components/radio-service-package/radio-service-package";
import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insurance-question";
import servicePackageQuestion from "./service-package-question/service-package-question";
import textBlock from "@/common-components/text-block/text-block";
@ -156,34 +156,68 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
console.log("cmsContent", cmsContent);
vm.setCmsContent(cmsContent);
vm.isInsurance = vm.getDefaultIsInsuranceValue();
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue();
vm.selectedPackage = vm.getDefaultSelectedPackageValue();
vm.lineItems = [
{
PartNumber : "001",
Description : "Windshield with Recal",
PartType : "Windshield",
Quantity : "1",
BasePartNumber : "001",
Color : "Green",
CanSafeliteRecalibrate: true,
Price : 350.00
},
{
PartNumber : "002",
Description : "Front Wipers",
PartType : "frontWiper",
Quantity : "1",
Price : 35.00,
},
{
PartNumber : "003",
Description : "Rear Wiper",
PartType : "rearWiper",
Quantity : "1",
Price : 25.00,
},
{
PartNumber : "004",
Description : "Rain Defense",
PartType : "rainDefense",
Quantity : "1",
Price : 30.00,
},
{
PartNumber : "005",
Description : "Recalibration",
PartType : "recalibration",
Quantity : "1",
Price : 150.00,
},
];
});
},
data(){
return {
cashOrInsuranceThreshhold: 600,
pricingRequestResult: null,
isInsurance: null,
isInsuranceSelected: null,
selectedPackage: null,
}
},
watch: {
selectedPackage(newPackage, oldPackage) {
console.log("new package: ", newPackage);
console.log("old package: ", oldPackage);
lineItems: null,
}
},
methods: {
arePagePrerequisitesValid() {
return store.getters.order.damage.isRepair || (store.getters.order.lineItems?.glassParts != null && store.getters.order.lineItems.glassParts.length > 0);
},
getDefaultIsInsuranceValue() {
var defaultIsInsuranceValue = this.$store.getters.order.payment.isInsurance;
if(defaultIsInsuranceValue != null) {
return defaultIsInsuranceValue;
getDefaultIsInsuranceSelectedValue() {
const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance;
if(defaultIsInsuranceSelectedValue != null) {
return defaultIsInsuranceSelectedValue;
} else {
return this.economyPackagePrice > this.cashOrInsuranceThreshhold;
}
@ -194,16 +228,6 @@ export default {
},
},
computed: {
years() {
return ["2011", "2012"];
},
answersFromCms() {
return ["Pay on my own", "Pay with insurance"];
},
economyPackagePrice() {
//TODO build out the pricing logic CSR-504
return 0;
},
selectedChipCountValues: {
get: function() {
return this.modelValue;
@ -219,7 +243,6 @@ export default {
vehicleBanner,
funnelSubHeader,
Form,
radioServicePackage,
textBlock,
cashOrInsuranceQuestion,
servicePackageQuestion,

View file

@ -1,13 +1,22 @@
<template>
<div class="service-package-question">
<buttonQuestion
:answers="cashAnswersFromCms"
:groupName="groupName"
buttonType="servicePackageRadio"
v-model="selectedValues"
isRequired
/>
</div>
<buttonQuestion
v-if="!isInsuranceSelected"
:answers="cashAnswersFromCms"
:groupName="groupName"
buttonType="servicePackageRadio"
v-model="selectedValues"
isRequired
:additionalData="additionalData"
/>
<buttonQuestion
v-if="isInsuranceSelected"
:answers="insuranceAnswersFromCms"
:groupName="groupName"
buttonType="servicePackageRadio"
v-model="selectedValues"
isRequired
:additionalData="additionalData"
/>
</template>
<script>
@ -20,22 +29,33 @@ export default ({
groupName: String,
cashCmsWidgetName: String,
insuranceCmsWidgetName: String,
isInsuranceSelected: Boolean,
lineItems: null,
},
computed: {
cashAnswersFromCms(){
let cashAnswers = "";
let cmsContent = this.getCmsContent(this.cashCmsWidgetName, 'Answers');
const cmsContent = this.getCmsContent(this.cashCmsWidgetName, 'Answers');
if (cmsContent) {
cashAnswers = cmsContent?.map((x) => ({
buttonLabel : x.Name,
value : x.SubWidgetName
value : x.SubWidgetName,
}));
}
return cashAnswers;
},
insuranceAnswersFromCms() {
return this.getCmsContent(this.insuranceCmsWidgetName, 'Answers');
// I'm starting to think there is no reason to have a separate insurance package answers
// the only benefit would be
insuranceAnswersFromCms() {
let insuranceAnswers = "";
const cmsContent = this.getCmsContent(this.insuranceCmsWidgetName, 'Answers');
if (cmsContent) {
insuranceAnswers = cmsContent?.map((x) => ({
buttonLabel : x.Name,
value : x.SubWidgetName,
}));
}
return insuranceAnswers;
},
selectedValues: {
get: function() {
@ -45,9 +65,15 @@ export default ({
this.$emit("update:modelValue", newValue);
}
},
additionalData() {
return {
lineItems: this.lineItems,
isInsuranceSelected: this.isInsuranceSelected
};
},
},
components: {
buttonQuestion,
},
})
</script>
</script>

View file

@ -1,27 +1,50 @@
<template>
<baseInputButton
v-if="shouldDisplayThisPackage"
v-bind="$props"
buttonWrapperClasses="ui-radio form-check test000"
inputClasses="form-check-input"
@buttonClicked="handleAnswerChange">
<div class="d-flex align-items-start form-check-label">
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
<span v-if="screenReaderOnlyText" class="sr-only">{{
screenReaderOnlyText
}}</span>
<div class="package-label" for="testradio">
<div class="package-specs">
<p :class="[this.getSubHeaderTextFromCms ? 'mb-2' : 'm-0']">
<span v-html="this.getHeaderTextFromCms"></span>
<span class="pricing-info" v-html="getPackagePriceString"></span>
</p>
<p class="sub-label m-0" v-if="this.getSubHeaderTextFromCms" v-html="this.getSubHeaderTextFromCms"></p>
<!-- Parse the body text containing <ul> with logic -->
<ul>
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem">
<!-- no textLink -->
<span v-if="!doesCopyContainTextLink(listItem)" v-html="listItem"></span>
<!-- with textLink -->
<template v-else v-for="copy in splitCopyOnCMSPlaceHolder(listItem)" :key="copy">
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span>
<span v-else>
<textLink linkType="dashedUnderline" :text="getTextLinkDisplayTextFromCopy(copy)" href="#!" data-bs-toggle="modal" :data-bs-target="'#' + getTextAreaTargetFromCopy(copy)" aria-label="Modal window" />
</span>
</template>
</li>
</ul>
<!-- End of body text parsing -->
<div class="package-footer" v-if="getFooterTextFromCms" v-html="getFooterTextFromCms"></div>
</div>
</div>
</baseInputButton>
</template>
<script>
import baseInputButton from "@/common-components/base-input-button/base-input-button";
import textLink from "@/ux-components/text-link/text-link";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
import baseMixin from "@/mixins/base-mixin.js";
import { splitCopyOnCMSPlaceHolder, checkForIfStatements } from "@/helpers/cms-content-helper";
import { dynamicStrings } from "@/constants/dynamic-strings";
export default {
name: "servicePackageRadio",
mixins: [inputButtonWrapperMixin],
components: {
baseInputButton,
textLink,
},
computed: {
getHeaderTextFromCms(){
@ -31,63 +54,269 @@ export default {
return this.getCmsContent(this.value, 'SubheaderText');
},
getBodyTextFromCms(){
return this.getCmsContent(this.value, 'BodyText');
const bodyText = this.getCmsContent(this.value, 'BodyText');
return this.checkForIfStatements(bodyText, "custom", this.getCustomValueFromString);
},
getFooterTextFromCms(){
return this.getCmsContent(this.value, 'FooterText');
const footerText = this.getCmsContent(this.value, 'FooterText');
return this.checkForIfStatements(footerText, "custom", this.getCustomValueFromString);
},
getPackagePriceString() {
const formattedPriceFloat = parseFloat(this.getPackagePrice()).toFixed(2);
return (this.additionalData.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
},
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.getBodyTextFromCms);
},
isRecalibrationOnOrder() {
return this.lineItemsContainsPartType("recalibration");
},
frontWipersApplicableForStandard() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
const isRepair = this.$store.getters.order.damage.isRepair;
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation("Windshield");
if (frontWipersAreAvailable) {
if (isRepair) {
return true;
} else {
if (glassToReplaceContainsWindshield) {
return true;
} else {
return false;
}
}
} else {
return false;
}
},
rearWiperApplicableForStandard() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("rearWiper");
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
},
frontWipersApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
return frontWipersAreAvailable;
},
rearWiperApplicableForPremium() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("rearWiper");
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
},
rainDefenseApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation("Windshield");
const glassToReplaceContainsRearGlass = this.glassToReplaceContainsGlassLocation("Rear");
if(this.frontWipersApplicableForStandard) {
return true;
} else if (!frontWipersAreAvailable) {
return true;
} else if (!glassToReplaceContainsWindshield && !glassToReplaceContainsRearGlass) {
return true;
} else {
return false;
}
},
shouldDisplayThisPackage() {
if (this.value != "StandardServicePackage") {
return true;
} else if (this.frontWipersApplicableForStandard || this.rearWiperApplicableForStandard) {
return true;
} else {
return false;
}
}
},
methods: {
splitCopyOnCMSPlaceHolder,
checkForIfStatements,
stripUlTagFromCopy(copy) {
const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g;
return copy.replace(regex, '');
},
getArrayOfListItemsFromRawCmsCopy(copy) {
const withoutUlTags = this.stripUlTagFromCopy(copy);
return withoutUlTags.split(/(?:<li(?:.*?)>)|(?:<\/li>)/g).filter(lineItem => lineItem);
},
doesCopyContainTextLink(copy) {
return copy.includes(dynamicStrings.TEXT_LINK);
},
getTextAreaTargetFromCopy(copy){
// sample input: {textLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'estimate'
return copy.split(':')[1].split(',')[0];
},
getTextLinkDisplayTextFromCopy(copy){
// sample input: {textLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'provide your VIN'
return copy.split(':')[1].split(',')[1];
},
getCustomValueFromString(str) {
return this[str];
},
lineItemsContainsPartType(partType) {
const results = this.additionalData.lineItems.filter(lineItem => lineItem.PartType === partType);
return !!results.length;
},
glassToReplaceContainsGlassLocation(glassLocation) {
const glassLocationMatches = this.$store.getters.order.damage.glassToReplace.filter(glassToReplace => glassToReplace.glassLocation === glassLocation);
return !!glassLocationMatches.length;
},
getPackagePrice() {
let priceFloat = this.additionalData.isInsuranceSelected ? 0 : baseMixin.methods.getEconomyPackagePrice(this.additionalData.lineItems);
if (this.value === "StandardServicePackage") {
priceFloat += this.getStandardPackageVapsPrice();
} else if (this.value === "PremiumServicePackage") {
priceFloat += this.getPremiumPackageVapsPrice();
}
return priceFloat;
},
getStandardPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForStandard;
const priceRearWipers = this.rearWiperApplicableForStandard;
this.additionalData.lineItems.forEach(item => {
if ((priceFrontWipers && item.PartType === "frontWiper") || (priceRearWipers && item.PartType === "rearWiper")) {
vapsPrice += item.Price;
}
});
return vapsPrice;
},
getPremiumPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForPremium;
const priceRearWipers = this.rearWiperApplicableForPremium;
const priceRainDefense = this.rainDefenseApplicableForPremium;
this.additionalData.lineItems.forEach(item => {
if ((priceFrontWipers && item.PartType === "frontWiper") || (priceRearWipers && item.PartType === "rearWiper") || (priceRainDefense && item.PartType === "rainDefense")) {
vapsPrice += item.Price;
}
});
return vapsPrice;
},
},
};
</script>
<style lang="scss">
.form-check {
position: relative;
.package-main {
.package-wrapper {
margin: 0.5rem 0;
.form-check-input {
border: 1px solid $gray-500;
border-radius: 50%;
margin-right: 0.5rem;
opacity: 1;
height: 1em;
width: 1em;
&:checked {
background-color: $white;
background-size: 71%;
background-position: center;
border: 1px solid $blue;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' xml:space='preserve'%3e%3ccircle cx='25' cy='25' r='25' fill='%231574A1'/%3e%3c/svg%3e");
+ .form-check-label {
p {
font-weight: 500;
font-size: 0.875rem;
color: $black;
}
}
}
&:focus {
box-shadow: 0 0 0 2.5px $blue;
}
&,
& + .form-check-label {
margin-top: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
label {
display: block;
}
&:hover {
.form-check-input {
box-shadow: 0 0 0 4px $blue-300;
input[type="radio"] {
opacity: 0;
position: absolute;
left: -9999px;
+ .package-label {
display: flex;
align-items: flex-start;
position: relative;
cursor: pointer;
width: 100%;
padding: 1rem;
border: 1px solid $gray-300;
box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2);
border-radius: .5rem;
overflow: hidden;
min-height: 58px;
&:before {
content: "";
position: relative;
top: 5px;
margin-right: 1rem;
border-radius: 50%;
border: 1px solid $gray-500;
width: 16px;
height: 16px;
min-width: 16px;
}
&:after {
content: "";
position: absolute;
left: 19px;
top: 24px;
border-radius: 50%;
width: 10px;
height: 10px;
min-width: 10px;
}
}
&:hover {
+ .package-label {
&:before {
border: 1px solid #8E9292;
box-shadow: 0px 0px 0px 4px #9FCEE6, 0px 1px 4px rgba(0, 0, 0, 0.2);
}
}
}
&:checked {
+ .package-label {
.package-specs {
max-height: 1000px;
}
}
+ .package-label {
background-color: $blue-100;
border: 1px solid $blue;
max-height: 500px;
}
+ .package-label {
&:before {
box-shadow: 0px 0px 0px 1px $blue;
}
}
+ .package-label {
&:after {
background: $blue;
}
}
}
&:focus {
+ .package-label {
&:before {
border: 2px solid $blue;
}
}
}
}
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
.package-specs {
display: flex;
flex-direction: column;
width: 100%;
max-height: 0;
transition: all .75s ease;
p {
font-weight: 500;
display: flex;
justify-content: space-between;
span.pricing-info {
color: $green;
}
&.sub-label {
color: $green;
text-transform: uppercase;
}
}
ul {
margin: 1rem 0 0 -15px;
padding: 0;
li {
margin-bottom: .5rem;
}
}
}
}
}
</style>

View file

@ -56,6 +56,14 @@ export default {
isServiceable: serviceZipValidationResponse.data.isServiceable,
state: serviceZipValidationResponse.data.state
};
},
getEconomyPackagePrice(lineItems) {
const nonVapsItems = lineItems.filter((lineItem) => {
return lineItem.PartType != "frontWiper" && lineItem.PartType != "rearWiper" && lineItem.PartType != "rainDefense";
});
let totalPrice = 0;
nonVapsItems.forEach(item => totalPrice += item.Price);
return totalPrice;
}
},
computed: {

View file

@ -16,7 +16,8 @@ export default {
valueToLogType: String,
validationRules: String,
isWide: Boolean,
isRequired: Boolean
isRequired: Boolean,
additionalData: null,
},
data() {
return {
@ -31,7 +32,6 @@ export default {
if (this.preHandleAnswerChange) {
this.preHandleAnswerChange(e)
}
this.$emit("change", e);
},
},

View file

@ -50,7 +50,7 @@ const getDefaultState = () => {
glassToReplace: null,
partQuestionAnswers: null,
moldingQuestionAnswers: null,
capabilityQuestionAnswers: null
capabilityQuestionAnswers: null,
},
lineItems: {
glassParts: null
@ -372,6 +372,10 @@ export const getters = {
},
eventBus: (state) => state.applicationUser.eventBus,
damage: (state) => state.order.damage,
hasAnyNonWindshieldGlassParts: (state) => {
const nonWindshieldItems = state.order.damage.glassToReplace.filter(glassToReplace => glassToReplace.glassLocation != "Windshield");
return !!nonWindshieldItems.length;
},
lineItems: (state) => state.order.lineItems,
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
applicationUser: (state) => state.applicationUser,
@ -1133,7 +1137,6 @@ function getHasRecalibrationPart(state) {
} else { // Does not have 'requiresRecalibration'
return false;
}
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {