@windward/core 0.26.0 → 0.28.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 (109) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/components/Content/Blocks/Accordion.vue +9 -15
  3. package/components/Content/Blocks/BlockQuote.vue +29 -4
  4. package/components/Content/Blocks/ClickableIcons.vue +22 -9
  5. package/components/Content/Blocks/Email.vue +11 -4
  6. package/components/Content/Blocks/Feedback/FeedbackAnalytics.vue +179 -0
  7. package/components/Content/Blocks/Feedback.vue +115 -111
  8. package/components/Content/Blocks/FileDownload.vue +2 -2
  9. package/components/Content/Blocks/Image.vue +144 -0
  10. package/components/Content/Blocks/OpenResponse.vue +419 -5
  11. package/components/Content/Blocks/ScenarioChoice.vue +11 -2
  12. package/components/Content/Blocks/Tab.vue +16 -29
  13. package/components/Content/Blocks/UserUpload.vue +66 -38
  14. package/components/Content/Blocks/Video.vue +377 -28
  15. package/components/Settings/AccordionSettings.vue +3 -15
  16. package/components/Settings/BlockQuoteSettings.vue +6 -4
  17. package/components/Settings/ClickableIconsSettings.vue +24 -10
  18. package/components/Settings/EmailSettings.vue +3 -11
  19. package/components/Settings/FileDownloadSettings.vue +8 -2
  20. package/components/Settings/ImageSettings.vue +26 -0
  21. package/components/Settings/OpenResponseCollateSettings.vue +10 -0
  22. package/components/Settings/OpenResponseSettings.vue +67 -7
  23. package/components/Settings/ScenarioChoiceSettings.vue +11 -5
  24. package/components/Settings/TabSettings.vue +3 -18
  25. package/components/Settings/UserUploadSettings.vue +16 -8
  26. package/components/Settings/VideoSettings/SourcePicker.vue +55 -21
  27. package/components/Settings/VideoSettings.vue +18 -2
  28. package/components/utils/ContentViewer.vue +180 -1
  29. package/components/utils/glossary/GlossaryToolTip.vue +4 -23
  30. package/helpers/GlossaryHelper.ts +4 -7
  31. package/i18n/en-US/components/content/blocks/accordion.ts +3 -0
  32. package/i18n/en-US/components/content/blocks/block_quote.ts +3 -1
  33. package/i18n/en-US/components/content/blocks/feedback.ts +2 -0
  34. package/i18n/en-US/components/content/blocks/file_download.ts +2 -1
  35. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  36. package/i18n/en-US/components/content/blocks/open_response.ts +19 -1
  37. package/i18n/en-US/components/content/blocks/open_response_collate.ts +1 -1
  38. package/i18n/en-US/components/content/blocks/scenario_choice.ts +2 -0
  39. package/i18n/en-US/components/content/blocks/user_upload.ts +2 -1
  40. package/i18n/en-US/components/settings/accordion.ts +2 -1
  41. package/i18n/en-US/components/settings/block_quote.ts +1 -1
  42. package/i18n/en-US/components/settings/clickable_icon.ts +5 -0
  43. package/i18n/en-US/components/settings/email.ts +2 -1
  44. package/i18n/en-US/components/settings/file_download.ts +2 -2
  45. package/i18n/en-US/components/settings/image.ts +1 -0
  46. package/i18n/en-US/components/settings/open_response.ts +8 -0
  47. package/i18n/en-US/components/settings/open_response_collate.ts +3 -0
  48. package/i18n/en-US/components/settings/scenario_choice.ts +3 -1
  49. package/i18n/en-US/components/settings/tab.ts +4 -3
  50. package/i18n/en-US/components/settings/user_upload.ts +1 -0
  51. package/i18n/en-US/components/settings/video.ts +3 -1
  52. package/i18n/en-US/shared/content_blocks.ts +1 -1
  53. package/i18n/es-ES/components/content/blocks/accordion.ts +3 -0
  54. package/i18n/es-ES/components/content/blocks/block_quote.ts +3 -1
  55. package/i18n/es-ES/components/content/blocks/feedback.ts +2 -0
  56. package/i18n/es-ES/components/content/blocks/file_download.ts +2 -1
  57. package/i18n/es-ES/components/content/blocks/index.ts +2 -0
  58. package/i18n/es-ES/components/content/blocks/open_response.ts +19 -2
  59. package/i18n/es-ES/components/content/blocks/open_response_collate.ts +1 -1
  60. package/i18n/es-ES/components/content/blocks/scenario_choice.ts +2 -0
  61. package/i18n/es-ES/components/content/blocks/user_upload.ts +2 -1
  62. package/i18n/es-ES/components/settings/accordion.ts +4 -2
  63. package/i18n/es-ES/components/settings/block_quote.ts +1 -1
  64. package/i18n/es-ES/components/settings/clickable_icon.ts +7 -0
  65. package/i18n/es-ES/components/settings/email.ts +2 -1
  66. package/i18n/es-ES/components/settings/image.ts +1 -0
  67. package/i18n/es-ES/components/settings/open_response.ts +8 -0
  68. package/i18n/es-ES/components/settings/open_response_collate.ts +3 -0
  69. package/i18n/es-ES/components/settings/scenario_choice.ts +3 -1
  70. package/i18n/es-ES/components/settings/tab.ts +3 -2
  71. package/i18n/es-ES/components/settings/user_upload.ts +1 -0
  72. package/i18n/es-ES/components/settings/video.ts +3 -1
  73. package/i18n/es-ES/shared/content_blocks.ts +1 -1
  74. package/i18n/sv-SE/components/content/blocks/accordion.ts +3 -0
  75. package/i18n/sv-SE/components/content/blocks/block_quote.ts +3 -1
  76. package/i18n/sv-SE/components/content/blocks/feedback.ts +2 -0
  77. package/i18n/sv-SE/components/content/blocks/file_download.ts +2 -1
  78. package/i18n/sv-SE/components/content/blocks/index.ts +2 -0
  79. package/i18n/sv-SE/components/content/blocks/open_response.ts +19 -2
  80. package/i18n/sv-SE/components/content/blocks/open_response_collate.ts +1 -1
  81. package/i18n/sv-SE/components/content/blocks/scenario_choice.ts +2 -0
  82. package/i18n/sv-SE/components/content/blocks/user_upload.ts +2 -1
  83. package/i18n/sv-SE/components/settings/accordion.ts +2 -1
  84. package/i18n/sv-SE/components/settings/block_quote.ts +1 -1
  85. package/i18n/sv-SE/components/settings/clickable_icon.ts +6 -0
  86. package/i18n/sv-SE/components/settings/email.ts +2 -1
  87. package/i18n/sv-SE/components/settings/image.ts +1 -0
  88. package/i18n/sv-SE/components/settings/open_response.ts +8 -0
  89. package/i18n/sv-SE/components/settings/open_response_collate.ts +3 -0
  90. package/i18n/sv-SE/components/settings/scenario_choice.ts +3 -1
  91. package/i18n/sv-SE/components/settings/tab.ts +5 -3
  92. package/i18n/sv-SE/components/settings/user_upload.ts +1 -0
  93. package/i18n/sv-SE/components/settings/video.ts +3 -1
  94. package/i18n/sv-SE/shared/content_blocks.ts +1 -1
  95. package/models/SurveyResultMetric.ts +8 -0
  96. package/package.json +2 -2
  97. package/plugin.js +8 -0
  98. package/test/Components/Content/Blocks/Feedback/FeedbackTemplates/FeedbackAnalytics.spec.js +23 -0
  99. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.spec.js +1 -1
  100. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.spec.js +1 -1
  101. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.spec.js +1 -1
  102. package/test/Components/Settings/AccordionSettings.spec.js +0 -13
  103. package/test/Components/Settings/ClickableIconsSettings.spec.js +1 -12
  104. package/test/Components/Settings/EmailSettings.spec.js +0 -9
  105. package/test/Components/Settings/TabSettings.spec.js +0 -13
  106. package/test/helpers/GlossaryHelper.spec.js +8 -8
  107. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.vue +1 -1
  108. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.vue +1 -1
  109. /package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.vue +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
1
1
  # Changelog
2
2
 
3
+ ## Release [0.28.0] - 2026-02-18
4
+
5
+ * Merged in feature/LE-2250/open-response-feedback-2 (pull request #485)
6
+ * Merged in feature/LE-2223-add-title-and-instructions-to-im (pull request #483)
7
+ * Merged in feature/LE-2250/open-response-feedback (pull request #482)
8
+ * Merged in feature/LE-2223-add-title-and-instructions-to-im (pull request #481)
9
+ * Merged in feature/LE-2208-translate-course (pull request #478)
10
+ * Merge remote-tracking branch 'origin/release/0.28.0' into feature/LE-2208-translate-course
11
+ * Merge branch 'develop' into feature/LE-2208-translate-course
12
+ * Merged in release/0.27.0 (pull request #462)
13
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #476)
14
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
15
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #474)
16
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #473)
17
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
18
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
19
+ * Merged in LE-1915-block-quote-design-updates (pull request #468)
20
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-1915-block-quote-design-updates
21
+ * Merged in LE-2185-feedback-block-add-container-bac (pull request #469)
22
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-2185-feedback-block-add-container-bac
23
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #472)
24
+ * Merged in feature/LE-1788-transcript-generation (pull request #465)
25
+ * Merged release/0.27.0 into feature/LE-2236-bar-charts-for-non-open-ended-it
26
+ * Merged in LE-2186-file-upload-block-add-container- (pull request #470)
27
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #471)
28
+ * Add localized "item header" message for ClickableIconsSettings and update "initial setup" text for OpenResponse block
29
+ * Merged in LE-1897-glossary-pop-up-displayy (pull request #467)
30
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #464)
31
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #466)
32
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
33
+ * Merge remote-tracking branch 'origin/feature/LE-2155-consistent-ui-for-adding-new-blo' into feature/LE-2155-consistent-ui-for-adding-new-blo
34
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
35
+ * Merged release/0.28.0 into feature/LE-2223-add-title-and-instructions-to-im
36
+ * Merged in feature/LE-2223-add-title-and-instructions-to-im (pull request #477)
37
+ * Merged in feature/LE-2215-clickable-icon-default-settings- (pull request #475)
38
+
39
+
40
+ ## Release [0.27.0] - 2026-01-15
41
+
42
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #476)
43
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
44
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #474)
45
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
46
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #473)
47
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
48
+ * Merged in LE-1915-block-quote-design-updates (pull request #468)
49
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-1915-block-quote-design-updates
50
+ * Merged in LE-2185-feedback-block-add-container-bac (pull request #469)
51
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-2185-feedback-block-add-container-bac
52
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #472)
53
+ * Merged in feature/LE-1788-transcript-generation (pull request #465)
54
+ * Merged release/0.27.0 into feature/LE-2236-bar-charts-for-non-open-ended-it
55
+ * Merged in LE-2186-file-upload-block-add-container- (pull request #470)
56
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #471)
57
+ * Merged in LE-1897-glossary-pop-up-displayy (pull request #467)
58
+ * Add localized "item header" message for ClickableIconsSettings and update "initial setup" text for OpenResponse block
59
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #466)
60
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
61
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #464)
62
+ * Merge remote-tracking branch 'origin/feature/LE-2155-consistent-ui-for-adding-new-blo' into feature/LE-2155-consistent-ui-for-adding-new-blo
63
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
64
+ * add localized messages for OpenResponseCollate and update default settings handling
65
+ * Merged in release/0.26.0 (pull request #455)
66
+ * Merged in hotfix/0.25.1 (pull request #457)
67
+
68
+
3
69
  ## Release [0.26.0] - 2025-12-05
4
70
 
5
71
  * Merged in feature/LE-2154-open-response-download-add-a-tit (pull request #461)
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :class="blockDirectionClasses" :dir="blockTextDirection">
3
3
  <v-container
4
4
  v-if="
5
5
  block.metadata.config.title ||
@@ -25,7 +25,15 @@
25
25
  {{ block.metadata.config.instructions }}
26
26
  </p>
27
27
  </v-container>
28
+ <v-alert v-if="block.metadata.config.items.length === 0" type="warning">
29
+ {{
30
+ $t(
31
+ 'windward.core.components.content.blocks.accordion.no_accordion_items'
32
+ )
33
+ }}
34
+ </v-alert>
28
35
  <v-expansion-panels
36
+ v-else
29
37
  :value="selectedPanels"
30
38
  flat
31
39
  accordion
@@ -174,21 +182,7 @@ export default {
174
182
  this.$set(this.block.metadata.config, 'display_title', true)
175
183
  }
176
184
  if (_.isEmpty(this.block.metadata.config.items)) {
177
- const defaultObject = {
178
- header: '',
179
- expand: false,
180
- content: '',
181
- fileConfig: {
182
- display: {
183
- width: 100,
184
- margin: '',
185
- padding: '',
186
- },
187
- hideBackground: true,
188
- },
189
- }
190
185
  this.block.metadata.config.items = []
191
- this.block.metadata.config.items.push(defaultObject)
192
186
  }
193
187
  this.block.body = this.$t(
194
188
  'windward.core.shared.content_blocks.title.accordion'
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-container class="container-left-border">
2
+ <v-container :class="['container-left-border', blockDirectionClasses]" :dir="blockTextDirection">
3
3
  <v-row
4
4
  v-if="
5
5
  block.metadata.config.title ||
@@ -25,7 +25,16 @@
25
25
  </p>
26
26
  </v-col>
27
27
  </v-row>
28
- <v-row>
28
+ <v-row v-if="!block.metadata.config.block_quote.quote" class="pa-2">
29
+ <v-alert type="warning" width="100%">
30
+ {{
31
+ $t(
32
+ 'windward.core.components.content.blocks.block_quote.no_items'
33
+ )
34
+ }}
35
+ </v-alert>
36
+ </v-row>
37
+ <v-row v-else>
29
38
  <v-col cols="1">
30
39
  <v-icon color="primary">mdi-format-quote-open</v-icon>
31
40
  </v-col>
@@ -67,6 +76,19 @@ export default {
67
76
  return {}
68
77
  },
69
78
  computed: {
79
+ containerClasses() {
80
+ const classes = ['container-left-border']
81
+
82
+ // Add surface class when highlight is applied
83
+ if (
84
+ !_.isEmpty(this.block.metadata.display) &&
85
+ !_.isEmpty(this.block.metadata.display.highlight)
86
+ ) {
87
+ classes.push('surface')
88
+ }
89
+
90
+ return classes.join(' ')
91
+ },
70
92
  citation() {
71
93
  const parts = []
72
94
 
@@ -152,7 +174,9 @@ export default {
152
174
  this.block.metadata.config = {}
153
175
  }
154
176
  if (_.isEmpty(this.block.metadata.config.title)) {
155
- this.block.metadata.config.title = ''
177
+ this.block.metadata.config.title = this.$t(
178
+ 'windward.core.components.content.blocks.block_quote.title'
179
+ )
156
180
  }
157
181
  if (!_.isBoolean(this.block.metadata.config.display_title)) {
158
182
  this.$set(this.block.metadata.config, 'display_title', true)
@@ -183,10 +207,11 @@ export default {
183
207
  <style scoped>
184
208
  .container-left-border {
185
209
  border-style: solid;
186
- border-color: var(--v-secondary-base);
187
210
  border-width: 2px 2px 2px 16px;
188
211
  border-radius: 4px;
212
+ border-color: var(--v-primary-base);
189
213
  }
214
+
190
215
  .span-title {
191
216
  font-style: italic;
192
217
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :class="blockDirectionClasses" :dir="blockTextDirection">
3
3
  <v-container class="pa-0">
4
4
  <h2
5
5
  v-if="
@@ -18,7 +18,14 @@
18
18
  {{ block.metadata.config.instructions }}
19
19
  </p>
20
20
  </v-container>
21
- <v-container class="pa-0">
21
+ <v-alert v-if="block.metadata.config.items.length === 0" type="warning">
22
+ {{
23
+ $t(
24
+ 'windward.core.components.settings.clickable_icon.no_clickable_items'
25
+ )
26
+ }}
27
+ </v-alert>
28
+ <v-container class="pa-0" v-else>
22
29
  <v-container
23
30
  v-for="(item, itemIndex) in block.metadata.config.items"
24
31
  :key="itemIndex"
@@ -57,9 +64,11 @@
57
64
  class="clickable--icon dark-text--text"
58
65
  >{{ item.icon }}
59
66
  </v-icon>
60
- <span v-else :class="iconClass + ' dark-text--text'">{{
61
- decode(item.icon)
62
- }}</span>
67
+ <span
68
+ v-else
69
+ :class="iconClass + ' dark-text--text'"
70
+ >{{ decode(item.icon) }}</span
71
+ >
63
72
  </button>
64
73
  </v-col>
65
74
  <v-col style="min-width: 0" class="ma-3 mt-5">
@@ -142,7 +151,8 @@ export default {
142
151
  ) &&
143
152
  this.itemColor(itemIndex)
144
153
  ) {
145
- classes += ' ' + this.itemColor(itemIndex) + ' dark-text--text'
154
+ classes +=
155
+ ' ' + this.itemColor(itemIndex) + ' dark-text--text'
146
156
  }
147
157
  return classes
148
158
  }
@@ -232,7 +242,9 @@ export default {
232
242
  )
233
243
  }
234
244
  if (_.isEmpty(this.block.metadata.config.title)) {
235
- this.block.metadata.config.title = ''
245
+ this.block.metadata.config.title = this.$t(
246
+ 'windward.core.components.settings.clickable_icon.clickable_icon_title'
247
+ )
236
248
  }
237
249
  if (!_.isBoolean(this.block.metadata.config.display_title)) {
238
250
  this.$set(this.block.metadata.config, 'display_title', true)
@@ -242,10 +254,11 @@ export default {
242
254
  }
243
255
  if (_.isEmpty(this.block.metadata.config.display)) {
244
256
  this.block.metadata.config.display = {
245
- show_title: false,
246
- show_background: false,
257
+ show_title: true,
258
+ show_background: true,
247
259
  round_icon: false,
248
260
  italic_icon: false,
261
+ large_icon: false,
249
262
  autocolor: true,
250
263
  }
251
264
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-container>
2
+ <v-container :class="blockDirectionClasses" :dir="blockTextDirection">
3
3
  <v-row
4
4
  v-if="
5
5
  block.metadata.config.title ||
@@ -25,10 +25,15 @@
25
25
  </p>
26
26
  </v-col>
27
27
  </v-row>
28
- <v-row>
28
+ <v-row v-if="block.metadata.config.emails.length === 0">
29
+ <v-alert type="warning" width="100%">
30
+ {{ $t('windward.core.components.settings.email.no_emails') }}
31
+ </v-alert>
32
+ </v-row>
33
+ <v-row v-else>
29
34
  <v-expansion-panels
30
- v-model="selectedPanels"
31
35
  :key="expansionPanelKey"
36
+ v-model="selectedPanels"
32
37
  accordion
33
38
  >
34
39
  <v-container
@@ -256,7 +261,9 @@ export default {
256
261
  this.block.metadata.config = {}
257
262
  }
258
263
  if (_.isEmpty(this.block.metadata.config.title)) {
259
- this.block.metadata.config.title = ''
264
+ this.block.metadata.config.title = this.$t(
265
+ 'windward.core.components.content.blocks.email.email'
266
+ )
260
267
  }
261
268
  if (!_.isBoolean(this.block.metadata.config.display_title)) {
262
269
  this.$set(this.block.metadata.config, 'display_title', true)
@@ -0,0 +1,179 @@
1
+ <template>
2
+ <div>
3
+ <div v-if="questionCharts.length === 0">No Responses Available</div>
4
+ <div v-else>
5
+ <v-row dense>
6
+ <v-col
7
+ v-for="(opts, idx) in questionCharts"
8
+ :key="idx"
9
+ cols="12"
10
+ sm="12"
11
+ md="6"
12
+ lg="4"
13
+ class="mb-6"
14
+ >
15
+ <highcharts :options="opts"></highcharts>
16
+ </v-col>
17
+ </v-row>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import SurveyResultMetric from '../../../../models/SurveyResultMetric'
24
+ import Course from '~/models/Course'
25
+ import ContentBlock from '~/models/ContentBlock'
26
+ export default {
27
+ name: 'FeedbackAnalytics',
28
+ components: {},
29
+ props: {
30
+ loading: { type: Boolean, required: false, default: false },
31
+ filters: {
32
+ type: Object,
33
+ required: false,
34
+ default() {
35
+ return { sections: [], roles: [], enrollments: [] }
36
+ },
37
+ },
38
+ courseContent: {
39
+ type: Object,
40
+ required: false,
41
+ default() {
42
+ return {}
43
+ },
44
+ },
45
+ block: {
46
+ type: Object,
47
+ required: true,
48
+ default() {
49
+ return {}
50
+ },
51
+ },
52
+ },
53
+ data() {
54
+ return {
55
+ metrics: [],
56
+ questionCharts: [],
57
+ }
58
+ },
59
+ async fetch() {
60
+ await this.loadMetrics()
61
+ },
62
+ watch: {
63
+ filters: {
64
+ deep: true,
65
+ handler() {
66
+ // Reload the metrics if this filter changes
67
+ this.loadMetrics()
68
+ },
69
+ },
70
+ },
71
+ methods: {
72
+ async loadMetrics() {
73
+ this.metrics = await SurveyResultMetric.custom(
74
+ new Course({ id: this.courseContent.course_id }),
75
+ new ContentBlock({ id: this.block.id }),
76
+ new SurveyResultMetric()
77
+ )
78
+ .whereIn('course_user_id', this.filters.enrollments)
79
+ .whereIn('course_section_id', this.filters.sections)
80
+ .whereIn('user.role_id', this.filters.roles)
81
+ .get()
82
+ this.createTables(this.metrics)
83
+ },
84
+ createTables(metrics) {
85
+ const charts = []
86
+
87
+ for (const m of metrics) {
88
+ if (this.isLikertMetric(m)) {
89
+ const categories = [
90
+ 'strongly agree',
91
+ 'agree',
92
+ 'neutral',
93
+ 'disagree',
94
+ 'strongly disagree',
95
+ ]
96
+ const counts = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }
97
+ for (const r of m.responses || []) {
98
+ const v =
99
+ typeof r.response_text === 'string'
100
+ ? parseInt(r.response_text, 10)
101
+ : r.response_text
102
+ if (Number.isInteger(v) && v >= 1 && v <= 5) {
103
+ counts[v] =
104
+ (counts[v] || 0) + (r.response_count || 0)
105
+ }
106
+ }
107
+ charts.push({
108
+ chart: { type: 'column' },
109
+ title: { text: m.survey_question_body || 'Question' },
110
+ xAxis: { categories },
111
+ yAxis: { min: 0 },
112
+ plotOptions: {
113
+ column: { pointPadding: 0.2, borderWidth: 0 },
114
+ },
115
+ series: [
116
+ {
117
+ name: 'Responses',
118
+ data: [
119
+ counts[5],
120
+ counts[4],
121
+ counts[3],
122
+ counts[2],
123
+ counts[1],
124
+ ],
125
+ },
126
+ ],
127
+ })
128
+ } else if (this.isBooleanMetric(m)) {
129
+ const categories = ['true', 'false']
130
+ let trueCount = 0
131
+ let falseCount = 0
132
+ for (const r of m.responses || []) {
133
+ if (r.response_text === 'true') {
134
+ trueCount += r.response_count || 0
135
+ } else if (r.response_text === 'false') {
136
+ falseCount += r.response_count || 0
137
+ }
138
+ }
139
+ charts.push({
140
+ chart: { type: 'column' },
141
+ title: { text: m.survey_question_body || 'Question' },
142
+ xAxis: { categories },
143
+ yAxis: { min: 0 },
144
+ plotOptions: {
145
+ column: { pointPadding: 0.2, borderWidth: 0 },
146
+ },
147
+ series: [
148
+ {
149
+ name: 'Responses',
150
+ data: [trueCount, falseCount],
151
+ },
152
+ ],
153
+ })
154
+ } else {
155
+ // Unsupported (e.g., open-text); skip for now
156
+ }
157
+ }
158
+
159
+ this.questionCharts = charts
160
+ },
161
+ isLikertMetric(metric) {
162
+ if (!metric || !Array.isArray(metric.responses)) return false
163
+
164
+ return metric.responses.every((r) => {
165
+ const v = r && r.response_text
166
+ const n = typeof v === 'string' ? parseInt(v, 10) : v
167
+ return Number.isInteger(n) && n >= 1 && n <= 5
168
+ })
169
+ },
170
+ isBooleanMetric(metric) {
171
+ if (!metric || !Array.isArray(metric.responses)) return false
172
+ return metric.responses.every((r) => {
173
+ const v = r && r.response_text
174
+ return v === 'true' || v === 'false'
175
+ })
176
+ },
177
+ },
178
+ }
179
+ </script>