project-booster-vue 9.4.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/src/components/appointment/PbAppointmentForm.stories.mdx +94 -0
  3. package/src/components/appointment/PbAppointmentForm.vue +57 -0
  4. package/src/components/appointment/PbAppointmentStartSection.vue +52 -1
  5. package/src/components/appointment/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-appointment-qualification-pb-appointment-form-/360/237/246/240-101-sandbox-1-snap.png +0 -0
  6. package/src/components/pedagogy/PbPedagogy.stories.mdx +2 -2
  7. package/src/components/pedagogy/PbPedagogy.vue +41 -9
  8. package/src/components/pedagogy/default-payload.json +6 -1
  9. package/src/components/projects/project-hub/PbProjectHub-Features-Appointment.stories.mdx +21 -4
  10. package/src/components/projects/project-hub/PbProjectHub.vue +24 -4
  11. package/src/components/projects/project-hub/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-project-hub-/360/237/246/240-features-appointment-showcase-with-store-appointment-1-snap.png +0 -0
  12. package/src/components/projects/project-hub/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-project-hub-/360/237/246/240-features-appointment-showcase-with-visio-appointment-1-snap.png +0 -0
  13. package/src/components/question/PbQuestion.vue +11 -13
  14. package/src/components/question/list-select/PbListSelect.vue +1 -1
  15. package/src/components/scenario/{PbScenario-Features-Test.stories.mdx → PbScenario-Features-Appointment.stories.mdx} +59 -3
  16. package/src/components/scenario/PbScenario.vue +15 -2
  17. package/src/components/scenario/scenarii/appointment-qualification-kitchen.json +242 -69
  18. package/src/services/api/appointmentQualificationsApi.ts +35 -1
  19. package/src/services/api/mocks/inhabitantsMock.ts +1 -0
  20. package/src/services/api/mocks/jsons/projects.json +8 -1
  21. package/src/services/api/mocks/projectsMock.ts +14 -3
  22. package/src/stores/modules/appointmentQualificationStore.ts +58 -0
  23. package/src/components/pedagogy/PbPedagogyStore.ts +0 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-booster-vue",
3
- "version": "9.4.0",
3
+ "version": "9.7.0",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -0,0 +1,94 @@
1
+ import { Story, Meta, ArgsTable, Canvas } from '@storybook/addon-docs';
2
+ import { nestedAppDecorator } from '../../../.storybook/nested-app-decorator';
3
+ import PbAppointmentForm from './PbAppointmentForm';
4
+ import appointmentQualificationStore from '../../stores/modules/appointmentQualificationStore';
5
+ import inhabitantsStore from '../../stores/modules/inhabitantsStore';
6
+ import { rest } from 'msw';
7
+ import { useStore } from 'vuex';
8
+ import { v4 as uuidv4 } from 'uuid';
9
+ import { getInhabitantByIdResolver } from '../../services/api/mocks/inhabitantsMock';
10
+ import { declarationsWithAppointmentResolver } from '../../services/api/mocks/appointmentQualificationMock';
11
+ import SCENARIO from '../scenario/scenarii/appointment-qualification-kitchen.json';
12
+
13
+ <Meta
14
+ title="Project Booster/Components/Appointment qualification/ PbAppointmentForm 🦠"
15
+ component={PbAppointmentForm}
16
+ argTypes={{}}
17
+ parameters={{
18
+ layout: 'fullscreen',
19
+ }}
20
+ decorators={[
21
+ nestedAppDecorator(
22
+ {
23
+ modules: {
24
+ appointmentQualification: appointmentQualificationStore,
25
+ inhabitants: inhabitantsStore,
26
+ },
27
+ mutations: {
28
+ eventBusSendEvent(state, { code, payload }) {
29
+ console.log('event bus data : ', code, payload);
30
+ },
31
+ },
32
+ actions: {
33
+ sendEventToBus({ commit }, action) {
34
+ commit('eventBusSendEvent', action);
35
+ },
36
+ },
37
+ },
38
+ [],
39
+ ),
40
+ ]}
41
+ />
42
+
43
+ # 🦠 `PbAppointmentForm` - Component
44
+
45
+ The `PbAppointmentForm` component that include in a iframe the clicRDV form.
46
+
47
+ export const TemplateSandbox = (args, { argTypes }) => ({
48
+ props: Object.keys(argTypes),
49
+ components: { PbAppointmentForm },
50
+ setup() {
51
+ const store = useStore();
52
+ store.dispatch('appointmentQualification/initSessions', {
53
+ formId: 'appointment-qualification-kitchen',
54
+ correlationId: uuidv4(),
55
+ businessUnit: '001',
56
+ source: 'APPOINTMENT_QUALIFICATION',
57
+ sourceDetail: 'KITCHEN',
58
+ calendarId: '375615',
59
+ storeId: '006',
60
+ appointmentQualificationFormStructure: SCENARIO,
61
+ });
62
+ store.commit('appointmentQualification/setCurrentAppointmentQualification', {
63
+ inhabitantProjectId: uuidv4(),
64
+ appointmentQualificationId: uuidv4(),
65
+ });
66
+ store.commit('inhabitants/setCurrentInhabitant', {
67
+ firstName: 'Jean',
68
+ lastName: 'Dupont',
69
+ email: 'jean.dupont@yopmail.com',
70
+ });
71
+ store.commit('appointmentQualification/setClicRDVBaseUrl', 'https://sandbox-user.clicrdv.com');
72
+ return { args };
73
+ },
74
+ template: `<pb-appointment-form />
75
+ />`,
76
+ });
77
+
78
+ <Canvas>
79
+ <Story
80
+ inline={false}
81
+ height="862px"
82
+ name="101 Sandbox"
83
+ parameters={{
84
+ msw: [
85
+ rest.get('/api/inhabitant-api/users/:buId/:inhabitantId', getInhabitantByIdResolver),
86
+ rest.post('/api/declarations-without-appointment', declarationsWithAppointmentResolver),
87
+ ],
88
+ }}
89
+ >
90
+ {TemplateSandbox.bind({})}
91
+ </Story>
92
+ </Canvas>
93
+
94
+ <ArgsTable story="101 Sandbox" />
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <m-flex class="pb-appointment-form__container">
3
+ <iframe v-if="appointmentFormUrl" :src="appointmentFormUrl" style="border: 0; min-height: 110rem; width: 100%">
4
+ <p>
5
+ Your browser doesn't support iframes. To book an appointment, please follow this link :
6
+ <a :href="appointmentFormUrl">{{ appointmentFormUrl }}</a>
7
+ </p>
8
+ </iframe>
9
+ </m-flex>
10
+ </template>
11
+
12
+ <script lang="ts">
13
+ import { computed, defineComponent } from 'vue';
14
+ import { useStore } from 'vuex';
15
+ import MFlex from '../mozaic/flex/MFlex.vue';
16
+
17
+ export default defineComponent({
18
+ name: 'PbAppointmentForm',
19
+ components: { MFlex },
20
+
21
+ setup() {
22
+ const store = useStore();
23
+
24
+ const appointmentFormUrl = computed(() => {
25
+ const sessions = store.getters['appointmentQualification/getSessions'];
26
+ const currentAppointmentQualification =
27
+ store.getters['appointmentQualification/getCurrentAppointmentQualification'];
28
+ const currentUser = store.getters['inhabitants/getCurrentInhabitant'];
29
+ const baseUrl = store.getters['appointmentQualification/getClicRDVBaseUrl'];
30
+
31
+ let url = `${baseUrl}{/leroy-merlin-${sessions.storeId}`;
32
+ url += `?calendar_id=${sessions.calendarId}`;
33
+ url += `&vevent[str5]=${currentAppointmentQualification.inhabitantProjectId}`;
34
+ url += `&fiche[email]=${currentUser.email}`;
35
+ url += `&fiche[lastname]=${currentUser.lastName}`;
36
+ url += `&fiche[firstname]=${currentUser.firstName}`;
37
+
38
+ return url;
39
+ });
40
+
41
+ return {
42
+ appointmentFormUrl,
43
+ };
44
+ },
45
+ });
46
+ </script>
47
+
48
+ <style lang="scss" scoped>
49
+ @import 'pb-variables';
50
+
51
+ .pb-appointment-form {
52
+ &__container {
53
+ margin: 0 auto;
54
+ width: 100%;
55
+ }
56
+ }
57
+ </style>
@@ -1,7 +1,9 @@
1
1
  <template>
2
2
  <m-flex full-width class="pb-appointment-section">
3
3
  <m-flex align-items="center" full-width class="pb-appointment-section__content" direction="column">
4
- <div class="pb-appointment-section__content-title">Gagnez du temps en préparant votre Rendez-vous</div>
4
+ <div class="pb-appointment-section__content-title">
5
+ {{ decorate(payload.viewModel.title, payload.defaultDecoratorValue) }}
6
+ </div>
5
7
  <m-flex
6
8
  justify-content="center"
7
9
  align-items="center"
@@ -50,6 +52,7 @@ import MFlex from '../mozaic/flex/MFlex.vue';
50
52
  import PbItemsList from '../items/PbItemsList.vue';
51
53
  import MButton from '../mozaic/buttons/MButton.vue';
52
54
  import { ScenarioStepAnswer, ScenarioStepPayload } from '@/types/pb/Scenario';
55
+ import objectPath from 'object-path';
53
56
 
54
57
  export default defineComponent({
55
58
  name: 'PbAppointmentStartSection',
@@ -62,6 +65,20 @@ export default defineComponent({
62
65
  type: Object as PropType<ScenarioStepPayload>,
63
66
  default: () => null,
64
67
  },
68
+ /**
69
+ * The options provided at runtime to customize component behaviour
70
+ */
71
+ runtimeOptions: {
72
+ type: Object,
73
+ default: () => ({}),
74
+ },
75
+ /**
76
+ * The previous answers to inject
77
+ */
78
+ answers: {
79
+ type: Object as PropType<Map<string, ScenarioStepAnswer[]>>,
80
+ default: () => new Map<string, ScenarioStepAnswer[]>(),
81
+ },
65
82
  },
66
83
  methods: {
67
84
  selectAnswer(answer: ScenarioStepAnswer) {
@@ -70,6 +87,40 @@ export default defineComponent({
70
87
  nextStep: undefined,
71
88
  });
72
89
  },
90
+ decorate(valueToDecorate: string, defaultValue = '') {
91
+ if (valueToDecorate) {
92
+ valueToDecorate = `\`${valueToDecorate}\``;
93
+ try {
94
+ return this.doEval(valueToDecorate, defaultValue);
95
+ } catch (e) {
96
+ console.error(e);
97
+ return valueToDecorate;
98
+ }
99
+ }
100
+ return valueToDecorate;
101
+ },
102
+ decorateBoolean(valueToDecorate: string, defaultValue = false) {
103
+ if (valueToDecorate) {
104
+ return this.doEval(valueToDecorate, defaultValue);
105
+ }
106
+ return valueToDecorate;
107
+ },
108
+ doEval(valueToEval: string, defaultValue: string | boolean) {
109
+ return new Function('getAnswerValue', 'answers', 'defaultValue', 'runtimeOptions', `return ${valueToEval}`).call(
110
+ this,
111
+ this.getAnswerValue,
112
+ Object.fromEntries(this.answers),
113
+ defaultValue,
114
+ this.runtimeOptions,
115
+ );
116
+ },
117
+ getAnswerValue(answerCode: string, path: string) {
118
+ if (!this.answers.get(answerCode) || this.answers.get(answerCode)?.length === 0) {
119
+ return null;
120
+ }
121
+
122
+ return objectPath.get(this.answers.get(answerCode)?.at(0) as any, path);
123
+ },
73
124
  },
74
125
  });
75
126
  </script>
@@ -2,7 +2,7 @@ import { Anchor, Story, Preview, Meta, Props, ArgsTable, Source, Canvas } from '
2
2
  import DEFAULT_PAYLOAD from './default-payload';
3
3
  import { nestedAppDecorator } from '../../../.storybook/nested-app-decorator';
4
4
  import PbPedagogy from './PbPedagogy';
5
- import { pbPedagogyStore } from './PbPedagogyStore';
5
+ import store from '../../stores/store';
6
6
 
7
7
  <Meta
8
8
  title="Project Booster/Components/Pedagogy/ PbPedagogy 🦠"
@@ -23,7 +23,7 @@ import { pbPedagogyStore } from './PbPedagogyStore';
23
23
  parameters={{
24
24
  layout: 'fullscreen',
25
25
  }}
26
- decorators={[nestedAppDecorator(pbPedagogyStore, [])]}
26
+ decorators={[nestedAppDecorator(store, [])]}
27
27
  />
28
28
 
29
29
  # 🦠 `PbPedagogy` - Component
@@ -4,10 +4,16 @@
4
4
  <m-icon class="pb-pedagogy__content-icon" :icon="payload.viewModel.icon" color="primary-01-500" size="xl" />
5
5
  <div class="pb-pedagogy__content-title">{{ payload.viewModel.title }}</div>
6
6
 
7
- <div class="pb-pedagogy__content-description">
8
- <m-icon :icon="IMAGE_SRC" />
9
- <div class="pb-pedagogy__content-description-text" v-html="payload.viewModel.description" />
10
- </div>
7
+ <m-flex class="pb-pedagogy__content-description" v-if="payload.viewModel.description" direction="column">
8
+ <div
9
+ class="pb-pedagogy__content-description-line"
10
+ v-for="(line, index) in payload.viewModel.description"
11
+ :key="index"
12
+ >
13
+ <m-icon :icon="line.icon" v-if="line.icon" />
14
+ <div class="pb-pedagogy__content-description-line-text" v-html="line.label" />
15
+ </div>
16
+ </m-flex>
11
17
  <m-button
12
18
  v-if="payload.viewModel.downloadButton"
13
19
  :theme="payload.viewModel.downloadButton.theme"
@@ -15,12 +21,20 @@
15
21
  class="pb-pedagogy__content-upload-button"
16
22
  @click="handleButtonLinkClicked"
17
23
  />
24
+ <m-button
25
+ v-if="payload.multiSelect"
26
+ theme="primary"
27
+ :label="payload.multiSelect.actions.VALIDATE.label"
28
+ class="pb-pedagogy__content-action-button"
29
+ @click="handleValidateClicked"
30
+ />
18
31
  <m-flex
19
32
  justify-content="center"
20
33
  align-items="center"
21
34
  full-width
22
35
  class="pb-pedagogy__content-cards"
23
36
  direction="column"
37
+ v-if="payload.cards"
24
38
  >
25
39
  <div class="pb-pedagogy__content-cards-title">Pour aller plus loin</div>
26
40
  <pb-cards-list :cards="payload.cards" @card-click="handleCardClick" />
@@ -35,6 +49,7 @@ import MButton from '../mozaic/buttons/MButton.vue';
35
49
  import MFlex from '../mozaic/flex/MFlex.vue';
36
50
  import MIcon from '../mozaic/icon/MIcon.vue';
37
51
  import PbCardsList from '../cards/PbCardsList.vue';
52
+ import { mapGetters } from 'vuex';
38
53
 
39
54
  interface PedagogyCardViewmodel {
40
55
  url: string;
@@ -51,8 +66,6 @@ interface PedagogyCard {
51
66
  viewModel: PedagogyCardViewmodel;
52
67
  }
53
68
 
54
- const IMAGE_SRC = 'https://storage.googleapis.com/project-booster-media/vad/lm-advisor.png';
55
-
56
69
  export default defineComponent({
57
70
  name: 'PbPedagogy',
58
71
  components: { PbCardsList, MButton, MIcon, MFlex },
@@ -66,9 +79,13 @@ export default defineComponent({
66
79
  },
67
80
  },
68
81
  data: () => ({
69
- IMAGE_SRC,
70
82
  value: [],
71
83
  }),
84
+ computed: {
85
+ ...mapGetters('appointmentQualification', {
86
+ currentAppointmentQualification: 'getCurrentAppointmentQualification',
87
+ }),
88
+ },
72
89
  watch: {
73
90
  value(newValue) {
74
91
  const selectedCard = this.payload.cards.find((card: PedagogyCard) => card.value === newValue);
@@ -107,7 +124,18 @@ export default defineComponent({
107
124
  },
108
125
  },
109
126
  });
110
- window.open(card.viewModel.url, '_blank');
127
+ const targetUrl = card.viewModel.url.replace(
128
+ '@@PB_PROJECT_ID_FILLER@@',
129
+ this.currentAppointmentQualification.inhabitantProjectId,
130
+ );
131
+ window.open(targetUrl, '_blank');
132
+ },
133
+
134
+ async handleValidateClicked() {
135
+ this.$emit('step-completed', {
136
+ answers: [],
137
+ nextStep: this.payload.multiSelect.actions.VALIDATE.nextStep,
138
+ });
111
139
  },
112
140
  },
113
141
  });
@@ -154,7 +182,7 @@ $responsive-breakpoint: 'm';
154
182
  min-height: $mu400;
155
183
  }
156
184
 
157
- &-description {
185
+ &-description-line {
158
186
  align-items: center;
159
187
  display: flex;
160
188
  flex-direction: column;
@@ -185,6 +213,10 @@ $responsive-breakpoint: 'm';
185
213
  margin-top: $mu200;
186
214
  }
187
215
 
216
+ &-action-button {
217
+ margin-top: $mu200;
218
+ }
219
+
188
220
  &-cards {
189
221
  margin-top: $mu200;
190
222
 
@@ -2,7 +2,12 @@
2
2
  "viewModel": {
3
3
  "title": "Un email de rappel pour préparer votre RDV vous sera envoyé",
4
4
  "icon": "https://storage.googleapis.com/project-booster-media/vad/fin-de-parcours/mail_80.svg",
5
- "description": " Les mesures et &nbsp; <span class='pb-pedagogy__content-description-bold'> plans de votre pièce </span> &nbsp; sont importants pour le rendez-vous. Ils\n permettront à votre conseiller de créer un &nbsp; <span class='pb-pedagogy__content-description-bold'> plan 3D adapté </span> &nbsp; à votre cuisine.\n <span class='pb-pedagogy__content-description-bold'> Vous pouvez les ajouter à partir à partir du mail que vous avez reçu. </span>",
5
+ "description": [
6
+ {
7
+ "label": "Les mesures et &nbsp; <span class='pb-pedagogy__content-description-bold'> plans de votre pièce </span> &nbsp; sont importants pour le rendez-vous. Ils\n permettront à votre conseiller de créer un &nbsp; <span class='pb-pedagogy__content-description-bold'> plan 3D adapté </span> &nbsp; à votre cuisine.\n <span class='pb-pedagogy__content-description-bold'> Vous pouvez les ajouter à partir à partir du mail que vous avez reçu. </span>",
8
+ "icon": "https://storage.googleapis.com/project-booster-media/vad/lm-advisor.png"
9
+ }
10
+ ],
6
11
  "downloadButton": {
7
12
  "label": "Comment faire son plan ? (PDF)",
8
13
  "theme": "bordered",
@@ -7,7 +7,8 @@ import { successResolver, internalServerErrorResolver } from '../../../services/
7
7
  import {
8
8
  getProjectByIdResolver,
9
9
  getDoneAppointmentByIdResolver,
10
- getAppointmentByIdResolver,
10
+ getStoreAppointmentByIdResolver,
11
+ getVisioAppointmentByIdResolver,
11
12
  } from '../../../services/api/mocks/projectsMock';
12
13
 
13
14
  <Meta
@@ -54,12 +55,28 @@ The `PbProjectHub` component to access your project.
54
55
 
55
56
  <Canvas>
56
57
  <Story
57
- name="Showcase with appointment"
58
+ name="Showcase with store appointment"
58
59
  parameters={{
59
60
  controls: { disable: true },
60
61
  msw: [
61
62
  rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
62
- rest.get('/api/inhabitant-projects/:projectId/main-appointment', getAppointmentByIdResolver),
63
+ rest.get('/api/inhabitant-projects/:projectId/main-appointment', getStoreAppointmentByIdResolver),
64
+ ],
65
+ }}
66
+ height="100vh"
67
+ >
68
+ {TemplateSandbox.bind({})}
69
+ </Story>
70
+ </Canvas>
71
+
72
+ <Canvas>
73
+ <Story
74
+ name="Showcase with visio appointment"
75
+ parameters={{
76
+ controls: { disable: true },
77
+ msw: [
78
+ rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
79
+ rest.get('/api/inhabitant-projects/:projectId/main-appointment', getVisioAppointmentByIdResolver),
63
80
  ],
64
81
  }}
65
82
  height="100vh"
@@ -77,7 +94,7 @@ The `PbProjectHub` component to access your project.
77
94
  controls: { disable: true },
78
95
  msw: [
79
96
  rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
80
- rest.get('/api/inhabitant-projects/:projectId/main-appointment', getAppointmentByIdResolver),
97
+ rest.get('/api/inhabitant-projects/:projectId/main-appointment', getStoreAppointmentByIdResolver),
81
98
  ],
82
99
  }}
83
100
  args={{
@@ -72,12 +72,20 @@
72
72
  class="pb-project-hub__appointment-notification"
73
73
  type="appointment"
74
74
  :title="appointment.appointmentDate"
75
- :link-label="readOnly ? '' : 'Préparez votre rendez-vous !'"
76
- :link-href="
75
+ :text="
77
76
  readOnly
78
77
  ? ''
79
- : `/projets/preparation-rendez-vous/cuisine.html?appointmentId=${appointment.appointmentId}&groupId=${appointment.groupId}&calendarId=${appointment.calendarId}&leadSourceDetail=Project_Space`
78
+ : appointment.appointmentDate.endsWith('en magasin')
79
+ ? 'N’oubliez pas !'
80
+ : 'Retrouvez le lien d\'accès à la visio-conférence dans le mail qui vous est envoyé. \n' +
81
+ 'N’oubliez pas !'
80
82
  "
83
+ :link-label="
84
+ readOnly
85
+ ? ''
86
+ : 'Ajoutez des plans aux mesures de votre pièce et des photos pour bien préparer le rendez-vous.'
87
+ "
88
+ @link-click="handleAppointmentLinkClick"
81
89
  />
82
90
  </m-flex>
83
91
  </m-flex>
@@ -773,7 +781,7 @@
773
781
  </template>
774
782
 
775
783
  <script lang="ts">
776
- import { defineComponent } from 'vue';
784
+ import { defineComponent, nextTick } from 'vue';
777
785
  import { format } from 'date-fns';
778
786
  import Velocity from 'velocity-animate';
779
787
  import { mapGetters } from 'vuex';
@@ -1370,6 +1378,18 @@ export default defineComponent({
1370
1378
  handleMediaUploaded() {
1371
1379
  this.$store.dispatch('media/refreshMediaList');
1372
1380
  },
1381
+
1382
+ handleAppointmentLinkClick() {
1383
+ this.$store.dispatch('sendEventToBus', {
1384
+ code: 'appointmentLinkClicked',
1385
+ payload: this.project.id,
1386
+ });
1387
+ nextTick(() => {
1388
+ (<any>(
1389
+ window
1390
+ )).location = `/projets/preparation-rendez-vous/cuisine.html?inhabitantProjectId=${this.project.id}&appointmentId=${this.appointment.appointmentId}&groupId=${this.appointment.groupId}&calendarId=${this.appointment.calendarId}&leadSourceDetail=Project_Space`;
1391
+ });
1392
+ },
1373
1393
  },
1374
1394
  });
1375
1395
  </script>
@@ -297,7 +297,7 @@ export default defineComponent({
297
297
  props: {
298
298
  /**
299
299
  * The component view model and business data as an object. The provided prop
300
- * is merged with the default payload value so only overriden values will change
300
+ * is merged with the default payload value so only overridden values will change
301
301
  * from the default ones.
302
302
  */
303
303
  payload: {
@@ -320,7 +320,7 @@ export default defineComponent({
320
320
  default: true,
321
321
  },
322
322
  /**
323
- * Name for the event to send when the step is questio is answered
323
+ * Name for the event to send when the step is question is answered
324
324
  */
325
325
  completedEventName: {
326
326
  type: String,
@@ -432,14 +432,13 @@ export default defineComponent({
432
432
  const questionPossibleAnswer = this.questionPossibleAnswers.get(answerCode);
433
433
  if (
434
434
  questionPossibleAnswer &&
435
- (questionPossibleAnswer?.selected ||
436
- answerValues.findIndex(
437
- (answer: ScenarioStepAnswer | string) =>
438
- (typeof answer === 'string' && answer === answerCode) ||
439
- (typeof answer === 'object' &&
440
- (<ScenarioStepAnswer>answer)?.code === answerCode &&
441
- (<ScenarioStepAnswer>answer)?.selected),
442
- ) >= 0) &&
435
+ answerValues.findIndex(
436
+ (answer: ScenarioStepAnswer | string) =>
437
+ (typeof answer === 'string' && answer === answerCode) ||
438
+ (typeof answer === 'object' &&
439
+ (<ScenarioStepAnswer>answer)?.code === answerCode &&
440
+ (<ScenarioStepAnswer>answer)?.selected),
441
+ ) >= 0 &&
443
442
  areConditionsValid(questionPossibleAnswer.conditions!, this.answers, this.runtimeOptions)
444
443
  ) {
445
444
  this.selectedAnswers.set(answerCode, true);
@@ -499,10 +498,9 @@ export default defineComponent({
499
498
  if (!this.payload.multiSelect) {
500
499
  this.initAnswersSelectedState(this.payload.answers);
501
500
  }
502
- answer.selected = !answer.selected;
503
501
 
504
502
  if (this.payload.multiSelect) {
505
- this.selectedAnswers.set(answer.code, answer.selected);
503
+ this.selectedAnswers.set(answer.code, !this.selectedAnswers.get(answer.code) ?? true);
506
504
 
507
505
  const selected = Object.fromEntries(Object.entries(this.selectedAnswers).filter(([, value]) => value === true));
508
506
  /**
@@ -514,7 +512,7 @@ export default defineComponent({
514
512
  Object.keys(Object.fromEntries(this.selectedAnswers)).forEach((answerCode) => {
515
513
  this.selectedAnswers.set(answerCode, false);
516
514
  });
517
- this.selectedAnswers.set(answer.code, answer.selected);
515
+ this.selectedAnswers.set(answer.code, !this.selectedAnswers.get(answer.code) ?? true);
518
516
 
519
517
  /**
520
518
  * Emitted when step is completed
@@ -44,7 +44,7 @@
44
44
  justify-content="center"
45
45
  >
46
46
  <m-button
47
- v-if="payload.skippable && !hasSelectedAnswers"
47
+ v-if="payload.skippable && (!hasSelectedAnswers || payload.viewModel.alwaysDisplaySkippable)"
48
48
  class="pb-list-select__next-button"
49
49
  :label="payload.skippable.label"
50
50
  :left-icon="payload.skippable.leftIcon"