project-booster-vue 9.36.4 → 9.38.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 (15) hide show
  1. package/package.json +1 -1
  2. package/src/components/media/upload/PbMediaUpload.vue +1 -1
  3. package/src/components/projects/projects/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-projects-/360/237/246/240-showcase-1-snap.png +0 -0
  4. package/src/components/projects/projects/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-projects-/360/237/246/240-showcase-empty-state-1-snap.png +0 -0
  5. package/src/components/projects/projects-list/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-projects-list-/360/237/246/240-showcase-1-snap.png +0 -0
  6. package/src/components/projects/projects-list/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-projects-pb-projects-list-/360/237/246/240-showcase-empty-state-1-snap.png +0 -0
  7. package/src/components/question/PbQuestion.vue +4 -1
  8. package/src/components/question/city-search/PbCitySearch.vue +1 -1
  9. package/src/components/question/incremental-amount-input/PbIncrementalAmountInput.vue +6 -0
  10. package/src/components/restitution/PbRestitutionList.stories.mdx +147 -0
  11. package/src/components/restitution/PbRestitutionList.vue +354 -0
  12. package/src/components/restitution/PbRestitutionListBlock.vue +171 -0
  13. package/src/components/restitution/PbRestitutionListLine.vue +80 -0
  14. package/src/components/scenario/PbScenario-Estimator-Attic-Insulation.stories.mdx +245 -0
  15. package/src/components/scenario/scenarii/estimator-attic-insulation.json +2180 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-booster-vue",
3
- "version": "9.36.4",
3
+ "version": "9.38.0",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -470,7 +470,7 @@ export default defineComponent({
470
470
  return await mediaValidator({
471
471
  minHeight: 16,
472
472
  minWidth: 16,
473
- maxFileSize: 20000000,
473
+ maxFileSize: 20_000_000, // max document file size is: 20MB
474
474
  accept: this.acceptedUploadedTypes,
475
475
  })(file);
476
476
  },
@@ -410,12 +410,15 @@ export const doEval = (
410
410
  );
411
411
  };
412
412
 
413
+ //TODO Think to move this kind of function in something like "helpers" or "tools"
414
+ // because this is use in multiple components (as PbIncrementalAmountInput.vue),
415
+ // and it shouldn't be in a component.
413
416
  export const decorate = (
414
417
  answers: Map<string, ScenarioStepAnswer[]>,
415
418
  runtimeOptions = {},
416
419
  valueToDecorate: string,
417
420
  defaultValue = '',
418
- ) => {
421
+ ): string => {
419
422
  let decoratedValue = valueToDecorate;
420
423
  if (valueToDecorate) {
421
424
  const stringToEval = `\`${valueToDecorate}\``;
@@ -9,7 +9,7 @@
9
9
  'pb-city-search__back-button--hidden':
10
10
  !showBackButton && !decorate(answers, runtimeOptions, payload.viewModel.forceBackButton),
11
11
  }"
12
- @click.once="$router.go(-1)"
12
+ @click.once="$emit('go-back')"
13
13
  />
14
14
  </m-flex>
15
15
  <div class="pb-city-search__title">
@@ -155,7 +155,13 @@ export default defineComponent({
155
155
  const computedPayload = computed(() => {
156
156
  let tempPayload = cloneDeep(DEFAULT_PAYLOAD);
157
157
  tempPayload = merge(tempPayload, props.payload);
158
+
159
+ // This will doEval and decorate dynamically variables from JSON templates
160
+ // For instance:
161
+ // "${getAnswerValue('DEVELOPER', 'code') == 'YES' ? 'I'm developer' : 'I'm not developer'}"
158
162
  tempPayload.viewModel.label = decorate(props.answers!, props.runtimeOptions, tempPayload.viewModel.label);
163
+ tempPayload.viewModel.subtitle = decorate(props.answers!, props.runtimeOptions, tempPayload.viewModel.subtitle);
164
+
159
165
  return tempPayload as IncrementalAmountPayload;
160
166
  });
161
167
  const pbIncrementalAmountInput = ref<HTMLElement>();
@@ -0,0 +1,147 @@
1
+ import PbRestitutionList from './PbRestitutionList.vue';
2
+ import { Meta, Canvas, Story, ArgsTable, Source } from '@storybook/addon-docs';
3
+ import { useStore } from 'vuex';
4
+ import { nestedAppDecorator } from '../../../.storybook/nested-app-decorator';
5
+ import store from './../../stores/store';
6
+ import { rest } from 'msw';
7
+ import { successResolver, internalServerErrorResolver } from '../../services/api/mocks/commonMock';
8
+ import { getProjectsResolver } from '../../services/api/mocks/projectsMock';
9
+ import DEFAULT_PAYLOAD from './restitution-default.json';
10
+ import NO_PRICE_BAR from './restitution-no-pricebar.json';
11
+ import SAVE_ITEM from './restitution-with-save-item.json';
12
+ import EXIT_OPTIONS from './restitution-with-exit-options.json';
13
+ import EXIT_OPTIONS_WITH_CONDITIONNAL from './restitution-with-conditionnal-exit-options.json';
14
+ import CALL_TO_ACTION_WITH_MODAL from './restitution-call-to-actions-with-modal.json';
15
+
16
+ <Meta
17
+ title="Project Booster/Components/Restitution/PbRestitutionList 🦠"
18
+ component={PbRestitutionList}
19
+ decorators={[nestedAppDecorator(store, [])]}
20
+ parameters={{
21
+ layout: 'fullscreen',
22
+ }}
23
+ />
24
+
25
+ export const summary = {
26
+ subprojectId: 'ae62d167-e02e-4744-81b1-102cc5816be2',
27
+ subprojectTemplateId: '22248ac3-9391-47f4-8a54-6fe280c2fa5b',
28
+ businessUnit: 'LMFR',
29
+ summaryDate: '2020-05-13T09:49:45.886602047Z',
30
+ subprojectTemplateLabel: '<strong>Pompe à chaleur air/air</strong>',
31
+ cost: {
32
+ min: 3139.99,
33
+ max: 3939.99,
34
+ currency: 'EURO',
35
+ },
36
+ components: [
37
+ {
38
+ componentId: 'LAND',
39
+ title: '<strong>Pompe à chaleur installée</strong> (hors aides)',
40
+ cost: {
41
+ min: 3139.99,
42
+ max: 3939.99,
43
+ currency: 'EURO',
44
+ },
45
+ lines: [
46
+ {
47
+ text: '<strong>Pompe à chaleur installée</strong> (hors aides)',
48
+ details: 'Terrain constructible, délimité et viabilisé. La nature du sous-sol est garantie.',
49
+ cost: {
50
+ min: 6499,
51
+ max: 7299,
52
+ currency: 'EURO',
53
+ },
54
+ },
55
+ {
56
+ text: '<strong>Pompe à chaleur installée</strong> (hors aides)',
57
+ cost: {
58
+ min: 6499,
59
+ max: 7299,
60
+ currency: 'EURO',
61
+ },
62
+ },
63
+ {
64
+ text: '<strong>Aide estimées</strong> (MaPrimeRénov, Prime Energie)',
65
+ type: 'REDUCTION',
66
+ cost: {
67
+ reduce: -3600,
68
+ currency: 'EURO',
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ subprojectAttributes: {
75
+ finish: 'PARTIALLY_BY_MYSELF',
76
+ houseType: 'CONTEMPORARY',
77
+ inseeCode: '59350',
78
+ land: 'NOT_YET',
79
+ landSurfaceInSquareMeters: 123,
80
+ landType: 'IN_A_LOT',
81
+ livingAreaInSquareMeters: 465,
82
+ },
83
+ suggestedTypologies: [
84
+ {
85
+ domesticSpaceLabel: 'Cuisine',
86
+ domesticSpaceHref: '/domestic-spaces/59405',
87
+ projectKindHref: '/project-kinds/34048',
88
+ projectTypeHref: '/project-types/29239',
89
+ },
90
+ {
91
+ domesticSpaceLabel: 'Tout le logement',
92
+ domesticSpaceHref: '/domestic-spaces/59461',
93
+ projectKindHref: '/project-kinds/34048',
94
+ projectTypeHref: '/project-types/61855',
95
+ },
96
+ ],
97
+ legalMentions: [
98
+ "Les prix ci-dessus ne sont proposés qu'à titre indicatif et sont basés sur les prix du marché.",
99
+ "L'outil d'estimation ne vaut pas offre de vente.",
100
+ "La disponibilité des produits au moment de l'achat n'est pas garantie.",
101
+ ],
102
+ };
103
+
104
+ # 🦠 `PbRestitutionList` - Component
105
+
106
+ The `PbRestitutionList` component allows to display the summary of an estimate.
107
+
108
+ export const TemplateSandbox = (args, { argTypes }) => ({
109
+ props: Object.keys(argTypes),
110
+ components: { PbRestitutionList },
111
+ setup() {
112
+ const store = useStore();
113
+ store.dispatch('projects/loadProjects');
114
+ store.dispatch('estimates/setSummary', summary);
115
+ store.dispatch('projects/loadTypologies');
116
+ store.dispatch('estimates/initSessions', {
117
+ formId: 'FORM_ID',
118
+ correlationId: 'CORRELATION_ID',
119
+ estimatorId: 'ESTIMATOR_ID',
120
+ businessUnit: 'BU',
121
+ subprojectFormStructure: {},
122
+ source: 'SOURCE',
123
+ sourceDetail: 'SOURCE_DETAIL',
124
+ });
125
+ return { args };
126
+ },
127
+ template: `<pb-restitution-list :payload="args.payload" :answers="args.answers" :show-save-estimate="args.showSaveEstimate" :runtime-options="args.runtimeOptions" :show-back-button="args.showBackButton" />`,
128
+ });
129
+
130
+ <Canvas>
131
+ <Story
132
+ name="101 Sandbox"
133
+ args={{ payload: DEFAULT_PAYLOAD }}
134
+ parameters={{
135
+ msw: [
136
+ rest.get('/api/inhabitant-projects', getProjectsResolver),
137
+ rest.post('/api/estimates', successResolver),
138
+ rest.post('/api/inhabitant-projects', successResolver),
139
+ ],
140
+ storyshots: { disable: true },
141
+ }}
142
+ inline={false}
143
+ height="1024px"
144
+ >
145
+ {TemplateSandbox.bind({})}
146
+ </Story>
147
+ </Canvas>
@@ -0,0 +1,354 @@
1
+ <template>
2
+ <m-flex align-items="center" direction="column">
3
+ <m-flex
4
+ :class="`pb-restitution-list`"
5
+ align-items="center"
6
+ justify-content="center"
7
+ direction="column"
8
+ ref="pbRestitutionList"
9
+ >
10
+ <m-link
11
+ v-if="showBackButton"
12
+ :left-icon="BACK_ICON"
13
+ :label="payload.backLabel || 'Modifier l\'estimation'"
14
+ class="pb-restitution-list__back-button"
15
+ @click.once="$emit('go-back')"
16
+ />
17
+ </m-flex>
18
+ <div class="pb-restitution-list__title">Votre estimation par travaux</div>
19
+
20
+ <div class="pb-restitution-list__line">
21
+ <pb-restitution-list-block
22
+ @button="handleShowDialog"
23
+ v-for="component in summary.components"
24
+ :key="component.componentId"
25
+ :payload="component"
26
+ ></pb-restitution-list-block>
27
+ </div>
28
+
29
+ <m-flex class="pb-restitution-list__footer" direction="row" align-items="center" justify-content="center">
30
+ <div class="pb-restitution-list__footer__call">
31
+ <m-button label="Être rappelé par un expert"></m-button>
32
+ </div>
33
+ <div class="pb-restitution-list__footer__save">
34
+ <m-button label="Enregistrer mon estimation" theme="bordered"></m-button>
35
+ </div>
36
+ </m-flex>
37
+
38
+ <m-dialog
39
+ class="pb-restitution-list__modal"
40
+ :show-dialog="showDialog"
41
+ width="771px"
42
+ maxHeight="100vh"
43
+ @update:show-dialog="handleShowDialog"
44
+ >
45
+ <template #body>
46
+ <m-flex class="pb-restitution-list__modal__details" direction="column">
47
+ <div class="pb-restitution-list__modal__details__title" v-html="summary.subprojectTemplateLabel"></div>
48
+
49
+ <div class="pb-restitution-list__modal__details__row">
50
+ <m-flex class="pb-restitution-list__modal__details__row__title" justify-content="space-between">
51
+ <div v-html="summary.components[0].title"></div>
52
+ <div>
53
+ <strong>{{ summary.components[0].cost.min }}€ - {{ summary.components[0].cost.max }}€</strong>
54
+ </div>
55
+ </m-flex>
56
+
57
+ <div v-for="line in summary.components[0].lines" :key="line.text">
58
+ <div class="pb-restitution-list__modal__details__row__label" v-if="line.details">
59
+ {{ line.details }}
60
+ </div>
61
+ </div>
62
+
63
+ <div class="pb-restitution-list__modal__details__row__list pb-restitution-list__modal__details__row">
64
+ <ul>
65
+ <div v-for="line in summary.components[0].lines" :key="line.text">
66
+ <li v-if="!line.cost.reduce">
67
+ <div v-html="line.text" v-if="!line.cost.reduce"></div>
68
+ <div v-if="!line.cost.reduce">{{ line.cost.min }}€ - {{ line.cost.max }}€</div>
69
+ </li>
70
+ </div>
71
+ </ul>
72
+ </div>
73
+
74
+ <m-flex
75
+ class="pb-restitution-list__modal__details__row__title help border-bottom"
76
+ justify-content="space-between"
77
+ >
78
+ <span v-for="line in summary.components[0].lines" :key="line" :class="{ hidden: !line.cost.reduce }">
79
+ <div v-if="line.cost.reduce">
80
+ <div v-html="line.text" class="green"></div>
81
+ </div>
82
+ </span>
83
+
84
+ <div v-for="line in summary.components[0].lines" :key="line">
85
+ <div v-if="line.cost.reduce" class="green">
86
+ <strong>{{ line.cost.reduce }}€</strong>
87
+ </div>
88
+ </div>
89
+ </m-flex>
90
+ </div>
91
+
92
+ <m-flex
93
+ class="pb-restitution-list__modal__details__totals"
94
+ justify-content="space-between"
95
+ align-items="center"
96
+ >
97
+ <div><strong>Montant total</strong> (aides déduites)</div>
98
+ <div>
99
+ entre <strong>{{ summary.cost.min }}€</strong> et <strong>{{ summary.cost.max }}€</strong>
100
+ </div>
101
+ </m-flex>
102
+ </m-flex>
103
+ </template>
104
+ <template #footer>
105
+ <m-flex class="pb-restitution-list__modal__footer" align-items="center" justify-content="center">
106
+ <m-button label="Fermer" theme="bordered-neutral" @click.prevent="handleShowDialog"></m-button>
107
+ </m-flex>
108
+ </template>
109
+ </m-dialog>
110
+ </m-flex>
111
+ </template>
112
+
113
+ <script lang="ts">
114
+ import { defineComponent, PropType } from 'vue';
115
+ import { mapGetters } from 'vuex';
116
+ import MButton from '../mozaic/buttons/MButton.vue';
117
+ import MDialog from '../mozaic/dialog/MDialog.vue';
118
+ import MFlex from '../mozaic/flex/MFlex.vue';
119
+ import MLink from '../mozaic/link/MLink.vue';
120
+
121
+ import { RestitutionPayload } from '@/types/pb/Restitution';
122
+ import { ScenarioStepAnswer } from '@/types/pb/Scenario';
123
+ import PbRestitutionListBlock from './PbRestitutionListBlock.vue';
124
+
125
+ export default defineComponent({
126
+ components: {
127
+ MButton,
128
+ MDialog,
129
+ MFlex,
130
+ MLink,
131
+ PbRestitutionListBlock,
132
+ },
133
+ data() {
134
+ return {
135
+ showDialog: false,
136
+ dialogContent: null,
137
+ };
138
+ },
139
+ // eslint-disable-next-line vue/order-in-components
140
+ props: {
141
+ /**
142
+ * The component view model and business data as an object. The provided prop
143
+ * is merged with the default payload value so only overriden values will change
144
+ * from the default ones.
145
+ */
146
+ payload: {
147
+ type: Object as PropType<RestitutionPayload>,
148
+ default: () => ({}),
149
+ required: true,
150
+ },
151
+ /**
152
+ * The previous answers to inject
153
+ */
154
+ answers: {
155
+ type: Object as PropType<Map<string, ScenarioStepAnswer[]>>,
156
+ default: () => null,
157
+ },
158
+ /**
159
+ * The options provided at runtime to customize component behaviour
160
+ */
161
+ runtimeOptions: {
162
+ type: Object,
163
+ default: () => ({}),
164
+ },
165
+ /**
166
+ * Flag indidicating whether the back button should be displayed
167
+ */
168
+ showBackButton: {
169
+ type: Boolean,
170
+ default: true,
171
+ },
172
+ /**
173
+ * Flag to show/hide the save estimate button
174
+ */
175
+ showSaveEstimate: {
176
+ type: Boolean,
177
+ default: false,
178
+ },
179
+ },
180
+ computed: {
181
+ ...mapGetters('estimates', { summary: 'getSummary' }),
182
+ },
183
+ methods: {
184
+ handleShowDialog(content: any) {
185
+ this.dialogContent = content;
186
+ this.showDialog = !this.showDialog;
187
+ },
188
+ },
189
+ });
190
+ </script>
191
+
192
+ <style lang="scss" scoped>
193
+ @import 'pb-variables';
194
+
195
+ .hidden {
196
+ display: none;
197
+ }
198
+
199
+ .pb-restitution-list {
200
+ @include set-font-face('regular');
201
+
202
+ align-items: center;
203
+ display: flex;
204
+ flex-direction: column;
205
+ flex-grow: 1;
206
+ margin: 0 auto;
207
+ padding-bottom: $mu250;
208
+ position: relative;
209
+ width: calc(100% - #{$mu050} - #{$mu050});
210
+
211
+ &__back-button {
212
+ align-self: flex-start;
213
+ opacity: 1;
214
+ padding: $mu100 0 $mu100 $mu025;
215
+ transition: opacity 0.15s 0.5s;
216
+ z-index: 2;
217
+
218
+ &--hidden {
219
+ opacity: 0;
220
+ pointer-events: none;
221
+ }
222
+ }
223
+
224
+ &__title {
225
+ @include set-font-face('semi-bold');
226
+ @include set-font-scale('07', 's');
227
+ left: 0;
228
+ padding: $mu100 0 $mu100 $mu025;
229
+ position: absolute;
230
+ right: 0;
231
+ text-align: center;
232
+ top: 0;
233
+ }
234
+
235
+ &__footer {
236
+ background: #ffffff;
237
+ bottom: 0;
238
+ left: 0;
239
+ padding: $mu100 0 $mu100 $mu025;
240
+ position: fixed;
241
+ right: 0;
242
+ @include set-box-shadow('l');
243
+
244
+ &__save,
245
+ &__call {
246
+ margin: 0 $mu050;
247
+ }
248
+ }
249
+
250
+ &__line {
251
+ margin-top: $mu250;
252
+ }
253
+
254
+ &__modal {
255
+ &__details {
256
+ padding: $mu250 $mu250 0 $mu250;
257
+
258
+ .green {
259
+ color: $color-secondary-green-600;
260
+ }
261
+
262
+ &__title {
263
+ @include set-font-face('semi-bold');
264
+ @include set-font-scale('07', 's');
265
+ margin-bottom: $mu150;
266
+ }
267
+
268
+ &__row {
269
+ border-bottom: 1px solid $color-grey-600;
270
+ margin-bottom: $mu150;
271
+ width: 100%;
272
+
273
+ &__title {
274
+ &.help {
275
+ margin-bottom: $mu150;
276
+ }
277
+ }
278
+
279
+ &__label {
280
+ background: $color-primary-01-100;
281
+ border-radius: 4px;
282
+ color: $color-grey-600;
283
+ margin-top: 10px;
284
+ padding: $mu075;
285
+ position: relative;
286
+ @include set-font-scale('05', 's');
287
+
288
+ &::before {
289
+ border-color: transparent transparent $color-primary-01-100 transparent;
290
+ border-style: solid;
291
+ border-width: 0 10px 16px 10px;
292
+ content: '';
293
+ height: 0;
294
+ left: 10px;
295
+ position: absolute;
296
+ top: -8px;
297
+ width: 0;
298
+ }
299
+ }
300
+
301
+ &__list {
302
+ color: $color-grey-600;
303
+ list-style: disc;
304
+
305
+ ul {
306
+ padding: 0 0 0 $mu100;
307
+ }
308
+
309
+ li {
310
+ display: flex;
311
+ justify-content: space-between;
312
+ margin-bottom: $mu075;
313
+
314
+ &::before {
315
+ content: '•';
316
+ margin-right: 5px;
317
+ }
318
+
319
+ div {
320
+ &:first-child {
321
+ @include set-font-face('regular');
322
+ @include set-font-scale('05', 's');
323
+ width: 60%;
324
+ }
325
+
326
+ &:last-child {
327
+ @include set-font-face('semi-bold');
328
+ text-align: right;
329
+ width: 40%;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ &__totals {
337
+ width: 100%;
338
+
339
+ div {
340
+ &:last-child {
341
+ strong {
342
+ @include set-font-scale('07', 's');
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ &__footer {
350
+ padding: $mu150 0;
351
+ }
352
+ }
353
+ }
354
+ </style>