@worksafevictoria/wcl7.5 1.18.1 → 1.19.0-beta.6

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.
@@ -9,6 +9,7 @@ import Breadcrumb from './../../../SubComponents/Breadcrumb/index.vue'
9
9
  import RichText from './../../RichText/index.vue'
10
10
  import SelectableCards from './../../SelectableCards/index.vue'
11
11
  import PlannerNameModal from './../Planners/PlannerNameModal.vue'
12
+ import HowTo from './HowTo.vue'
12
13
 
13
14
  const props = defineProps({
14
15
  injuryPage: {
@@ -43,15 +44,13 @@ const props = defineProps({
43
44
  type: Array,
44
45
  default: () => [],
45
46
  },
46
- showModalOnButtonRole: {
47
- type: Boolean,
48
- default: false,
49
- },
50
47
  })
51
48
 
52
49
  const screen = ref(1)
53
50
  const selectedCard = ref(null)
54
51
  const showModal = ref(false)
52
+ const step = ref(1)
53
+ const plannerId = ref(null)
55
54
 
56
55
  // local copy of breadcrumb items allowed to mutate
57
56
  const breadcrumbItemsLocal = ref([...props.breadcrumbItems])
@@ -96,6 +95,16 @@ function onCrumbClick({ index }) {
96
95
  function nextScreenAgent(card) {
97
96
  showModal.value = true
98
97
  }
98
+
99
+ /* -----------------------
100
+ Modal submit
101
+ ------------------------ */
102
+ function onSubmit(planner) {
103
+ if (!planner) return
104
+
105
+ plannerId.value = planner.id
106
+ step.value = 2
107
+ }
99
108
  </script>
100
109
 
101
110
  <template>
@@ -123,7 +132,7 @@ function nextScreenAgent(card) {
123
132
  :titles="titles.physical"
124
133
  :cards="cards"
125
134
  :button-role="'modal-quest'"
126
- :showModalOnButtonRole="showModalOnButtonRole"
135
+ :showModalOnButtonRole="true"
127
136
  @nextScreen="nextScreen"
128
137
  />
129
138
  </div>
@@ -172,22 +181,66 @@ function nextScreenAgent(card) {
172
181
  </div>
173
182
 
174
183
  <div v-if="injuryPage === 'selectAgent'">
175
- <card-container
176
- ref="selectAgentContainer"
177
- :columns="4"
178
- :title="titles.cardContactInformaton?.heading"
179
- :cards="contactCards"
180
- :button-role="'link'"
181
- @nextScreen="nextScreenAgent"
182
- />
184
+ <div v-if="step === 1">
185
+ <card-container
186
+ ref="selectAgentContainer"
187
+ :columns="4"
188
+ :title="titles.cardContactInformaton?.heading"
189
+ :cards="contactCards"
190
+ :button-role="'link'"
191
+ @nextScreen="nextScreenAgent"
192
+ />
193
+ </div>
194
+ <div v-if="step === 2">
195
+ <HowTo
196
+ mainTitle="How to use the Return to Work planner"
197
+ subTitle="Let's get started"
198
+ mainText="Read the information on a page, complete the actions and scroll to the bottom to tick off the task."
199
+ buttonText="Next"
200
+ skipLinkText=""
201
+ @next="step = 3"
202
+ />
203
+ </div>
204
+ <div v-if="step === 3">
205
+ <HowTo
206
+ mainTitle="How to use the Return to Work planner"
207
+ subTitle="Contact your employee"
208
+ mainText="Start at Task 1. Read the information and contact your employee. If you have already done this you then go onto the next screen."
209
+ buttonText="Step 1. Contact your employee"
210
+ skipLinkText="I've already contacted my employee"
211
+ :mock="2"
212
+ @next="emit('startPlanner', plannerId)"
213
+ @skip="emit('skipContact', plannerId)"
214
+ />
215
+ </div>
183
216
  </div>
184
217
 
185
- <PlannerNameModal class="injuries-app__new-planner" v-model="showModal" />
218
+ <PlannerNameModal
219
+ class="injuries-app__new-planner"
220
+ v-model="showModal"
221
+ @onsubmit="onSubmit"
222
+ />
186
223
  </div>
187
224
  </template>
188
225
 
189
226
  <style lang="scss" scoped>
227
+ $fuchsia: #ea338f;
228
+
190
229
  .injuries-app {
230
+ :deep(.control_selectcard .card-grid-item__card) {
231
+ border: 4px solid #d8d5d1;
232
+ }
233
+ :deep(.control_selectcard .card-grid-item__card:hover) {
234
+ background-color: white;
235
+ border: 4px solid #5664ac;
236
+ opacity: unset;
237
+ border-radius: 10px;
238
+ }
239
+ :deep(.control_selectcard .card-grid-item__card:focus) {
240
+ background-color: #e9ebf4;
241
+ border: 4px solid #5664ac;
242
+ outline: solid 3px $fuchsia !important;
243
+ }
191
244
  .wcl-hero-header {
192
245
  border: none;
193
246
  }
@@ -1,108 +1,80 @@
1
- <template>
2
- <Container>
3
- <Row>
4
- <Column col="12">
5
- <h2 class="fw-bold my-4">{{ props.activePlanner?.name }}'s planner</h2>
6
- </Column>
7
- </Row>
8
- <Row>
9
- <Column col="12" md="8">
10
- <div class="card shadow-sm border-3 rounded-3 p-4">
11
- <BCardText v-html="activeContent.html"></BCardText>
12
- <CtaButton
13
- :rtl="
14
- isTaskCompleted(activeTask.id, activeContent.id) ? true : false
15
- "
16
- :workwell="
17
- isTaskCompleted(activeTask.id, activeContent.id) ? true : false
18
- "
19
- @click="toggleTask(activeTask.id, activeContent.id)"
20
- class="mt-1"
21
- >
22
- {{
23
- isTaskCompleted(activeTask.id, activeContent.id)
24
- ? 'Completed'
25
- : 'Mark Done'
26
- }}
27
- </CtaButton>
28
- </div>
29
- <div
30
- role="button"
31
- class="card shadow-sm border-3 rounded-3 p-4 mt-4 text-end"
32
- :class="getNextTask() ? '' : 'disabled'"
33
- @click="setActiveContent(getNextTask().task, getNextTask().content)"
34
- >
35
- <h4 class="fw-bold mt-0 mb-3">Go to next task</h4>
36
- <p class="text-muted mb-0">
37
- <span class="text-decoration-underline">
38
- {{
39
- getNextTask() ? getNextTask().content.btnText : 'No more tasks'
40
- }}
41
- </span>
42
- </p>
43
- </div>
44
- </Column>
45
- <Column col="12" md="4">
46
- <div class="card shadow-sm border-3 rounded-3 p-4">
47
- <h4 class="fw-bold mb-3">All tasks</h4>
48
- <div v-for="task in props.tasks" :key="task.id" class="mb-3">
49
- <p class="fw-bold">{{ task.title }}</p>
50
- <div v-for="content in task.content" :key="content.id">
51
- <button
52
- class="d-flex justify-content-between align-items-center w-100 border-0 rounded-2 p-2 mb-2 text-start"
53
- :class="
54
- isTaskCompleted(task.id, content.id)
55
- ? activeContent.id === content.id
56
- ? 'bg-success-subtle text-dark fw-bold active-done'
57
- : 'bg-success-subtle text-dark fw-bold'
58
- : activeContent.id === content.id
59
- ? 'active'
60
- : 'bg-light text-muted'
61
- "
62
- @click="setActiveContent(task, content)"
63
- >
64
- <span class="text-decoration-underline">
65
- {{ content.btnText }}
66
- </span>
67
- <span
68
- v-if="isTaskCompleted(task.id, content.id)"
69
- class="text-success"
70
- >
71
- <TickCircleIcon
72
- class="ms-2"
73
- style="width: 2em; height: 2em"
74
- />
75
- </span>
76
- <span v-else class="text-muted">
77
- <TickCircleIcon
78
- class="ms-2"
79
- style="width: 2em; height: 2em"
80
- />
81
- </span>
82
- </button>
83
- </div>
84
- </div>
85
- </div>
86
- </Column>
87
- </Row>
88
- </Container>
89
- </template>
90
-
91
1
  <script setup>
92
- import { ref } from 'vue'
93
- // import { CtaButton, Row, Column, Container }
2
+ import { computed, ref, onMounted, onBeforeUnmount, watch } from 'vue'
3
+ import { BCard, BCardText, BButton } from 'bootstrap-vue-next'
4
+ import Column from './../../../Containers/Column/index.vue'
5
+ import Container from './../../../Containers/Container/index.vue'
6
+ import Row from './../../../Containers/Row/index.vue'
94
7
  import TickCircleIcon from './../icons/TickCircleIcon.vue'
8
+ import ChevRightIcon from './../icons/ChevRightIcon.vue'
95
9
 
96
10
  const props = defineProps({
97
11
  activePlanner: { type: Object, required: true },
98
12
  tasks: { type: Array, required: true },
99
13
  })
100
14
 
101
- const emit = defineEmits(['updatePlanner'])
102
- const tasks = ref([])
103
- const activeTask = ref(props.tasks[0] || { id: '', title: '', content: [] })
104
- const activeContent = ref(
105
- props.tasks[0]?.content[0] || { btnText: '', html: '' },
15
+ const emit = defineEmits(['updatePlanner', 'taskResource', 'allCompleted'])
16
+
17
+ const showTasksModal = ref(false)
18
+ const isMobile = ref(false)
19
+
20
+ let mq = null
21
+ function handleMq(e) {
22
+ isMobile.value = e.matches
23
+ if (!isMobile.value) showTasksModal.value = false
24
+ }
25
+
26
+ onMounted(() => {
27
+ mq = window.matchMedia('(max-width: 767.98px)')
28
+ isMobile.value = mq.matches
29
+ if (mq.addEventListener) {
30
+ mq.addEventListener('change', handleMq)
31
+ } else {
32
+ mq.addListener(handleMq)
33
+ }
34
+ })
35
+
36
+ onBeforeUnmount(() => {
37
+ if (!mq) return
38
+ if (mq.removeEventListener) {
39
+ mq.removeEventListener('change', handleMq)
40
+ } else {
41
+ mq.removeListener(handleMq)
42
+ }
43
+ })
44
+
45
+ function findFirstUncompleted() {
46
+ const completed = props.activePlanner?.tasksCompleted || []
47
+ for (const task of props.tasks || []) {
48
+ for (const content of task.content || []) {
49
+ const key = `${task.id}_${content.id}`
50
+ if (!completed.includes(key)) {
51
+ return { task, content }
52
+ }
53
+ }
54
+ }
55
+ const firstTask = props.tasks?.[0] || { id: '', title: '', content: [] }
56
+ const firstContent = firstTask.content?.[0] || {
57
+ id: '',
58
+ btnText: '',
59
+ html: '',
60
+ }
61
+ return { task: firstTask, content: firstContent }
62
+ }
63
+
64
+ const initial = findFirstUncompleted()
65
+ const activeTask = ref(initial.task)
66
+ const activeContent = ref(initial.content)
67
+
68
+ watch(
69
+ activeContent,
70
+ (content) => {
71
+ if (content?.resources) {
72
+ emit('taskResource', content.resources)
73
+ } else {
74
+ emit('taskResource', null)
75
+ }
76
+ },
77
+ { immediate: true },
106
78
  )
107
79
 
108
80
  const toggleTask = (taskId, contentId) => {
@@ -112,11 +84,8 @@ const toggleTask = (taskId, contentId) => {
112
84
  tasksCompleted: [...props.activePlanner.tasksCompleted],
113
85
  }
114
86
  const index = updatedPlanner.tasksCompleted.indexOf(key)
115
- if (index === -1) {
116
- updatedPlanner.tasksCompleted.push(key)
117
- } else {
118
- updatedPlanner.tasksCompleted.splice(index, 1)
119
- }
87
+ if (index === -1) updatedPlanner.tasksCompleted.push(key)
88
+ else updatedPlanner.tasksCompleted.splice(index, 1)
120
89
  emit('updatePlanner', updatedPlanner)
121
90
  }
122
91
 
@@ -126,12 +95,20 @@ const isTaskCompleted = (taskId, contentId) => {
126
95
  }
127
96
 
128
97
  const setActiveContent = (task, content) => {
129
- console.log('🚀 ~ setActiveContent ~ task:', task)
130
98
  activeTask.value = task
131
99
  activeContent.value = content
132
- scrollTo(0, 0)
100
+ window.scrollTo({ top: 0, behavior: 'smooth' })
133
101
  }
134
102
 
103
+ const setActiveContentFromModal = (task, content) => {
104
+ setActiveContent(task, content)
105
+ if (isMobile.value) showTasksModal.value = false
106
+ }
107
+
108
+ const totalTaskCount = computed(() => {
109
+ return props.tasks.reduce((sum, task) => sum + (task.content?.length || 0), 0)
110
+ })
111
+
135
112
  const getNextTask = () => {
136
113
  for (const task of props.tasks) {
137
114
  for (const content of task.content) {
@@ -149,12 +126,244 @@ const getNextTask = () => {
149
126
  }
150
127
  }
151
128
  }
129
+ return null
130
+ }
131
+
132
+ const handleNextTask = () => {
133
+ const next = getNextTask()
134
+
135
+ // ✅ No more tasks → emit completion
136
+ if (!next) {
137
+ emit('allCompleted')
138
+ return
139
+ }
140
+
141
+ // otherwise go to next task
142
+ setActiveContent(next.task, next.content)
152
143
  }
153
144
  </script>
154
145
 
146
+ <template>
147
+ <Container>
148
+ <Row>
149
+ <Column cols="12">
150
+ <h2 class="fw-bold my-4">{{ props.activePlanner?.name }}'s planner</h2>
151
+ </Column>
152
+ </Row>
153
+
154
+ <Row>
155
+ <Column cols="12" md="7">
156
+ <BCard class="shadow-sm border-3 rounded-3 p-1">
157
+ <Row class="mb-3 d-md-none">
158
+ <Column cols="7">
159
+ <h6 class="fw-bold mt-0 mb-0">
160
+ <span
161
+ v-if="isTaskCompleted(activeTask.id, activeContent.id)"
162
+ class="text-success"
163
+ >
164
+ <TickCircleIcon style="width: 1em; height: 1em" />
165
+ </span>
166
+ <span v-else class="text-muted">
167
+ <TickCircleIcon
168
+ class="opacity-25"
169
+ style="width: 1em; height: 1em"
170
+ />
171
+ </span>
172
+ {{ activeTask.title }}
173
+ </h6>
174
+ </Column>
175
+ <Column cols="5" class="text-end">
176
+ <BButton
177
+ class="rounded-2"
178
+ @click="showTasksModal = true"
179
+ variant="outline-secondary"
180
+ >
181
+ View All tasks
182
+ <ChevRightIcon class="ms-2" style="width: 1em; height: 1em" />
183
+ </BButton>
184
+ </Column>
185
+ </Row>
186
+
187
+ <h4 class="fw-bold mt-0 mb-3 d-none d-md-block">
188
+ <span
189
+ v-if="isTaskCompleted(activeTask.id, activeContent.id)"
190
+ class="text-success"
191
+ >
192
+ <TickCircleIcon style="width: 1em; height: 1em" />
193
+ </span>
194
+ <span v-else class="text-muted">
195
+ <TickCircleIcon
196
+ class="opacity-25"
197
+ style="width: 1em; height: 1em"
198
+ />
199
+ </span>
200
+ {{ activeTask.title }}
201
+ </h4>
202
+
203
+ <BCardText v-html="activeContent.html"> </BCardText>
204
+ <BButton
205
+ class="task-btn w-100 d-flex justify-content-between align-items-center"
206
+ :class="{
207
+ completed: isTaskCompleted(activeTask.id, activeContent.id),
208
+ }"
209
+ @click="toggleTask(activeTask.id, activeContent.id)"
210
+ >
211
+ <span>
212
+ {{
213
+ isTaskCompleted(activeTask.id, activeContent.id)
214
+ ? "You've completed this task"
215
+ : 'Mark this task as completed'
216
+ }}
217
+ </span>
218
+
219
+ <span
220
+ v-if="isTaskCompleted(activeTask.id, activeContent.id)"
221
+ class="completed"
222
+ >
223
+ <TickCircleIcon class="ms-2" style="width: 2em; height: 2em" />
224
+ </span>
225
+ <span v-else class="text-muted">
226
+ <TickCircleIcon
227
+ class="ms-2 opacity-25"
228
+ style="width: 2em; height: 2em"
229
+ />
230
+ </span>
231
+ </BButton>
232
+ </BCard>
233
+
234
+ <BButton
235
+ v-show="getNextTask()"
236
+ class="next-task card w-100 d-flex shadow-sm border-3 rounded-3 p-4 mt-4 text-end"
237
+ :class="{
238
+ completed: isTaskCompleted(activeTask.id, activeContent.id),
239
+ }"
240
+ @click="handleNextTask"
241
+ >
242
+ <h4 class="fw-bold mt-0 mb-3">
243
+ Go to next task
244
+ <ChevRightIcon style="width: 1em; height: 1em" />
245
+ </h4>
246
+ <p class="mb-0">
247
+ <span class="text-decoration-underline">
248
+ <strong>
249
+ {{
250
+ getNextTask()
251
+ ? getNextTask().content.btnText
252
+ : 'No more tasks'
253
+ }}
254
+ </strong>
255
+ </span>
256
+ </p>
257
+ </BButton>
258
+ </Column>
259
+
260
+ <Column cols="12" md="5" class="d-none d-md-block">
261
+ <BCard class="shadow-sm border-3 rounded-3 p-1">
262
+ <h4 class="fw-bold mb-3">All tasks</h4>
263
+
264
+ <div v-for="task in props.tasks" :key="task.id" class="mb-3">
265
+ <h5 class="fw-bold">{{ task.title }}</h5>
266
+ <div v-for="content in task.content" :key="content.id">
267
+ <BButton
268
+ class="task-list-btn d-flex justify-content-between align-items-center w-100 border-0 rounded-2 p-2 mb-2 text-start text-dark fw-bold"
269
+ :class="
270
+ isTaskCompleted(task.id, content.id)
271
+ ? activeContent.id === content.id
272
+ ? 'bg-success-subtle active-done'
273
+ : 'bg-success-subtle fw-bold'
274
+ : activeContent.id === content.id
275
+ ? 'active'
276
+ : 'bg-light'
277
+ "
278
+ @click="setActiveContent(task, content)"
279
+ >
280
+ <span class="text-decoration-underline">
281
+ {{ content.btnText }}
282
+ </span>
283
+ <span
284
+ v-if="isTaskCompleted(task.id, content.id)"
285
+ class="text-success"
286
+ >
287
+ <TickCircleIcon
288
+ class="ms-2"
289
+ style="width: 1.5em; height: 1.5em"
290
+ />
291
+ </span>
292
+ <span v-else class="text-muted">
293
+ <TickCircleIcon
294
+ class="ms-2 opacity-25"
295
+ style="width: 1.5em; height: 1.5em"
296
+ />
297
+ </span>
298
+ </BButton>
299
+ </div>
300
+ </div>
301
+ </BCard>
302
+ </Column>
303
+ </Row>
304
+
305
+ <Row>
306
+ <Column>
307
+ <BModal
308
+ v-if="showTasksModal"
309
+ v-model="showTasksModal"
310
+ title="All tasks"
311
+ size="xl"
312
+ no-footer
313
+ >
314
+ <div>
315
+ <div v-for="task in props.tasks" :key="'m-' + task.id" class="mb-3">
316
+ <h5 class="fw-bold">{{ task.title }}</h5>
317
+ <div
318
+ v-for="content in task.content"
319
+ :key="'mcontent-' + content.id"
320
+ >
321
+ <BButton
322
+ class="task-list-btn d-flex justify-content-between align-items-center w-100 border-0 rounded-2 p-2 mb-2 text-start text-dark fw-bold"
323
+ :class="
324
+ isTaskCompleted(task.id, content.id)
325
+ ? activeContent.id === content.id
326
+ ? 'bg-success-subtle active-done'
327
+ : 'bg-success-subtle fw-bold'
328
+ : activeContent.id === content.id
329
+ ? 'active'
330
+ : 'bg-light'
331
+ "
332
+ @click="setActiveContentFromModal(task, content)"
333
+ >
334
+ <span class="text-decoration-underline">
335
+ {{ content.btnText }}
336
+ </span>
337
+ <span
338
+ v-if="isTaskCompleted(task.id, content.id)"
339
+ class="text-success"
340
+ >
341
+ <TickCircleIcon
342
+ class="ms-2"
343
+ style="width: 1.5em; height: 1.5em"
344
+ />
345
+ </span>
346
+ <span v-else class="text-muted">
347
+ <TickCircleIcon
348
+ class="ms-2 opacity-25"
349
+ style="width: 1.5em; height: 1.5em"
350
+ />
351
+ </span>
352
+ </BButton>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </BModal>
357
+ </Column>
358
+ </Row>
359
+ </Container>
360
+ </template>
361
+
155
362
  <style lang="scss" scoped>
156
363
  @import './../../../../includes/scss/all.scss';
157
364
 
365
+ $fuchsia: #ea338f;
366
+
158
367
  .active {
159
368
  color: black !important;
160
369
  background-color: $yellow !important;
@@ -162,9 +371,103 @@ const getNextTask = () => {
162
371
  .active-done {
163
372
  border-left: 10px solid $green !important;
164
373
  }
165
- .disabled {
166
- pointer-events: none;
167
- opacity: 0.5;
168
- cursor: not-allowed;
374
+ // .disabled {
375
+ // pointer-events: none;
376
+ // opacity: 0.5;
377
+ // cursor: not-allowed;
378
+ // }
379
+ :deep(span.blockquote) {
380
+ display: block;
381
+ font-style: italic;
382
+ color: #222;
383
+ background-color: #f6f6f6;
384
+ border-left: 4px solid #ccc;
385
+ padding: 0.75rem 1rem;
386
+ margin: 1rem 0;
387
+ border-radius: 4px;
388
+ font-size: 1rem;
389
+ line-height: 1.5;
390
+ }
391
+
392
+ .next-task {
393
+ background-color: #fff; // default gray background
394
+ color: #000;
395
+ font-weight: 600;
396
+ padding: 1rem;
397
+ font-size: 1rem;
398
+ transition: all 0.2s ease-in-out;
399
+
400
+ /* Active/completed state */
401
+ &.completed {
402
+ background-color: #f6f6f6;
403
+ }
404
+
405
+ /* Hover state */
406
+ &:hover {
407
+ background-color: #fff;
408
+ border-color: #222;
409
+ border-width: 3px;
410
+ }
411
+
412
+ /* Focus state */
413
+ &:focus {
414
+ background-color: #fff;
415
+ border-color: #222;
416
+ border-width: 3px;
417
+ }
418
+ }
419
+
420
+ .task-btn {
421
+ background-color: #f2f1f5; // default gray background
422
+ color: #000;
423
+ font-weight: 600;
424
+ border: 2px solid transparent;
425
+ border-radius: 0.5rem;
426
+ padding: 1rem;
427
+ font-size: 1rem;
428
+ transition: all 0.2s ease-in-out;
429
+
430
+ /* Hover state */
431
+ &:hover {
432
+ background-color: #fff;
433
+ border-color: #222;
434
+ border-width: 2px;
435
+ }
436
+
437
+ /* Focus state */
438
+ &:focus {
439
+ background-color: #fff;
440
+ border-color: #222;
441
+ border-width: 3px;
442
+ outline: solid 3px $fuchsia !important;
443
+ outline-offset: 3px;
444
+ border-radius: 0.5rem !important;
445
+ box-shadow: none !important;
446
+ }
447
+
448
+ /* Active/completed state */
449
+ &.completed {
450
+ background-color: #4b8750;
451
+ color: #fff;
452
+ border-color: #4b8750;
453
+ border-width: 3px;
454
+ outline: solid 3px transparent !important;
455
+ outline-offset: 3px;
456
+ }
457
+ }
458
+
459
+ .task-list-btn {
460
+ transition:
461
+ background-color 0.2s ease-in-out,
462
+ color 0.2s ease-in-out;
463
+ &:hover {
464
+ background: #5562a9 !important;
465
+ color: #fff !important;
466
+ border-color: transparent;
467
+
468
+ svg {
469
+ color: #fff !important;
470
+ }
471
+ }
169
472
  }
170
473
  </style>