INSR-7557: Add to calender button fixes

This commit is contained in:
Alex Humphries 2026-02-11 15:59:48 -05:00
parent 4dab87bf3f
commit 8bf7d282d4
8 changed files with 142 additions and 309 deletions

View file

@ -7,9 +7,7 @@ module.exports = {
ecmaVersion: 'latest'
},
extends: [
'eslint-config-airbnb-base',
'plugin:vue/vue3-recommended',
'plugin:jsdoc/recommended'
'plugin:vue/vue3-recommended'
],
rules: {
'linebreak-style': 'off',
@ -30,10 +28,6 @@ module.exports = {
singleline: 'never',
multiline: 'never'
}],
'jsdoc/check-tag-names': ['error', {
definedTags: ['store', 'endpoint', 'category', 'subcategory', 'remarks']
}],
'jsdoc/require-jsdoc': 0,
'vue/html-self-closing': ['error', {
html: {
void: 'any',
@ -43,7 +37,6 @@ module.exports = {
svg: 'always',
math: 'always'
}],
'import/extensions': ['error', 'always', { js: 'ignorePackages' }],
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['item'] }],
'no-restricted-syntax': ['off', 'ForOfStatement'],
'no-return-await': 'off'

View file

@ -321,7 +321,6 @@ export default {
},
basePriceLabel() {
const content = this.getCmsContent(this.widget.basePrice, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
console.log('Base Price Label before processing if statements:', content);
return processIfStatements(
content,
'custom',
@ -436,7 +435,6 @@ export default {
return vapsTypeDescription?.Text ?? '';
},
getCustomValueFromString(str) {
console.log(this.cartOrder)
switch (str) {
case 'isRepair':
return this.cartOrder.damage.isRepair;

View file

@ -1,7 +1,7 @@
<template>
<div class="calendar-section">
<div class="add-to-calendar">
<button @click="openCalendarModal">
<button @click="toggleCalendarOptionsOpen">
<span class="add-to-calendar-span">
<img
src="@/assets/img/icons/add-to-calendar.svg"
@ -10,14 +10,12 @@
</span>
</button>
</div>
<calendarModalQuestion
ref="calendarModalQuestion"
v-model="selectedCalendarOption"
customComponentId="calendarModalQuestion"
cmsWidgetName="CalendarModalQuestion"
<calendarOptionSelection
ref="calendarOptions"
:open="calendarOptionsOpen"
:calendarOptionsData="calendarValues"
@calendarModalClosed="calendarModalClosed">
</calendarModalQuestion>
@calendarOptionSelected="calendarOptionSelected">
</calendarOptionSelection>
</div>
</template>
<script>
@ -32,12 +30,12 @@ import { AppointmentTypeStrings, RouteCodeFlags } from '@/constants/schedule-con
import { getCalendarFile, download } from '@/helpers/add-to-calendar-helper';
import serviceType from '@/constants/service-type';
import applicationConfig from '@/constants/application-config.js';
import calendarModalQuestion from '@/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue';
import calendarOptionSelection from '@/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue';
export default {
name: 'add-to-calendar',
components: {
calendarModalQuestion
calendarOptionSelection
},
props: {
mobileWidgetName: String,
@ -59,7 +57,7 @@ export default {
},
data() {
return {
selectedCalendarOption: null
calendarOptionsOpen: false
};
},
computed: {
@ -84,7 +82,7 @@ export default {
return this.getCmsContent(this.mobileWidgetName, 'BodyText');
},
AddToCalendar_InShop_Subject() {
return this.getCmsContent('AddToCalendar_InShop', 'HeaderText');
return this.getCmsContent(this.inShopWidgetName, 'HeaderText');
},
AddToCalendar_InShop_Body() {
return this.getCmsContent(this.inShopWidgetName, 'BodyText');
@ -192,14 +190,6 @@ export default {
};
}
},
watch: {
selectedCalendarOption(newValue) {
if (newValue) {
this.setCalendarAppointment(newValue);
}
}
},
methods: {
setCalendarAppointment(calendarOption) {
if (
@ -211,12 +201,22 @@ export default {
window.open(calendarOption.url);
}
},
calendarModalClosed() {
// Clear the selectedOption if close the modal
this.selectedCalendarOption = null;
toggleCalendarOptionsOpen() {
if (this.calendarOptionsOpen) {
this.closeCalendarOptions();
} else {
this.openCalendarOptions();
}
},
openCalendarModal() {
this.$refs.calendarModalQuestion.openModal();
openCalendarOptions() {
console.log('Opening calendar options');
this.calendarOptionsOpen = true;
window.addEventListener('click', this.clickListener);
},
calendarOptionSelected(calendarOptionIndex) {
console.log('Selected calendar option:', calendarOptionIndex);
this.setCalendarAppointment(this.calendarValues[calendarOptionIndex]);
this.calendarOptionsOpen = false;
},
getCalendarData(type) {
let URL = '';
@ -253,29 +253,37 @@ export default {
};
const calBody = getCalendarFile(calFile);
download('SafeliteAppointment.ics', calBody);
},
closeCalendarOptions() {
console.log('Closing calendar options');
this.calendarOptionsOpen = false;
window.removeEventListener('click', this.clickListener);
},
clickListener(event) {
const calendarSection = document.querySelector('.calendar-section');
if (calendarSection && !calendarSection.contains(event.target)) {
this.closeCalendarOptions();
}
}
}
};
</script>
<style scoped>
<style scoped lang="scss">
div.calendar-section {
position: relative;
top: 17px;
display: inline;
cursor: pointer;
}
.add-to-calendar-text {
margin: 0 0 0 0.4375rem;
color: #1574a1;
color: $heritage-blue-secondary;
vertical-align: middle;
}
.add-to-calendar button {
border: 0;
background-color: #fff;
}
.calendar-section {
display: table;
position: relative;
margin: 0 auto;
}
.calendar-section .add-to-calendar {
padding-top: 1rem;
padding-bottom: 1rem;
}
.calendar-section .add-to-calendar .add-to-calendar-span {
cursor: pointer;
}

View file

@ -1,81 +0,0 @@
<template>
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<span
class="m-0 position-relative"
:class="textPosition">
<img :src="buttonLabel" />
<span class="calendar-text">{{ value }}</span>
</span>
</div>
</baseInputButton>
</template>
<script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button.vue';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin.js';
export default {
name: 'calendar-modal-list-button',
components: {
baseInputButton
},
mixins: [inputButtonWrapperMixin]
};
</script>
<style lang="scss" scoped>
.list-button {
outline: none;
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
&:focus-visible + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content {
color: $black;
font-weight: 500;
background: $blue-100;
box-shadow: 0 0 0 1px $blue;
}
&:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content p,
&:checked + .list-button-content span {
font-weight: 500;
}
&:checked + .list-button-content span:nth-child(2) {
font-weight: 400;
color: $gray-600;
}
}
}
.list-button-content {
color: $gray-600;
position: relative;
background: $white;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 0.063rem solid $gray-500;
width: 100%;
outline: none;
span.calendar-text {
margin-left: 0.313rem;
padding: 0.125rem 0.5rem;
}
}
.position-relative {
position: relative;
}
</style>

View file

@ -1,180 +0,0 @@
<template>
<modal
:ref="modalName"
:fontSize="true"
:headerText="modalHeaderText"
:footerButtonText="footerCloseButtonText"
:onModalClosedCallback="onModalClosed"
class="calendar-modal"
@isModalOpened="setModalStatus"
@footerButtonEvent="setSelectedCalendarOption">
<template v-if="isModalOpened">
<buttonQuestion
ref="buttonQuestion"
v-model="selectedCalendarOption"
buttonTypeString="calendarModalListButton"
:buttonTypeObject="calendarModalListButton"
:answers="getCalendarOptionData"
groupName="chooseCalendarType"
class="mt-1"
textPosition="text-center"
isRequired
validationRules="calendar-option-required" />
</template>
</modal>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import { defineRule, useField } from 'vee-validate';
import errorMessages from '@/constants/error-messages.js';
import { required } from '@/helpers/validation-rules';
import { deepClone } from '@/helpers/object-helper';
// eslint-disable-next-line import/no-extraneous-dependencies
import { v4 as uuidv4 } from 'uuid';
import calendarModalListButton from './calendar-modal-list-button/calendar-modal-list-button.vue';
// Validation for the modal button
defineRule('calendar-option-required', required(errorMessages.OPTION_REQUIRED));
export default {
name: 'calendar-modal-question',
components: {
modal,
buttonQuestion
},
props: {
calendarOptionsData: Object,
modelValue: {
type: Object,
default: () => ({
url: null,
name: null,
icon: null
})
}
},
emits: ['calendar-modal-closed', 'update:modelValue'],
setup(props) {
const uuid = uuidv4();
const componentId = !props.customComponentId
? `component-${uuid}`
: props.customComponentId;
const { modelValue } = deepClone(props);
const initialValue = modelValue;
const fieldOptions = {
value: modelValue,
initialValue
};
const { errorMessage, handleChange, meta, validate, errors } = useField(
componentId,
props.validationRules,
fieldOptions
);
return {
componentId,
errorMessage,
handleChange,
validate,
meta,
errors
};
},
data() {
return {
isModalOpened: false,
selectedValue: null,
calendarModalListButton
};
},
computed: {
modalName() {
return 'calendarOptions';
},
modalHeaderText() {
return 'Add to Calendar';
},
footerCloseButtonText() {
return 'Add to Calendar';
},
modal() {
return this.$refs[this.modalName];
},
selectedCalendarOption: {
get() {
return this.modelValue?.name;
},
set(newValue) {
// Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedCalendarObject(newValue);
}
},
getCalendarOptionData() {
return this.calendarOptionsData.map((item) => ({
value: item.name,
buttonLabel: item.icon
}));
},
watch: {
modelValue: {
handler(newValue) {
this.handleChange(newValue);
},
deep: true
}
}
},
methods: {
openModal() {
this.modal.openModal();
},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
closeModal() {
this.modal.closeModal();
},
onModalClosed() {
this.$emit('calendar-modal-closed');
},
async setSelectedCalendarOption() {
this.$emit('update:modelValue', this.selectedValue);
this.closeModal();
},
getSelectedCalendarObject(calendarOptionId) {
// eslint-disable-next-line no-shadow
const calendarOption = this.calendarOptionsData?.find((calendarOption) => calendarOption.name === calendarOptionId);
if (calendarOption) {
return {
url: calendarOption.url,
name: calendarOption.name,
icon: calendarOption.icon
};
}
return {
url: null,
name: null,
icon: null
};
}
}
};
</script>
<style lang="scss" scoped>
.calendar-modal.modal.modal-component {
> .modal-dialog > .modal-content {
margin-top: 1.5rem;
}
.modal-header {
padding-bottom: 0;
margin-bottom: 0 !important;
}
overflow: hidden;
}
</style>

View file

@ -0,0 +1,41 @@
<template>
<a
:aria-label="buttonLabel"
class="d-flex flex-row option-button">
<img :src="buttonLabel" />
<span class="calendar-text">{{ value }}</span>
</a>
</template>
<script>
export default {
name: 'calendar-option-button',
props: {
buttonLabel: String,
value: String
}
};
</script>
<style lang="scss" scoped>
.option-button {
text-decoration: none;
color: #4d4e53;
font-weight: 400;
padding: 0.5rem;
border-radius: 4px;
padding: .75rem 3.125rem .0625rem 1rem;
height: 2.75rem;
&:hover {
background-color: $background-color-selected;
}
img {
width: 1.25rem;
height: 1.25rem;
}
.calendar-text {
margin-left: 1rem;
}
}
</style>

View file

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import calendarModalQuestion from '@/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue';
import calendarOptionSelection from '@/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue';
describe('calendar-modal-question.vue', () => {
describe('calendar-option-selection.vue', () => {
test('When selected calendar option is changed, a correctly selectedCalendarOption should be emitted', async () => {
// Arrange
const url = 'iCal url';

View file

@ -0,0 +1,54 @@
<template>
<div
v-if="open"
class="calendar-selection">
<template
v-for="(option, index) in getCalendarOptionData"
:key="option.value">
<calendarOptionButton
:buttonLabel="option.buttonLabel"
:value="option.value"
@click="setSelectedCalendarOption(index)" />
</template>
</div>
</template>
<script>
import calendarOptionButton from './calendar-option-button/calendar-option-button.vue';
// Validation for the modal button
export default {
name: 'calendar-modal-question',
components: {
calendarOptionButton
},
props: {
calendarOptionsData: Object,
open: Boolean
},
emits: ['calendar-option-selected'],
computed: {
getCalendarOptionData() {
return this.calendarOptionsData.map((item) => ({
value: item.name,
buttonLabel: item.icon
}));
}
},
methods: {
async setSelectedCalendarOption(index) {
this.$emit('calendar-option-selected', index);
}
}
};
</script>
<style lang="scss" scoped>
.calendar-selection {
position: absolute;
z-index: 1;
display: table;
border: solid 1px #d4d6d8;
border-radius: .25rem;
background-color: #ffffff;
box-shadow: 0 0 10px 0 rgba(88, 188, 233, 0.2);
}
</style>