567 lines
21 KiB
Vue
567 lines
21 KiB
Vue
<template>
|
|
<div>
|
|
<div
|
|
id="cart-dropdown-head"
|
|
class="row cart-toggle flex align-items-center pt-4"
|
|
:class="[isExpanded ? 'expanded' : '']"
|
|
@click="toggleIsExpanded">
|
|
<a
|
|
aria-label="expand cart details"
|
|
href="javascript:void(0)"
|
|
class="col d-flex justify-content-between py-0">
|
|
<span class="color-black">{{ amountDueLabel }}</span>
|
|
<span class="color-green">{{ getDisplayed(amountDue) }}</span>
|
|
</a>
|
|
</div>
|
|
<div
|
|
id="cart-table"
|
|
class="cart-table">
|
|
<div
|
|
id="cart-line-items"
|
|
class="color-darker-gray">
|
|
<div
|
|
id="cart-deductible-or-base-price"
|
|
class="cart-item cart-gray">
|
|
<div
|
|
v-if="showDeductibleCartItem"
|
|
id="cart-deductible"
|
|
class="d-flex justify-content-between align-items-center">
|
|
<span id="deductible-label">{{ deductibleLabel }}</span>
|
|
<span id="deductible-value">{{ getDisplayed(deductible) }}</span>
|
|
</div>
|
|
<div
|
|
v-else
|
|
id="cart-base-price"
|
|
class="d-flex justify-content-between align-items-center">
|
|
<span id="base-price-label">{{ basePriceLabel }}</span>
|
|
<span id="base-price-value">{{ getDisplayed(baseServicePrice) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Packaged Cart Items -->
|
|
<div
|
|
id="cart-service-package"
|
|
class="px-5">
|
|
<div
|
|
id="service-package-name"
|
|
class="py-1 d-flex justify-content-between align-items-center">
|
|
<textBlock
|
|
:cmsWidgetName="servicePackageLabelWidget"
|
|
class="force-no-top-margin" />
|
|
<span id="service-package-price">{{ formatAmountInDollars(packagePrice) }}</span>
|
|
</div>
|
|
<div
|
|
id="service-items"
|
|
class="ps-4">
|
|
<div
|
|
v-for="(item, i) in servicePackageCartItems"
|
|
:key="i">
|
|
<div class="py-1 d-flex justify-content-between align-items-center">
|
|
<span>{{ item?.name ?? '' }}</span>
|
|
<textLink
|
|
v-if="!readOnly || !showAsPaid"
|
|
linkType="text"
|
|
text="Remove"
|
|
href="javascript:void(0)"
|
|
@clickEvent="removeVap(item.partType)">
|
|
<template #after-text>
|
|
<span class="sr-only">{{ item?.name ?? '' }}</span>
|
|
</template>
|
|
</textLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NonPackaged Cart Items -->
|
|
<div id="non-packaged-cart-items">
|
|
<div
|
|
v-for="(item, i) in nonServicePackageCartItems"
|
|
:key="i"
|
|
class="px-5 py-1 non-packaged-cart-item">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span
|
|
v-if="isRecycle(item.partType)"
|
|
id="recycle-fee-label">
|
|
<textLink
|
|
linkType="text"
|
|
:text="item?.name ?? ''"
|
|
href="javascript:void(0)"
|
|
@clickEvent="openModal(recyclingModalCmsWidgetName)" />
|
|
</span>
|
|
<span v-else>{{ item?.name ?? '' }}</span>
|
|
<span>{{ getDisplayed(item?.subTotal ?? 0, false) }}</span>
|
|
</div>
|
|
<textLink
|
|
v-if="isVap(item.partType) && (!readOnly || !showAsPaid)"
|
|
linkType="text"
|
|
text="Remove"
|
|
href="javascript:void(0)"
|
|
@clickEvent="removeVap(item.partType)">
|
|
<template #after-text>
|
|
<span class="sr-only">{{ item?.name ?? '' }}</span>
|
|
</template>
|
|
</textLink>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="cart-footer"
|
|
class="cart-footer pb-5">
|
|
<div
|
|
id="cart-subtotal-and-tax"
|
|
class="subtotal-and-tax">
|
|
<div
|
|
id="cart-subtotal"
|
|
class="col d-flex justify-content-between">
|
|
<span id="subtotal-label">{{ subtotalLabel }}</span>
|
|
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
|
|
</div>
|
|
<div
|
|
id="cart-sales-tax"
|
|
class="pt-1 col d-flex justify-content-between">
|
|
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
|
|
<span id="sales-tax-value">{{ formatAmountInDollars(salesTax) }}</span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="bottom-amount-due"
|
|
class="bottom-amount-due col d-flex justify-content-between">
|
|
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
|
|
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<contentGroupModal
|
|
ref="RecycleModal"
|
|
:cmsWidgetName="recyclingModalCmsWidgetName">
|
|
<textBlock cmsWidgetName="RecycleTextBlock" />
|
|
</contentGroupModal>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { useMainStore } from '@/store';
|
|
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
|
import textBlock from '@/digital-components/text-block/text-block.vue';
|
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
|
import getPriceOfLineItems from '@/helpers/price-calculator.js';
|
|
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
|
import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js';
|
|
|
|
// Constants
|
|
import partTypeStrings from '@/constants/part-type-strings.js';
|
|
import partNumberStrings from '@/constants/part-number-strings.js';
|
|
import widgetFields from '@/constants/cms-widget-fields.js';
|
|
|
|
const VERIFYING_COVERAGE = 'Verifying coverage';
|
|
|
|
export default {
|
|
name: 'cart-dropdown',
|
|
components: {
|
|
contentGroupModal,
|
|
textBlock,
|
|
textLink
|
|
},
|
|
props: {
|
|
showAsPaid: Boolean,
|
|
readOnly: Boolean,
|
|
recyclingModalCmsWidgetName: String,
|
|
availableVaps: Array
|
|
},
|
|
data() {
|
|
return {
|
|
isExpanded: false,
|
|
widget: {
|
|
amountDue: 'AmountDueTextWidget',
|
|
deductible: 'DeductibleWidget',
|
|
basePrice: 'BasePriceWidget',
|
|
subtotal: 'SubtotalWidget',
|
|
salesTax: 'SalesTaxWidget',
|
|
recycleFee: 'RecycleFeeWidget',
|
|
mobileFee: 'MobileServiceWidget',
|
|
servicePackage: 'ServicePackageTitle',
|
|
vapsItemDescriptions: 'VapsItemDescriptions'
|
|
}
|
|
};
|
|
},
|
|
computed: {
|
|
showDeductibleCartItem() {
|
|
return !useMainStore().isNoComp && !useMainStore().policy.isITAC;
|
|
},
|
|
deductible() {
|
|
// Note: added as computed so it can be used in the template.
|
|
return useMainStore().order.currentDeductible;
|
|
},
|
|
baseServiceLineItems() {
|
|
return [
|
|
...(useMainStore().lineItems.supportingItems ?? []),
|
|
...(useMainStore().lineItems.glassParts ?? []),
|
|
...(useMainStore().lineItems.otherParts ?? [])
|
|
];
|
|
},
|
|
baseServicePrice() {
|
|
return getPriceOfLineItems(this.baseServiceLineItems) ?? 0;
|
|
},
|
|
isUnverified() {
|
|
return !useMainStore().isNoComp && !useMainStore().policy.isITAC
|
|
&& (this.deductible == null || !useMainStore().isVerifiedCoverageStatus);
|
|
},
|
|
subTotal() {
|
|
const basePrice = !useMainStore().isNoComp && !useMainStore().policy.isITAC
|
|
? this.deductible
|
|
: this.baseServicePrice;
|
|
const nonPackageCartItemsPrice = this.nonServicePackageCartItems?.reduce(
|
|
(accumulator, cartItem) => accumulator + (cartItem?.subTotal ?? 0),
|
|
0
|
|
) ?? 0;
|
|
return basePrice + nonPackageCartItemsPrice + this.packagePrice;
|
|
},
|
|
salesTax() {
|
|
function sumTax(lineItems) {
|
|
return lineItems?.reduce((accumulator, lineItem) => accumulator + (lineItem.salesTax ?? 0), 0) ?? 0;
|
|
}
|
|
|
|
let result = 0;
|
|
|
|
if (useMainStore().payment.insuranceCoverage.isVerified) {
|
|
if (useMainStore().isITAC || useMainStore().isNoComp) {
|
|
result += sumTax(this.baseServiceLineItems);
|
|
} else {
|
|
// deductible-case need to show tax for Recycle Fee
|
|
const recycleFeeLineItem = useMainStore().lineItems.supportingItems
|
|
?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE);
|
|
|
|
result += recycleFeeLineItem != null ? recycleFeeLineItem.salesTax : 0;
|
|
}
|
|
}
|
|
|
|
result += sumTax(useMainStore().lineItems.vaps ?? []);
|
|
|
|
return result;
|
|
},
|
|
amountDue() {
|
|
return this.showAsPaid
|
|
? 0
|
|
: this.subTotal + this.salesTax;
|
|
},
|
|
availableLineItems() {
|
|
const { glassParts, supportingItems } = useMainStore().lineItems;
|
|
return [
|
|
...(glassParts ?? []),
|
|
...(supportingItems ?? []),
|
|
...(this.availableVaps ?? [])
|
|
];
|
|
},
|
|
servicePackageTier() {
|
|
return getHighestFullySatisfiedTier(
|
|
useMainStore().damage.glassToReplace ?? [],
|
|
this.availableLineItems,
|
|
useMainStore().damage.isRepair,
|
|
useMainStore().lineItems.vaps ?? []
|
|
);
|
|
},
|
|
partTypesInServicePackage() {
|
|
return getPackageContents(
|
|
useMainStore().damage.glassToReplace ?? [],
|
|
this.availableLineItems,
|
|
useMainStore().damage.isRepair,
|
|
this.servicePackageTier
|
|
) ?? [];
|
|
},
|
|
servicePackageCartItems() {
|
|
const items = [];
|
|
if (this.partTypesInServicePackage.includes(partTypeStrings.FRONT_WIPER) ?? this.frontWipersCartItem) {
|
|
items.push(this.frontWipersCartItem);
|
|
}
|
|
if (this.partTypesInServicePackage.includes(partTypeStrings.REAR_WIPER) ?? this.rearWipersCartItem) {
|
|
items.push(this.rearWipersCartItem);
|
|
}
|
|
if (this.partTypesInServicePackage.includes(partTypeStrings.RAIN_DEFENSE) ?? this.rainDefenseCartItem) {
|
|
items.push(this.rainDefenseCartItem);
|
|
}
|
|
return items;
|
|
},
|
|
nonServicePackageCartItems() {
|
|
const partTypesInOrder = useMainStore().lineItems.vaps?.reduce(
|
|
(accumulator, vap) => {
|
|
if (!accumulator.includes(vap.partType)) {
|
|
accumulator.push(vap.partType);
|
|
}
|
|
return accumulator;
|
|
},
|
|
[]
|
|
) ?? [];
|
|
const partTypesInOrderButNotPackage = partTypesInOrder
|
|
.filter((partType) => !this.partTypesInServicePackage.includes(partType))
|
|
?? [];
|
|
const vapsCartItemsNotInPackage = partTypesInOrderButNotPackage.map((partType) => {
|
|
switch (partType) {
|
|
case partTypeStrings.FRONT_WIPER:
|
|
return this.frontWipersCartItem;
|
|
case partTypeStrings.REAR_WIPER:
|
|
return this.rearWipersCartItem;
|
|
case partTypeStrings.RAIN_DEFENSE:
|
|
return this.rainDefenseCartItem;
|
|
default:
|
|
return null;
|
|
}
|
|
});
|
|
const items = vapsCartItemsNotInPackage.filter((item) => item != null);
|
|
if (this.recycleFeeCartItem) {
|
|
items.push(this.recycleFeeCartItem);
|
|
}
|
|
if (this.mobileFeeCartItem) {
|
|
items.push(this.mobileFeeCartItem);
|
|
}
|
|
return items;
|
|
},
|
|
frontWipersCartItem() {
|
|
return this.getCartItemForVapsPart(partTypeStrings.FRONT_WIPER);
|
|
},
|
|
rearWipersCartItem() {
|
|
return this.getCartItemForVapsPart(partTypeStrings.REAR_WIPER);
|
|
},
|
|
rainDefenseCartItem() {
|
|
return this.getCartItemForVapsPart(partTypeStrings.RAIN_DEFENSE);
|
|
},
|
|
packagePrice() {
|
|
return this.servicePackageCartItems?.reduce(
|
|
(accumulator, cartItem) => accumulator + (cartItem?.subTotal ?? 0),
|
|
0
|
|
) ?? 0;
|
|
},
|
|
amountDueLabel() {
|
|
return this.getCmsContent(this.widget.amountDue, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
|
},
|
|
deductibleLabel() {
|
|
return this.getCmsContent(this.widget.deductible, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
|
},
|
|
basePriceLabel() {
|
|
return this.getCmsContent(this.widget.basePrice, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
|
},
|
|
subtotalLabel() {
|
|
return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
|
},
|
|
salesTaxLabel() {
|
|
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
|
},
|
|
servicePackageNames() {
|
|
return this.getCmsContent(
|
|
this.widget.servicePackage,
|
|
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
|
|
);
|
|
},
|
|
servicePackageLabelWidget() {
|
|
if (!this.servicePackageNames) {
|
|
return '';
|
|
}
|
|
const currentPackage = this.servicePackageNames
|
|
?.find((entry) => entry?.Name === this.servicePackageTier);
|
|
|
|
return currentPackage?.SubWidgetName ?? '';
|
|
},
|
|
recycleFeeCartItem() {
|
|
const recycleFeeLineItem = useMainStore().lineItems.supportingItems
|
|
?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE);
|
|
return recycleFeeLineItem
|
|
? this.getCartItem(
|
|
this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
|
[recycleFeeLineItem],
|
|
partTypeStrings.REPLACE_FEE // NOTE this is not a mistake
|
|
)
|
|
: null;
|
|
},
|
|
mobileFeeCartItem() {
|
|
const mobileFeeLineItem = useMainStore().lineItems.mobileFee;
|
|
return mobileFeeLineItem
|
|
? this.getCartItem(
|
|
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
|
[mobileFeeLineItem],
|
|
partTypeStrings.MOBILE_FEE
|
|
)
|
|
: null;
|
|
}
|
|
},
|
|
methods: {
|
|
openModal(modalName) {
|
|
this.$refs[modalName].openModal();
|
|
},
|
|
toggleIsExpanded() {
|
|
this.isExpanded = !this.isExpanded;
|
|
},
|
|
getDisplayed(amount, canBeVerifyingCoverage = true) {
|
|
return canBeVerifyingCoverage
|
|
&& this.isUnverified
|
|
&& !useMainStore().isNoComp
|
|
&& !useMainStore().policy.isITAC
|
|
? VERIFYING_COVERAGE
|
|
: formatAmountInDollars(amount);
|
|
},
|
|
getCartItem(label, lineItems, partType) {
|
|
let cartItem = null;
|
|
|
|
if (lineItems && lineItems.length > 0) {
|
|
cartItem = {
|
|
name: label,
|
|
partType,
|
|
subTotal: getPriceOfLineItems(lineItems),
|
|
salesTax: lineItems?.reduce(
|
|
(accumulator, lineItem) => accumulator + lineItem.salesTax,
|
|
0
|
|
) ?? 0
|
|
};
|
|
}
|
|
|
|
return cartItem;
|
|
},
|
|
getCartItemForVapsPart(partType) {
|
|
const label = this.getCmsContentForVapsType(partType);
|
|
const lineItems = useMainStore().lineItems.vaps
|
|
?.filter((vapsLineItem) => vapsLineItem.partType === partType) ?? [];
|
|
return this.getCartItem(label, lineItems, partType);
|
|
},
|
|
removeVap(partType) {
|
|
const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? [];
|
|
useMainStore().updateVaps(newVaps);
|
|
},
|
|
getCmsContentForVapsType(vapsType) {
|
|
const vapsItemDescriptions = this.getCmsContent(
|
|
this.widget.vapsItemDescriptions,
|
|
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
|
|
);
|
|
|
|
if (!vapsItemDescriptions) {
|
|
return '';
|
|
}
|
|
|
|
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsType);
|
|
return vapsTypeDescription?.Text ?? '';
|
|
},
|
|
isVap(partType) {
|
|
const vapPartTypes = [
|
|
partTypeStrings.FRONT_WIPER,
|
|
partTypeStrings.REAR_WIPER,
|
|
partTypeStrings.RAIN_DEFENSE
|
|
];
|
|
return vapPartTypes.includes(partType);
|
|
},
|
|
isRecycle(partType) {
|
|
return partTypeStrings.REPLACE_FEE === partType;
|
|
},
|
|
formatAmountInDollars
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "@/styles/ux-variables-svg-strings.scss";
|
|
|
|
.force-no-top-margin {
|
|
margin-top: 0 !important;
|
|
}
|
|
|
|
.color-black {
|
|
color: $black;
|
|
}
|
|
|
|
.color-green {
|
|
color: $green;
|
|
}
|
|
|
|
.color-darker-gray {
|
|
color: $darker-gray;
|
|
}
|
|
|
|
.cart-table {
|
|
max-height: 0;
|
|
transition: all 350ms ease-in;
|
|
overflow: hidden;
|
|
visibility: hidden;
|
|
margin-top: 1rem;
|
|
|
|
:deep(a) {
|
|
padding-bottom: 0 !important;
|
|
line-height: 1.625 !important;
|
|
text-underline-offset: auto !important;
|
|
}
|
|
|
|
.non-packaged-cart-item:nth-child(odd){
|
|
background-color: $gray-100;
|
|
}
|
|
|
|
.cart-item {
|
|
padding: 0.25rem 1.5rem;
|
|
}
|
|
|
|
.cart-gray {
|
|
background-color: $gray-100;
|
|
}
|
|
|
|
.cart-footer {
|
|
border-top: 1px solid $green;
|
|
}
|
|
|
|
.subtotal-and-tax {
|
|
background-color: $green-150;
|
|
font-weight: $font-weight-bold;
|
|
color: $black;
|
|
padding: 0.25rem 1.5rem;
|
|
}
|
|
|
|
.bottom-amount-due {
|
|
color: $white;
|
|
background-color: $green;
|
|
padding: 0.25rem 1.5rem;
|
|
}
|
|
}
|
|
|
|
:deep(.modal-content a.external-text) {
|
|
text-underline-offset: 0.25rem;
|
|
line-height: 2;
|
|
padding: 0 0 0.25rem 0;
|
|
font-weight: 500;
|
|
max-width: -webkit-fit-content;
|
|
max-width: -moz-fit-content;
|
|
max-width: fit-content;
|
|
}
|
|
|
|
:deep(#RecycleModal h5) {
|
|
text-align: center;
|
|
}
|
|
|
|
.cart-toggle {
|
|
font-weight: $font-weight-bold;
|
|
|
|
&:after {
|
|
content: "";
|
|
transition: all 0.5s ease;
|
|
background-image: url($svg-payment-method-review-toggle);
|
|
background-repeat: no-repeat;
|
|
background-position: right center;
|
|
width: 1rem;
|
|
height: 0.5625rem;
|
|
display: inline-flex;
|
|
position: relative;
|
|
right: 0.75rem;
|
|
margin: 0.5rem 0 0.5rem 1rem;
|
|
cursor: pointer;
|
|
}
|
|
&.expanded:after {
|
|
transform: rotate(180deg);
|
|
}
|
|
&.expanded + .cart-table {
|
|
max-height: 50rem;
|
|
transition: all 150ms ease-in;
|
|
overflow: hidden;
|
|
visibility: visible;
|
|
}
|
|
a {
|
|
text-decoration: none;
|
|
}
|
|
}
|
|
|
|
:deep(#recycle-fee-label a){
|
|
line-height: initial;
|
|
}
|
|
</style>
|