This repository has been archived on 2024-07-22. You can view files and clone it, but cannot push or open issues or pull requests.
Zaimki/routes/census.vue

288 lines
11 KiB
Vue
Raw Normal View History

2020-12-18 02:34:58 -08:00
<template>
<div>
2021-08-30 02:36:57 -07:00
<CommunityNav/>
2020-12-18 02:34:58 -08:00
<h2>
<Icon v="user-chart"/>
<T>census.headerLong</T>
</h2>
<template v-if="q === null">
2020-12-30 15:03:30 -08:00
<section v-if="$isGranted('census')">
2020-12-18 08:41:01 -08:00
<div class="alert alert-info">
{{countResponses}}
<T>census.replies</T>
2021-02-02 07:52:23 -08:00
2021-03-01 13:35:03 -08:00
<a href="/api/census/export" class="btn btn-outline-secondary btn-sm float-end">
2021-02-02 07:52:23 -08:00
<Icon v="download"/>
</a>
2020-12-18 08:41:01 -08:00
</div>
</section>
2020-12-18 02:34:58 -08:00
<section>
<T :params='{
questions: questions.length,
2021-01-02 04:58:17 -08:00
start: start.setLocale(config.locale).toLocaleString(DateTime.DATE_SHORT),
end: end.setLocale(config.locale).toLocaleString(DateTime.DATE_SHORT),
2020-12-18 02:34:58 -08:00
}'>census.description</T>
</section>
2021-04-14 10:17:57 -07:00
<section v-if="Object.keys(config.census.results).length > 0" class="alert alert-info">
<ul class="mb-0">
<li v-for="(text, link) in config.census.results">
<router-link :to="`/blog/${link}`">{{text}}</router-link>
</li>
</ul>
</section>
2020-12-18 02:34:58 -08:00
<section>
<Share :title="$t('census.headerLong')"/>
</section>
2021-01-02 04:58:17 -08:00
<section v-if="open">
2020-12-18 02:34:58 -08:00
<div v-if="finished" class="alert alert-success">
<Icon v="badge-check"/>
<T>census.finished</T>
</div>
<template v-else>
<div class="form-group">
<div class="form-check">
<label class="form-check-label small">
<input type="checkbox" class="form-check-input" v-model="agreement">
<T>census.agree</T>
</label>
</div>
</div>
<div class="form-group">
2021-01-02 04:58:17 -08:00
<button class="btn btn-primary btn-lg" :disabled="!agreement" @click="startSurvey">
2020-12-18 02:34:58 -08:00
<T>census.start</T>
</button>
</div>
</template>
</section>
</template>
<template v-else-if="q < questions.length">
<div class="progress my-3">
<div class="progress-bar" role="progressbar" :style="`width: ${progress}%`" :aria-valuenow="q" aria-valuemin="0" :aria-valuemax="questions.length">
{{q}}/{{questions.length}}
</div>
</div>
2021-01-02 11:38:31 -08:00
<p class="h4 mt-5 mb-3">{{q+1}}. {{question.question}}</p>
<div v-if="question.instruction" class="alert alert-info small">
2021-01-02 06:34:45 -08:00
<p v-for="(line, i) in question.instruction" :class="i === question.instruction.length - 1 ? 'mb-0' : ''">
<LinkedText :text="line"/>
</p>
2021-01-02 05:54:53 -08:00
</div>
2021-01-19 12:04:22 -08:00
<form @submit.prevent="q++" ref="questionform">
2021-01-02 04:58:17 -08:00
<div v-if="question.type === 'radio'" :class="['form-group', question.options.length > 10 ? 'multi-column' : '']">
2021-01-02 07:42:22 -08:00
<div class="form-check mb-2" v-for="[option, help] in question.options">
2020-12-18 02:34:58 -08:00
<label class="form-check-label small">
<input type="radio" class="form-check-input" v-model="answers[q]" :name="'question' + q" :value="option" required/>
{{option}}
2021-01-02 07:42:22 -08:00
<span v-if="help" class="text-muted">({{help}})</span>
2020-12-18 02:34:58 -08:00
</label>
</div>
</div>
2021-01-02 04:58:17 -08:00
<div v-else-if="question.type === 'checkbox'" :class="['form-group', question.options.length > 10 ? 'multi-column' : '']">
2021-01-02 07:42:22 -08:00
<div class="form-check mb-2" v-for="[option, help] in question.options">
2020-12-18 02:34:58 -08:00
<label class="form-check-label small">
<input type="checkbox" class="form-check-input" v-model="answers[q]" :value="option"/>
{{option}}
2021-01-02 07:42:22 -08:00
<span v-if="help" class="text-muted">({{help}})</span>
2020-12-18 02:34:58 -08:00
</label>
</div>
</div>
2021-01-02 04:58:17 -08:00
<div v-else-if="question.type === 'text'" class="form-group">
2020-12-18 02:34:58 -08:00
<input type="text" class="form-control" v-model="answers[q]" required/>
</div>
<div v-else-if="question.type === 'number'" class="form-group">
<input type="number" class="form-control" :min="question.min" :max="question.max" v-model="answers[q]" required/>
</div>
2021-01-02 07:42:22 -08:00
<div v-else-if="question.type === 'textarea'" class="form-group">
<textarea class="form-control" v-model="answers[q]"/>
</div>
2020-12-18 02:34:58 -08:00
<div v-if="question.writein" class="form-group">
2021-01-02 04:58:17 -08:00
<input type="text" class="form-control form-control-sm" v-model="writins[q]" :placeholder="$t('census.writein')"/>
2020-12-18 02:34:58 -08:00
</div>
</form>
2021-01-22 14:54:24 -08:00
<div class="btn-group w-100">
2020-12-18 02:34:58 -08:00
<button class="btn btn-outline-primary" :disabled="q === 0" @click="q--">
<Icon v="arrow-alt-left"/>
<T>census.prev</T>
</button>
<button class="btn btn-primary" :disabled="!stepValid" @click="q++">
<T>census.next</T>
<Icon v="arrow-alt-right"/>
</button>
</div>
2021-01-02 06:34:45 -08:00
2021-01-22 14:54:24 -08:00
<div v-if="$user() && $user().username === 'andrea'" class="mt-4 btn-group w-100">
2021-01-02 06:34:45 -08:00
<button v-for="(question, i) in questions" :class="['btn', q === i ? 'btn-primary' : 'btn-outline-primary']" :disabled="q === i" @click="q = i">
{{i}}
</button>
</div>
2020-12-18 02:34:58 -08:00
</template>
<template v-else>
2021-01-02 04:58:17 -08:00
<div class="progress my-3">
<div class="progress-bar" role="progressbar" :style="`width: ${progress}%`" :aria-valuenow="q" aria-valuemin="0" :aria-valuemax="questions.length">
{{q}}/{{questions.length}}
</div>
</div>
2020-12-18 02:34:58 -08:00
<div class="alert alert-success">
<Icon v="badge-check"/>
<T>census.finished</T>
</div>
</template>
</div>
</template>
<script>
import {buildDict, head, shuffle} from "../src/helpers";
2021-01-02 04:58:17 -08:00
import {DateTime} from "luxon";
2020-12-18 02:34:58 -08:00
export default {
data() {
const questions = this.config.census.questions.map(q => {
if (q.randomise) {
2021-01-02 10:36:05 -08:00
q.options = [...shuffle(q.options), ...(q.optionsLast || [])];
2020-12-18 02:34:58 -08:00
}
return q;
});
return {
agreement: false,
q: null,
questions,
answers: buildDict(function* () {
let i = 0;
for (let question of questions) {
yield [i, question.type === 'checkbox' ? [] : null]
i++;
}
}),
writins: buildDict(function* () {
let i = 0;
for (let question of questions) {
yield [i, '']
i++;
}
}),
2021-01-02 04:58:17 -08:00
DateTime,
2020-12-18 02:34:58 -08:00
}
},
async asyncData({ app, store }) {
const finished = await app.$axios.$get(`/census/finished`);
2020-12-18 08:41:01 -08:00
const countResponses = await app.$axios.$get(`/census/count`);
2020-12-18 02:34:58 -08:00
return {
finished,
2020-12-18 08:41:01 -08:00
countResponses,
2020-12-18 02:34:58 -08:00
};
},
mounted() {
if (process.client && !this.$user()) {
this.finished = !!parseInt(window.localStorage.getItem('census-finished') || 0);
}
},
2020-12-18 02:34:58 -08:00
methods: {
2021-01-02 04:58:17 -08:00
startSurvey() {
2020-12-18 02:34:58 -08:00
this.q = 0;
}
},
computed: {
progress() {
return Math.round(100 * (this.q || 0) / this.questions.length);
},
question() {
return this.questions[this.q];
},
stepValid() {
if (!this.question) {
return false;
}
if (this.writins[this.q] !== '') {
return true;
}
2021-01-02 07:42:22 -08:00
if (this.question.optional) {
return true;
}
2020-12-18 02:34:58 -08:00
if (this.question.type === 'radio') {
2021-01-02 07:57:09 -08:00
return this.answers[this.q] !== undefined && this.answers[this.q] !== null;
2020-12-18 02:34:58 -08:00
}
if (this.question.type === 'checkbox') {
return this.answers[this.q] !== undefined && this.answers[this.q].length > 0;
}
if (this.question.type === 'number') {
const v = parseInt(this.answers[this.q]);
return this.answers[this.q] !== '' && v >= this.question.min && v <= this.question.max;
}
2021-01-02 07:42:22 -08:00
if (this.question.type === 'text' || this.question.type === 'textarea') {
2020-12-18 02:34:58 -08:00
return this.answers[this.q] !== '';
}
return true;
2021-01-02 04:58:17 -08:00
},
start() {
return DateTime.fromISO(this.config.census.start).toLocal();
},
end() {
return DateTime.fromISO(this.config.census.end).toLocal();
},
open() {
const now = DateTime.utc().setZone(this.config.format.timezone);
return now >= this.start && now <= this.end;
},
2020-12-18 02:34:58 -08:00
},
watch: {
async q() {
if (this.q === this.questions.length) {
await this.$post(`/census/submit`, {
2020-12-18 02:34:58 -08:00
answers: JSON.stringify(this.answers),
writins: JSON.stringify(this.writins),
});
this.finished = true;
window.localStorage.setItem('census-finished', '1');
2020-12-18 02:34:58 -08:00
}
2021-01-19 12:04:22 -08:00
this.$nextTick(() => {
if (this.$refs.questionform) {
this.$refs.questionform.querySelector('input,textarea').focus();
}
});
2020-12-18 02:34:58 -08:00
},
},
async beforeRouteLeave (to, from, next) {
if (this.q !== null && this.q < this.questions.length) {
try {
await this.$confirm(this.$t('census.leave'));
} catch {
next(false);
return;
}
}
next();
},
2020-12-18 02:34:58 -08:00
head() {
return head({
title: this.$t('census.headerLong'),
2021-12-23 08:56:27 -08:00
description: this.$t('census.description')[0],
2020-12-18 02:34:58 -08:00
});
},
};
</script>
2021-01-02 04:58:17 -08:00
<style lang="scss">
@import "../assets/style";
.multi-column {
columns: 2;
}
@include media-breakpoint-up('md', $grid-breakpoints) {
.multi-column {
columns: 3;
}
}
</style>