feat: Add collapsible list view of submissions

Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2023-03-15 14:22:06 +01:00
Родитель 87a5e47ca8
Коммит e865815d21
3 изменённых файлов: 200 добавлений и 15 удалений

Просмотреть файл

@ -59,6 +59,7 @@ export default {
&__text {
white-space: pre-line;
margin-left: 1rem;
}
}

Просмотреть файл

@ -0,0 +1,163 @@
<!--
- @copyright Copyright (c) 2023 Ferdinand Thiessen <rpm@fthiessen.de>
-
- @author Ferdinand Thiessen <rpm@fthiessen.de>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<NcListItem :title="submissionDateTime"
:bold="false"
:details="submissionAge"
:link-aria-label="t('forms', 'Click to expand submission')"
@click="onExpand">
<template #icon>
<NcAvatar v-if="!submission.userId.startsWith('anon-user-')"
:size="44"
:user="submission.userId"
:display-name="submission.userDisplayName" />
<IconAccountOff v-else :size="44" />
</template>
<template #subtitle>
{{ submission.userDisplayName }}
</template>
<template #extra>
<div v-if="expanded" class="submission">
<Answer v-for="question in answeredQuestions"
:key="question.id"
:answer-text="question.squashedAnswers"
:question-text="question.text" />
</div>
</template>
<template v-if="!viewed" #indicator>
<IconCheckboxBlankCircle :size="14" fill-color="var(--color-primary)" />
</template>
<template #actions>
<NcActionButton v-if="canDeleteSubmission" @click="onDelete">
<template #icon>
<IconDelete :size="20" />
</template>
{{ t('forms', 'Delete this response') }}
</NcActionButton>
</template>
</NcListItem>
</template>
<script>
import Answer from './Answer.vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
import moment from '@nextcloud/moment'
import IconAccountOff from 'vue-material-design-icons/AccountOff.vue'
import IconCheckboxBlankCircle from 'vue-material-design-icons/CheckboxBlankCircle.vue'
import IconDelete from 'vue-material-design-icons/Delete.vue'
export default {
name: 'SubmissionItem',
components: {
Answer,
IconAccountOff,
IconCheckboxBlankCircle,
IconDelete,
NcActionButton,
NcAvatar,
NcListItem,
},
props: {
submission: {
type: Object,
required: true,
},
questions: {
type: Array,
required: true,
},
canDeleteSubmission: {
type: Boolean,
required: true,
},
},
data() {
return {
expanded: false,
viewed: false,
}
},
computed: {
// Format submission-timestamp to DateTime
submissionDateTime() {
return moment(this.submission.timestamp, 'X').format('LLLL')
},
/**
* Age of the submission, e.g. '11 hours' or '1 year'
*/
submissionAge() {
return moment(this.submission.timestamp, 'X').fromNow(true)
},
/**
* Join answered Questions with corresponding answers.
* Multiple answers to a question are squashed into one string.
*
* @return {Array}
*/
answeredQuestions() {
const answeredQuestionsArray = []
this.questions.forEach(question => {
const answers = this.submission.answers.filter(answer => answer.questionId === question.id)
if (!answers.length) {
return // no answers, go to next question
}
const squashedAnswers = answers.map(answer => answer.text).join('; ')
answeredQuestionsArray.push({
id: question.id,
text: question.text,
squashedAnswers,
})
})
return answeredQuestionsArray
},
},
methods: {
onDelete() {
this.$emit('delete')
},
onExpand() {
this.expanded = !this.expanded
this.viewed = true
},
},
}
</script>
<style scoped lang="scss">
.session {
padding-left: 1em;
}
</style>

Просмотреть файл

@ -44,23 +44,33 @@
<div class="response-actions">
<div class="response-actions__radio">
<input id="show-summary--true"
v-model="showSummary"
v-model="viewMode"
value="summary"
type="radio"
:value="true"
class="hidden">
<label for="show-summary--true"
class="response-actions__radio__item"
:class="{ 'response-actions__radio__item--active': showSummary }">
:class="{ 'response-actions__radio__item--active': viewMode === 'summary' }">
{{ t('forms', 'Summary') }}
</label>
<input id="show-summary--false"
v-model="showSummary"
<input id="show-list"
v-model="viewMode"
value="list"
type="radio"
class="hidden">
<label for="show-list"
class="response-actions__radio__item"
:class="{ 'response-actions__radio__item--active': viewMode === 'list' }">
{{ t('forms', 'List') }}
</label>
<input id="show-summary--false"
v-model="viewMode"
value="items"
type="radio"
:value="false"
class="hidden">
<label for="show-summary--false"
class="response-actions__radio__item"
:class="{ 'response-actions__radio__item--active': !showSummary }">
:class="{ 'response-actions__radio__item--active': viewMode === 'items' }">
{{ t('forms', 'Responses') }}
</label>
</div>
@ -110,15 +120,27 @@
</section>
<!-- Summary view for visualization -->
<section v-if="!noSubmissions && showSummary">
<section v-else-if="viewMode === 'summary'">
<ResultsSummary v-for="question in form.questions"
:key="question.id"
:question="question"
:submissions="form.submissions" />
</section>
<!-- Responses view for individual responses using list style -->
<section v-else-if="viewMode === 'list'">
<ul>
<SubmissionItem v-for="submission in form.submissions"
:key="submission.id"
:submission="submission"
:questions="form.questions"
:can-delete-submission="canDeleteSubmissions"
@delete="deleteSubmission(submission.id)" />
</ul>
</section>
<!-- Responses view for individual responses -->
<section v-if="!noSubmissions && !showSummary">
<section v-else>
<Submission v-for="submission in form.submissions"
:key="submission.id"
:submission="submission"
@ -151,6 +173,7 @@ import IconShareVariant from 'vue-material-design-icons/ShareVariant.vue'
import ResultsSummary from '../components/Results/ResultsSummary.vue'
import Submission from '../components/Results/Submission.vue'
import SubmissionItem from '../components/Results/SubmissionItem.vue'
import TopBar from '../components/TopBar.vue'
import ViewsMixin from '../mixins/ViewsMixin.js'
import answerTypes from '../models/AnswerTypes.js'
@ -183,6 +206,7 @@ export default {
NcLoadingIcon,
ResultsSummary,
Submission,
SubmissionItem,
TopBar,
},
@ -191,7 +215,7 @@ export default {
data() {
return {
loadingResults: true,
showSummary: true,
viewMode: 'summary',
}
},
@ -368,20 +392,17 @@ export default {
margin-right: 8px;
&__item {
border-radius: var(--border-radius-pill);
padding: 8px 16px;
font-weight: bold;
background-color: var(--color-background-dark);
&:first-of-type {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-radius: var(--border-radius-pill) 0 0 var(--border-radius-pill);
padding-right: 8px;
}
&:last-of-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-radius: 0 var(--border-radius-pill) var(--border-radius-pill) 0;
padding-left: 8px;
}