@windward/core 0.25.1 → 0.27.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 (110) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/components/Content/Blocks/Accordion.vue +8 -14
  3. package/components/Content/Blocks/BlockQuote.vue +29 -4
  4. package/components/Content/Blocks/ClickableIcons.vue +18 -6
  5. package/components/Content/Blocks/Email.vue +10 -3
  6. package/components/Content/Blocks/Feedback/FeedbackAnalytics.vue +179 -0
  7. package/components/Content/Blocks/Feedback.vue +115 -111
  8. package/components/Content/Blocks/Image.vue +4 -0
  9. package/components/Content/Blocks/OpenResponse.vue +0 -9
  10. package/components/Content/Blocks/OpenResponseCollate.vue +27 -0
  11. package/components/Content/Blocks/ScenarioChoice.vue +10 -1
  12. package/components/Content/Blocks/Tab.vue +15 -28
  13. package/components/Content/Blocks/UserUpload.vue +65 -37
  14. package/components/Content/Blocks/Video.vue +17 -7
  15. package/components/Settings/AccordionSettings.vue +3 -15
  16. package/components/Settings/BlockQuoteSettings.vue +6 -4
  17. package/components/Settings/ClickableIconsSettings.vue +21 -7
  18. package/components/Settings/EmailSettings.vue +3 -11
  19. package/components/Settings/FileDownloadSettings.vue +8 -2
  20. package/components/Settings/OpenResponseCollateSettings.vue +19 -1
  21. package/components/Settings/OpenResponseSettings.vue +8 -7
  22. package/components/Settings/ScenarioChoiceSettings.vue +11 -5
  23. package/components/Settings/TabSettings.vue +3 -18
  24. package/components/Settings/UserUploadSettings.vue +16 -8
  25. package/components/Settings/VideoSettings/SourcePicker.vue +34 -8
  26. package/components/Settings/VideoSettings.vue +18 -2
  27. package/components/utils/glossary/GlossaryToolTip.vue +4 -23
  28. package/helpers/GlossaryHelper.ts +4 -7
  29. package/i18n/en-US/components/content/blocks/accordion.ts +3 -0
  30. package/i18n/en-US/components/content/blocks/block_quote.ts +3 -1
  31. package/i18n/en-US/components/content/blocks/feedback.ts +2 -0
  32. package/i18n/en-US/components/content/blocks/file_download.ts +2 -1
  33. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  34. package/i18n/en-US/components/content/blocks/open_response.ts +1 -1
  35. package/i18n/en-US/components/content/blocks/open_response_collate.ts +1 -1
  36. package/i18n/en-US/components/content/blocks/scenario_choice.ts +2 -0
  37. package/i18n/en-US/components/content/blocks/user_upload.ts +2 -1
  38. package/i18n/en-US/components/settings/accordion.ts +2 -1
  39. package/i18n/en-US/components/settings/block_quote.ts +1 -1
  40. package/i18n/en-US/components/settings/clickable_icon.ts +5 -0
  41. package/i18n/en-US/components/settings/email.ts +2 -1
  42. package/i18n/en-US/components/settings/file_download.ts +2 -2
  43. package/i18n/en-US/components/settings/image.ts +1 -0
  44. package/i18n/en-US/components/settings/open_response.ts +3 -0
  45. package/i18n/en-US/components/settings/open_response_collate.ts +3 -0
  46. package/i18n/en-US/components/settings/scenario_choice.ts +3 -1
  47. package/i18n/en-US/components/settings/tab.ts +4 -3
  48. package/i18n/en-US/components/settings/user_upload.ts +1 -0
  49. package/i18n/en-US/components/settings/video.ts +3 -1
  50. package/i18n/en-US/shared/content_blocks.ts +1 -1
  51. package/i18n/en-US/shared/settings.ts +1 -18
  52. package/i18n/es-ES/components/content/blocks/accordion.ts +3 -0
  53. package/i18n/es-ES/components/content/blocks/block_quote.ts +3 -1
  54. package/i18n/es-ES/components/content/blocks/feedback.ts +2 -0
  55. package/i18n/es-ES/components/content/blocks/file_download.ts +2 -1
  56. package/i18n/es-ES/components/content/blocks/index.ts +2 -0
  57. package/i18n/es-ES/components/content/blocks/open_response.ts +1 -2
  58. package/i18n/es-ES/components/content/blocks/open_response_collate.ts +1 -1
  59. package/i18n/es-ES/components/content/blocks/scenario_choice.ts +2 -0
  60. package/i18n/es-ES/components/content/blocks/user_upload.ts +2 -1
  61. package/i18n/es-ES/components/settings/accordion.ts +4 -2
  62. package/i18n/es-ES/components/settings/block_quote.ts +1 -1
  63. package/i18n/es-ES/components/settings/clickable_icon.ts +7 -0
  64. package/i18n/es-ES/components/settings/email.ts +2 -1
  65. package/i18n/es-ES/components/settings/image.ts +1 -0
  66. package/i18n/es-ES/components/settings/open_response.ts +3 -0
  67. package/i18n/es-ES/components/settings/open_response_collate.ts +3 -0
  68. package/i18n/es-ES/components/settings/scenario_choice.ts +3 -1
  69. package/i18n/es-ES/components/settings/tab.ts +3 -2
  70. package/i18n/es-ES/components/settings/user_upload.ts +1 -0
  71. package/i18n/es-ES/components/settings/video.ts +3 -1
  72. package/i18n/es-ES/shared/content_blocks.ts +1 -1
  73. package/i18n/es-ES/shared/settings.ts +1 -19
  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 +1 -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 +3 -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/i18n/sv-SE/shared/settings.ts +1 -18
  96. package/models/SurveyResultMetric.ts +8 -0
  97. package/package.json +1 -1
  98. package/plugin.js +27 -19
  99. package/test/Components/Content/Blocks/Feedback/FeedbackTemplates/FeedbackAnalytics.spec.js +23 -0
  100. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.spec.js +1 -1
  101. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.spec.js +1 -1
  102. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.spec.js +1 -1
  103. package/test/Components/Settings/AccordionSettings.spec.js +0 -13
  104. package/test/Components/Settings/ClickableIconsSettings.spec.js +0 -11
  105. package/test/Components/Settings/EmailSettings.spec.js +0 -9
  106. package/test/Components/Settings/TabSettings.spec.js +0 -13
  107. package/test/helpers/GlossaryHelper.spec.js +8 -8
  108. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.vue +1 -1
  109. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.vue +1 -1
  110. /package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.vue +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## Release [0.27.0] - 2026-01-15
4
+
5
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #476)
6
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
7
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #474)
8
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
9
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #473)
10
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
11
+ * Merged in LE-1915-block-quote-design-updates (pull request #468)
12
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-1915-block-quote-design-updates
13
+ * Merged in LE-2185-feedback-block-add-container-bac (pull request #469)
14
+ * Merge remote-tracking branch 'origin/release/0.27.0' into LE-2185-feedback-block-add-container-bac
15
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #472)
16
+ * Merged in feature/LE-1788-transcript-generation (pull request #465)
17
+ * Merged release/0.27.0 into feature/LE-2236-bar-charts-for-non-open-ended-it
18
+ * Merged in LE-2186-file-upload-block-add-container- (pull request #470)
19
+ * Merged in feature/LE-2236-bar-charts-for-non-open-ended-it (pull request #471)
20
+ * Merged in LE-1897-glossary-pop-up-displayy (pull request #467)
21
+ * Add localized "item header" message for ClickableIconsSettings and update "initial setup" text for OpenResponse block
22
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #466)
23
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
24
+ * Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #464)
25
+ * Merge remote-tracking branch 'origin/feature/LE-2155-consistent-ui-for-adding-new-blo' into feature/LE-2155-consistent-ui-for-adding-new-blo
26
+ * Merged release/0.27.0 into feature/LE-2155-consistent-ui-for-adding-new-blo
27
+ * add localized messages for OpenResponseCollate and update default settings handling
28
+ * Merged in release/0.26.0 (pull request #455)
29
+ * Merged in hotfix/0.25.1 (pull request #457)
30
+
31
+
32
+ ## Release [0.26.0] - 2025-12-05
33
+
34
+ * Merged in feature/LE-2154-open-response-download-add-a-tit (pull request #461)
35
+ * Merged in feature/LE-2156-block-settings-label (pull request #458)
36
+ * Merged in LE-2124-move-some-blocks-to-activities-s (pull request #459)
37
+ * Merged in bugfix/LE-2205-open-response-field-rendering-sw (pull request #460)
38
+ * Merged release/0.26.0 into bugfix/LE-2205-open-response-field-rendering-sw
39
+ * hotfix: add starting text support for OpenResponse blocks and enhance content insertion logic in TinyMCE
40
+ * Merged in hotfix/0.25.1 (pull request #456)
41
+ * Merged in feature/LE-2108/revise-text-fix (pull request #453)
42
+ * Merged in feature/LE-2123/open-res-gen-fix (pull request #452)
43
+ * Merged in release/0.25.0 (pull request #448)
44
+ * Merged in release/0.24.0 (pull request #442)
45
+
46
+
3
47
  ## Hotfix [0.25.1] - 2025-11-07
4
48
 
5
49
  * Merged in feature/LE-2108/revise-text-fix (pull request #453)
@@ -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="containerClasses">
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
  }
@@ -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)
@@ -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>
@@ -1,15 +1,12 @@
1
1
  <template>
2
- <v-container>
3
- <v-container v-if="!doesFeedbackExist" class="pa-0">
2
+ <div>
3
+ <v-alert v-if="!chosenPreset" type="warning">{{
4
+ $t('windward.core.components.content.blocks.feedback.no_feedback')
5
+ }}</v-alert>
6
+ <v-container v-if="!doesFeedbackExist && chosenPreset" class="pa-0">
4
7
  <v-row>
5
8
  <h2>
6
- {{
7
- block.metadata.config.definition.name
8
- ? block.metadata.config.definition.name
9
- : $t(
10
- 'windward.core.components.content.blocks.feedback.feedback'
11
- )
12
- }}
9
+ {{ block.metadata.config.definition.name }}
13
10
  </h2>
14
11
  </v-row>
15
12
  <v-row class="pt-2">
@@ -45,126 +42,133 @@
45
42
  </p>
46
43
  </v-row>
47
44
  </v-container>
48
- <v-divider class="mt-4 mb-6"></v-divider>
49
- <v-form
50
- ref="form"
51
- v-model="valid"
52
- lazy-validation
53
- v-if="!doesFeedbackExist"
54
- >
55
- <div v-if="block.metadata.config.definition.metadata">
45
+ <v-divider v-if="chosenPreset" class="mt-4 mb-6"></v-divider>
46
+ <!-- Survey Container with theme-based background -->
47
+ <v-card v-if="chosenPreset" class="pa-6 surface" flat>
48
+ <v-form
49
+ ref="form"
50
+ v-model="valid"
51
+ lazy-validation
52
+ v-if="!doesFeedbackExist"
53
+ >
54
+ <div v-if="block.metadata.config.definition.metadata">
55
+ <v-container
56
+ v-for="(question, questionIndex) in chosenPreset"
57
+ :key="questionIndex"
58
+ fluid
59
+ >
60
+ <v-container
61
+ v-if="question.type === 'likert'"
62
+ class="pa-0 ma-0"
63
+ >
64
+ <FeedbackQuestionLikert
65
+ v-model="chosenPreset[questionIndex]"
66
+ ></FeedbackQuestionLikert>
67
+ </v-container>
68
+ <v-container
69
+ v-if="question.type === 'true_false'"
70
+ class="pa-0 ma-0"
71
+ >
72
+ <FeedbackQuestionTrueFalse
73
+ v-model="chosenPreset[questionIndex]"
74
+ ></FeedbackQuestionTrueFalse>
75
+ </v-container>
76
+ <v-container
77
+ v-if="question.type === 'open_response'"
78
+ class="pa-0 ma-0"
79
+ >
80
+ <FeedbackQuestionOpenResponse
81
+ v-model="chosenPreset[questionIndex]"
82
+ ></FeedbackQuestionOpenResponse>
83
+ </v-container>
84
+ </v-container>
85
+ </div>
86
+ </v-form>
87
+ <v-row
88
+ class="d-flex justify-end align-center"
89
+ v-if="render && !doesFeedbackExist"
90
+ >
91
+ <v-btn
92
+ color="primary"
93
+ outlined
94
+ elevation="0"
95
+ class="text-center mt-4 mr-2"
96
+ @click="onReset()"
97
+ >{{
98
+ $t(
99
+ 'windward.core.components.content.blocks.feedback.reset'
100
+ )
101
+ }}</v-btn
102
+ >
103
+ <v-btn
104
+ elevation="0"
105
+ color="success"
106
+ class="text-center mt-4"
107
+ @click="validate"
108
+ >{{
109
+ $t(
110
+ 'windward.core.components.content.blocks.feedback.save'
111
+ )
112
+ }}</v-btn
113
+ >
114
+ </v-row>
115
+ <v-form
116
+ ref="form"
117
+ v-model="valid"
118
+ disabled
119
+ lazy-validation
120
+ v-if="doesFeedbackExist"
121
+ >
56
122
  <v-container
57
- v-for="(question, questionIndex) in chosenPreset"
123
+ v-for="(
124
+ question, questionIndex
125
+ ) in existingFeedback.survey_question_answers"
58
126
  :key="questionIndex"
59
127
  fluid
60
128
  >
61
- <v-container
62
- v-if="question.type === 'likert'"
63
- class="pa-0 ma-0"
64
- >
129
+ <div v-if="question.type === 'likert'">
65
130
  <FeedbackQuestionLikert
66
- v-model="chosenPreset[questionIndex]"
131
+ v-model="
132
+ existingFeedback.survey_question_answers[
133
+ questionIndex
134
+ ]
135
+ "
67
136
  ></FeedbackQuestionLikert>
68
- </v-container>
69
- <v-container
70
- v-if="question.type === 'true_false'"
71
- class="pa-0 ma-0"
72
- >
137
+ </div>
138
+ <div v-if="question.type === 'true_false'">
73
139
  <FeedbackQuestionTrueFalse
74
- v-model="chosenPreset[questionIndex]"
140
+ v-model="
141
+ existingFeedback.survey_question_answers[
142
+ questionIndex
143
+ ]
144
+ "
75
145
  ></FeedbackQuestionTrueFalse>
76
- </v-container>
77
- <v-container
78
- v-if="question.type === 'open_response'"
79
- class="pa-0 ma-0"
80
- >
146
+ </div>
147
+ <div v-if="question.type === 'open_response'">
81
148
  <FeedbackQuestionOpenResponse
82
- v-model="chosenPreset[questionIndex]"
149
+ v-model="
150
+ existingFeedback.survey_question_answers[
151
+ questionIndex
152
+ ]
153
+ "
154
+ :feedbackExist="true"
83
155
  ></FeedbackQuestionOpenResponse>
84
- </v-container>
156
+ </div>
85
157
  </v-container>
86
- </div>
87
- </v-form>
88
- <v-row
89
- class="d-flex justify-end align-center"
90
- v-if="render && !doesFeedbackExist"
91
- >
92
- <v-btn
93
- color="primary"
94
- outlined
95
- elevation="0"
96
- class="text-center mt-4 mr-2"
97
- @click="onReset()"
98
- >{{
99
- $t('windward.core.components.content.blocks.feedback.reset')
100
- }}</v-btn
101
- >
102
- <v-btn
103
- elevation="0"
104
- color="success"
105
- class="text-center mt-4"
106
- @click="validate"
107
- >{{
108
- $t('windward.core.components.content.blocks.feedback.save')
109
- }}</v-btn
110
- >
111
- </v-row>
112
- <v-form
113
- ref="form"
114
- v-model="valid"
115
- disabled
116
- lazy-validation
117
- v-if="doesFeedbackExist"
118
- >
119
- <v-container
120
- v-for="(
121
- question, questionIndex
122
- ) in existingFeedback.survey_question_answers"
123
- :key="questionIndex"
124
- fluid
125
- >
126
- <div v-if="question.type === 'likert'">
127
- <FeedbackQuestionLikert
128
- v-model="
129
- existingFeedback.survey_question_answers[
130
- questionIndex
131
- ]
132
- "
133
- ></FeedbackQuestionLikert>
134
- </div>
135
- <div v-if="question.type === 'true_false'">
136
- <FeedbackQuestionTrueFalse
137
- v-model="
138
- existingFeedback.survey_question_answers[
139
- questionIndex
140
- ]
141
- "
142
- ></FeedbackQuestionTrueFalse>
143
- </div>
144
- <div v-if="question.type === 'open_response'">
145
- <FeedbackQuestionOpenResponse
146
- v-model="
147
- existingFeedback.survey_question_answers[
148
- questionIndex
149
- ]
150
- "
151
- :feedbackExist="true"
152
- ></FeedbackQuestionOpenResponse>
153
- </div>
154
- </v-container>
155
- </v-form>
156
- </v-container>
158
+ </v-form>
159
+ </v-card>
160
+ </div>
157
161
  </template>
158
162
 
159
163
  <script>
160
164
  import _ from 'lodash'
161
165
  import { mapGetters } from 'vuex'
162
- import FeedbackQuestionLikert from './FeedbackTemplates/FeedbackQuestionLikert.vue'
163
- import FeedbackQuestionOpenResponse from './FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
164
- import FeedbackQuestionTrueFalse from './FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
166
+ import SurveyResult from '../../../models/SurveyResult'
167
+ import FeedbackQuestionLikert from './Feedback/FeedbackTemplates/FeedbackQuestionLikert.vue'
168
+ import FeedbackQuestionOpenResponse from './Feedback/FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
169
+ import FeedbackQuestionTrueFalse from './Feedback/FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
165
170
  import Uuid from '~/helpers/Uuid'
166
171
 
167
- import SurveyResult from '../../../models/SurveyResult'
168
172
  import Enrollment from '~/models/Enrollment'
169
173
  import ContentBlock from '~/models/ContentBlock'
170
174
  import Crypto from '~/helpers/Crypto'