574 lines
21 KiB
Vue
574 lines
21 KiB
Vue
<template>
|
|
<div>
|
|
<div
|
|
v-if="showDropdownHeader"
|
|
id="cart-dropdown-head"
|
|
class="row flex align-items-center pt-4 cart-header cart-toggle"
|
|
: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
|
|
v-else
|
|
id="cart-dropdown-head"
|
|
class="expanded cart-header">
|
|
</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 py-2">
|
|
<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(servicePrice) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Packaged Cart Items -->
|
|
<div
|
|
id="cart-service-package"
|
|
class="px-5">
|
|
<div
|
|
id="service-package-name"
|
|
class="py-2 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-2 d-flex justify-content-between align-items-center">
|
|
<span>{{ item?.name ?? '' }}</span>
|
|
<textLink
|
|
v-if="!readOnly"
|
|
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-2 non-packaged-cart-item">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span
|
|
v-if="item.cartItemType === cartItemType.RECYCLE_FEE"
|
|
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>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
|
|
</div>
|
|
<textLink
|
|
v-if="item.cartItemType === cartItemType.VAP
|
|
&& !readOnly"
|
|
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="py-2 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="py-2 col d-flex justify-content-between">
|
|
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
|
|
<span id="sales-tax-value">{{ formatAmountInDollars(salesTax) }}</span>
|
|
</div>
|
|
<div
|
|
v-if="showAsPaid"
|
|
id="cart-amount-paid"
|
|
class="py-2 col d-flex justify-content-between">
|
|
<span id="amount-paid-label">{{ amountPaidLabel }}</span>
|
|
<span if="amount-paid-value">{{ getDisplayed(amountPaid) }}</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 { 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 cartItemType from '@/constants/cart-item-type.js';
|
|
import widgetFields from '@/constants/cms-widget-fields.js';
|
|
import {
|
|
getCartTotal,
|
|
getDeductible,
|
|
getLineItems, getMobileFee,
|
|
getRecycleFee, getSalesTax,
|
|
getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
|
|
isOrderUnverified
|
|
} from '@/helpers/cart-helper';
|
|
import {getPriceOfLineItems, getTaxOfLineItems} from '@/helpers/price-calculator';
|
|
|
|
const VERIFYING_COVERAGE = 'Verifying coverage';
|
|
|
|
export default {
|
|
name: 'cart-dropdown',
|
|
components: {
|
|
contentGroupModal,
|
|
textBlock,
|
|
textLink
|
|
},
|
|
props: {
|
|
showAsPaid: Boolean,
|
|
readOnly: Boolean,
|
|
recyclingModalCmsWidgetName: String,
|
|
showDropdownHeader: Boolean,
|
|
isInitiallyExpanded: Boolean,
|
|
submittedOrder: Object
|
|
},
|
|
data() {
|
|
return {
|
|
isExpanded: this.isInitiallyExpanded,
|
|
availableVaps: this.submittedOrder ? this.submittedOrder.lineItems.vaps : useMainStore().order.availableVaps,
|
|
widget: {
|
|
amountDue: 'AmountDueTextWidget',
|
|
amountPaid: 'AmountPaidTextWidget',
|
|
deductible: 'DeductibleWidget',
|
|
basePrice: 'BasePriceWidget',
|
|
subtotal: 'SubtotalWidget',
|
|
salesTax: 'SalesTaxWidget',
|
|
recycleFee: 'RecycleFeeWidget',
|
|
mobileFee: 'MobileServiceWidget',
|
|
servicePackage: 'ServicePackageTitle',
|
|
vapsItemDescriptions: 'VapsItemDescriptions'
|
|
},
|
|
cartItemType
|
|
};
|
|
},
|
|
computed: {
|
|
cartOrder() {
|
|
return this.submittedOrder ?? useMainStore().order;
|
|
},
|
|
deductible() {
|
|
return getDeductible(this.cartOrder);
|
|
},
|
|
showDeductibleCartItem() {
|
|
return this.isUnverified || (!this.isNoComp && !this.isITAC);
|
|
},
|
|
lineItems() {
|
|
return getLineItems(this.cartOrder);
|
|
},
|
|
recycleFeeLineItem() {
|
|
return getRecycleFee(this.cartOrder);
|
|
},
|
|
servicePrice() {
|
|
return getPriceOfLineItems(getServiceLineItems(this.cartOrder), false) ?? 0;
|
|
},
|
|
isUnverified() {
|
|
return isOrderUnverified(this.cartOrder);
|
|
},
|
|
isITAC() {
|
|
return isOrderITAC(this.cartOrder);
|
|
},
|
|
isNoComp() {
|
|
return isOrderNoComp(this.cartOrder);
|
|
},
|
|
// TODO fix rounding
|
|
subTotal() {
|
|
return getSubtotal(this.cartOrder);
|
|
},
|
|
salesTax() {
|
|
return getSalesTax(this.cartOrder);
|
|
},
|
|
total() {
|
|
return getCartTotal(this.cartOrder);
|
|
},
|
|
amountDue() {
|
|
return this.showAsPaid ? 0 : this.total;
|
|
},
|
|
amountPaid() {
|
|
return !this.showAsPaid ? 0 : this.total;
|
|
},
|
|
availableLineItems() {
|
|
const { supportingItems, glassParts, otherParts, feeItems } = this.lineItems;
|
|
const result = [
|
|
...(supportingItems ?? []),
|
|
...(glassParts ?? []),
|
|
...(otherParts ?? []),
|
|
...(this.availableVaps ?? []),
|
|
...(feeItems ?? [])
|
|
];
|
|
return result;
|
|
},
|
|
vehicleDamage() {
|
|
return this.submittedOrder ? this.submittedOrder.damage : useMainStore().damage;
|
|
},
|
|
servicePackageTier() {
|
|
const { glassToReplace, isRepair } = this.vehicleDamage;
|
|
const { vaps } = this.lineItems;
|
|
return getHighestFullySatisfiedTier(
|
|
glassToReplace ?? [],
|
|
this.availableLineItems,
|
|
isRepair,
|
|
vaps ?? []
|
|
);
|
|
},
|
|
partTypesInServicePackage() {
|
|
const { glassToReplace, isRepair } = this.vehicleDamage;
|
|
return getPackageContents(
|
|
glassToReplace ?? [],
|
|
this.availableLineItems,
|
|
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 vapPartTypesInOrder = Array.from(new Set(this.lineItems.vaps?.map((vap) => vap.partType) ?? []));
|
|
const vapPartTypesInOrderButNotPackage = vapPartTypesInOrder
|
|
.filter((partType) => !this.partTypesInServicePackage.includes(partType))
|
|
?? [];
|
|
const vapsCartItemsNotInPackage = vapPartTypesInOrderButNotPackage.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);
|
|
},
|
|
allCartItems() {
|
|
return [
|
|
...this.servicePackageCartItems,
|
|
...this.nonServicePackageCartItems
|
|
];
|
|
},
|
|
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);
|
|
},
|
|
amountPaidLabel() {
|
|
return this.getCmsContent(this.widget.amountPaid, 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() {
|
|
return this.recycleFeeLineItem
|
|
? this.getCartItem(
|
|
this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
|
[this.recycleFeeLineItem],
|
|
cartItemType.RECYCLE_FEE,
|
|
partTypeStrings.REPLACE_FEE
|
|
)
|
|
: null;
|
|
},
|
|
mobileFeeCartItem() {
|
|
const mobileFeeLineItem = getMobileFee(this.cartOrder);
|
|
return mobileFeeLineItem
|
|
? this.getCartItem(
|
|
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
|
[mobileFeeLineItem],
|
|
cartItemType.MOBILE_FEE,
|
|
partTypeStrings.MOBILE_FEE
|
|
)
|
|
: null;
|
|
}
|
|
},
|
|
methods: {
|
|
openModal(modalName) {
|
|
this.$refs[modalName].openModal();
|
|
},
|
|
toggleIsExpanded() {
|
|
this.isExpanded = !this.isExpanded;
|
|
},
|
|
getDisplayed(amount) {
|
|
return this.isUnverified
|
|
? VERIFYING_COVERAGE
|
|
: formatAmountInDollars(amount);
|
|
},
|
|
getCartItem(label, lineItems, cartItemTypeString, partType) {
|
|
let cartItem = null;
|
|
|
|
if (lineItems && lineItems.length > 0) {
|
|
cartItem = {
|
|
name: label,
|
|
cartItemType: cartItemTypeString,
|
|
partType,
|
|
subTotal: getPriceOfLineItems(lineItems, false),
|
|
salesTax: getTaxOfLineItems(lineItems)
|
|
};
|
|
}
|
|
|
|
return cartItem;
|
|
},
|
|
getCartItemForVapsPart(partType) {
|
|
const label = this.getCmsContentForVapsType(partType);
|
|
const lineItems = this.lineItems.vaps
|
|
?.filter((vapsLineItem) => vapsLineItem.partType === partType) ?? [];
|
|
return this.getCartItem(label, lineItems, cartItemType.VAP, partType);
|
|
},
|
|
removeVap(partType) {
|
|
const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? [];
|
|
useMainStore().updateVaps(newVaps);
|
|
},
|
|
getCmsContentForVapsType(vapsPartType) {
|
|
const vapsItemDescriptions = this.getCmsContent(
|
|
this.widget.vapsItemDescriptions,
|
|
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
|
|
);
|
|
|
|
if (!vapsItemDescriptions) {
|
|
return '';
|
|
}
|
|
|
|
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType);
|
|
return vapsTypeDescription?.Text ?? '';
|
|
},
|
|
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.5rem 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-header {
|
|
&.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);
|
|
}
|
|
a {
|
|
text-decoration: none;
|
|
}
|
|
}
|
|
&.expanded + .cart-table {
|
|
max-height: 50rem;
|
|
transition: all 150ms ease-in;
|
|
overflow: hidden;
|
|
visibility: visible;
|
|
}
|
|
}
|
|
|
|
:deep(#recycle-fee-label a){
|
|
line-height: initial;
|
|
}
|
|
</style>
|