Commit 45b6579a authored by Vincent A's avatar Vincent A 🌱

Let users create ideas from topic

parent b19614db
......@@ -10978,6 +10978,11 @@
}
}
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
......
......@@ -2,12 +2,14 @@ import service from './service'
import { getSchools } from './school'
import * as user from './user'
import * as topic from './topic'
import * as ideaSpace from './ideaSpace'
import comment from './comment'
export default {
service,
topic,
getSchools,
ideaSpace,
user,
comment
}
<template>
<v-slide-y-transition mode="out-in">
<v-container fluid grid-list-md>
<v-layout row wrap align-center>
<v-flex md8 xs12 offset-md2 color="primary">
<span>
In
<span v-if="this.idea.topic != null">
{{ this.getPhaseName() }} für Thema {{ idea.topic.title }}
</span>
<span v-else>
<router-link :to="{ name: 'Ideas', params: { spaceSlug:$route.params['spaceSlug']} }">
{{ this.getPhaseName() }}
</router-link>
</span>
<v-layout row wrap align-center>
<v-flex md8 xs12 offset-md2 color="primary">
<span>
In
<span v-if="this.idea.topic != null">
{{ this.getPhaseName() }} für Thema
<router-link
:to="{
name: 'Topic',
params: {
spaceSlug: $route.params['spaceSlug'],
topicId: this.idea.topic.id
}
}"
>{{ idea.topic.title }}</router-link>
</span>
<h1 v-if="idea">{{ idea.title }}</h1>
<p v-if="idea.created_by != null">
{{
$vuetify.t('$vuetify.Idea.authorCreated',
idea.created_by.first_name,
created.toLocaleString()
)
}}
</p>
<p v-if='quorum != null && votes != null'>
{{ $vuetify.t('$vuetify.Idea.supporterCount',
proVotes.length,
quorum.requiredVoteCount
) }}
</p>
<p>{{ idea.description }}</p>
<p v-if="idea.category != null">
{{ $vuetify.t('$vuetify.Idea.category', idea.category.name) }}
</p>
<p v-else>{{ $vuetify.t('$vuetify.Idea.noCategory') }}</p>
<div>
<v-btn-toggle v-model="voteValue" @change="voteChanged">
<v-btn primary>
<v-icon left>thumb_up</v-icon>
Unterstützen
</v-btn>
</v-btn-toggle>
</div>
<Comments :disabled="!allowCommenting"/>
<div>
<h3 v-if="comments != null">
{{ $vuetify.t('$vuetify.Idea.suggestions', comments.length) }}
</h3>
<ul>
<li v-for="comment in comments">
<p>
<strong>{{ comment.created_by.first_name }}</strong>
{{ comment.text }}
</p>
</li>
</ul>
</div>
</v-flex>
<span v-else>
<router-link
:to="{ name: 'Ideas', params: { spaceSlug:$route.params['spaceSlug']} }"
>{{ this.getPhaseName() }}</router-link>
</span>
</span>
<h1 v-if="idea">{{ idea.title }}</h1>
<p v-if="idea.created_by != null">
{{
$vuetify.t('$vuetify.Idea.authorCreated',
idea.created_by.first_name,
created.toLocaleString()
)
}}
</p>
<p v-if="quorum != null && votes != null">
{{ $vuetify.t('$vuetify.Idea.supporterCount',
proVotes.length,
quorum.requiredVoteCount
) }}
</p>
<p>{{ idea.description }}</p>
<p
v-if="idea.category != null"
>{{ $vuetify.t('$vuetify.Idea.category', idea.category.name) }}</p>
<p v-else>{{ $vuetify.t('$vuetify.Idea.noCategory') }}</p>
<div>
<v-btn-toggle v-model="voteValue" @change="voteChanged">
<v-btn primary>
<v-icon left>thumb_up</v-icon>Unterstützen
</v-btn>
</v-btn-toggle>
</div>
<Comments :disabled="!allowCommenting"/>
<div>
<h3
v-if="comments != null"
>{{ $vuetify.t('$vuetify.Idea.suggestions', comments.length) }}</h3>
<ul>
<li v-for="comment in comments">
<p>
<strong>{{ comment.created_by.first_name }}</strong>
{{ comment.text }}
</p>
</li>
</ul>
</div>
</v-flex>
</v-layout>
</v-container>
</v-slide-y-transition>
......@@ -86,10 +94,6 @@ export default {
voteValue: null
}),
props: {
},
beforeMount: function () {
this.getIdea()
},
......
......@@ -3,51 +3,62 @@
<v-container fluid grid-list-md>
<v-layout row wrap align-center>
<v-flex md8 offset-md2 xs12>
<h1>{{ $vuetify.t('$vuetify.IdeaCreation.title') }}</h1>
<h1 v-if="this.topic != null">{{ $vuetify.t('$vuetify.IdeaCreation.titleWithTopic', this.topic.title) }}</h1>
<h1 v-else>{{ $vuetify.t('$vuetify.IdeaCreation.title') }}</h1>
</v-flex>
<v-flex md8 offset-md2 xs12>
<v-text-field
name='title'
name="title"
:label="$vuetify.t('$vuetify.IdeaCreation.name')"
:hint="$vuetify.t('$vuetify.IdeaCreation.nameExample')"
v-model="title"
v-validate="'required|max:160'"
:error-messages="errors.collect('title')"
required
></v-text-field>
></v-text-field>
</v-flex>
<v-flex md8 offset-md2 xs12>
<v-textarea
name='suggestion'
:label="$vuetify.t('$vuetify.IdeaCreation.suggestion')"
:hint="$vuetify.t('$vuetify.IdeaCreation.suggestionDescription')"
v-model="description"
<v-textarea
name="suggestion"
:label="$vuetify.t('$vuetify.IdeaCreation.suggestion')"
:hint="$vuetify.t('$vuetify.IdeaCreation.suggestionDescription')"
v-model="description"
></v-textarea>
</v-flex>
<v-flex xs12 md8 offset-md2 pa-2 align-center justify-center text-md-center text-xs-center>
<v-btn @click="submitIdea" round color="primary" dark>{{ $vuetify.t('$vuetify.IdeaCreation.publish') }}</v-btn>
<v-flex xs12 md8 offset-md2 pa-2 align-center justify-center text-md-center text-xs-center>
<v-alert error :value="!topicMayReceiveIdeas">
{{ $vuetify.t('$vuetify.Topic.cantCreateIdea') }}
</v-alert>
<v-btn
@click="submitIdea"
:disabled="!topicMayReceiveIdeas"
round
color="primary"
dark
>{{ $vuetify.t('$vuetify.IdeaCreation.publish') }}</v-btn>
<v-btn
v-if="this.topic != null"
href
:to="{name: 'Topic', params: {topicId: this.topicId}}"
round
dark
>{{ $vuetify.t('$vuetify.IdeaCreation.backToTopic') }}</v-btn>
</v-flex>
</v-layout>
<v-snackbar
v-model="showSnackbar"
:bottom="true"
>
<v-snackbar v-model="showSnackbar" :bottom="true">
{{ snackbarMsg }}
<v-btn
color="accent"
flat
@click="showSnackbar = false"
>
{{ $vuetify.t('$vuetify.Snackbar.close') }}
</v-btn>
>{{ $vuetify.t('$vuetify.Snackbar.close') }}</v-btn>
</v-snackbar>
</v-container>
</v-slide-y-transition>
</template>
<script>
import * as api from '@/api/ideaSpace'
import api from '@/api'
import { isUserMemberOf } from '../utils/permissions'
export default {
......@@ -60,6 +71,7 @@ export default {
tab: 0,
showSnackbar: false,
snackbarMsg: '',
topic: null,
dictionary: {
custom: {
title: {
......@@ -70,19 +82,38 @@ export default {
}
}),
computed: {
topicId: function () {
return this.$route.params.topicId
},
userMayCreateIdeas: function () {
return !isUserMemberOf(['school_admin', 'principal'])
},
topicMayReceiveIdeas: function () {
return this.topicId == null || (this.topic && this.topic.phase === 'edit_topics')
}
},
props: {
spaceSlug: ''
},
beforeMount: function () {
if (this.topicId != null) {
api.topic.get(this.topicId)
.then(res => {
this.topic = res.data[0]
})
}
},
mounted () {
this.$validator.localize('en', this.dictionary)
},
methods: {
userMayCreateIdeas: function () {
return !isUserMemberOf(['school_admin', 'principal'])
},
submitIdea: function () {
debugger
this.$validator.validate()
.then(isFormValid => {
// Do nothing if validation fails - errors are displayed in UI
......@@ -92,13 +123,13 @@ export default {
return
}
if (!this.userMayCreateIdeas()) {
if (!this.userMayCreateIdeas) {
this.showSnackbar = true
this.snackbarMsg = this.$vuetify.t('$vueetify.Snackbar.rightsError')
this.snackbarMsg = this.$vuetify.t('$vuetify.Snackbar.rightsError')
return
}
const newIdea = {
let newIdea = {
title: this.title,
description: this.description,
school_id: this.$store.getters.selected_school,
......@@ -107,7 +138,13 @@ export default {
idea_space: this.$route.params['spaceId']
}
api.createIdea(newIdea)
if (this.topic != null) {
newIdea = Object.assign({}, newIdea, {
topic: this.topic.id
})
}
api.ideaSpace.createIdea(newIdea)
.then((res) => {
if (res.status < 400 && res.data.length > 0) {
const spaceSlug = this.$route.params['spaceSlug']
......
......@@ -108,6 +108,19 @@
</v-card-text>
</v-card>
</v-flex>
<v-btn color="primary" fixed dark fab bottom right icon href
v-if="this.topic == null || this.topic.phase === 'edit_topics'"
:to="{
name: 'IdeaCreate',
params: {
space: this.$route.params.spaceSlug,
topicId: this.topic && this.topic.id
}
}"
>
<v-icon>add</v-icon>
</v-btn>
</v-layout>
</template>
......
<template>
<v-container pa-0>
<v-layout row wrap>
<v-flex xs12 class='phase-banner' px-3 py-2>
<h2>
{{ $vuetify.t('$vuetify.TopicPhase.' + topic.phase) }}
</h2>
<v-flex xs12 class="phase-banner" px-3 py-2>
<h2>{{ $vuetify.t('$vuetify.TopicPhase.' + topic.phase) }}</h2>
</v-flex>
<v-flex xs12 class='phase-notification' px-3 py-2>
<p>
<v-icon>timer</v-icon> Endet in 11 Stunden
</p>
<v-flex xs12 class="phase-notification" px-3 py-1 v-if="timeLeft != null">
<v-icon>timer</v-icon>
{{ $vuetify.t('$vuetify.Topic.phaseTimeLeft', timeLeft) }}
</v-flex>
<v-flex xs12 class="topic-wrapper">
<v-container px-0 pb-3>
......@@ -23,9 +20,8 @@
</v-layout>
</v-container>
</v-flex>
<v-flex>
<IdeaListing :ideas="ideas" :topic="topic"/>
<IdeaListing :ideas="ideas" :topic="topic" />
</v-flex>
</v-layout>
</v-container>
......@@ -36,13 +32,24 @@
import api from '@/api'
import IdeaListing from '@/components/IdeaListing'
import { isUserMemberOf } from '../utils/permissions'
import moment from 'moment'
export default {
name: 'Topic',
components: { IdeaListing },
computed: {
spaceId: function () { return this.$route.params['spaceId'] },
topicId: function () { return this.$route.params['topicId'] }
topicId: function () { return this.$route.params['topicId'] },
timeLeft: function () {
const phaseDuration = [1, 'day']
if (this.topic == null || this.topic.phase !== 'edit_topics') {
return null
} else {
return moment(this.topic.meta.editTopicsStarted)
.add(...phaseDuration)
.calendar()
}
}
},
data: function () {
......@@ -56,6 +63,9 @@ export default {
beforeMount: function () {
this.getTopic()
this.getIdeas()
// Set momeent locale to be the same as configured for vuetify
moment.locale(this.$vuetify.lang.current)
},
methods: {
......@@ -70,6 +80,7 @@ export default {
api.topic.get(this.topicId)
.then((res) => {
this.topic = res.data[0]
this.topic.meta = { editTopicsStarted: new Date() }
})
},
getIdeas: function () {
......@@ -96,16 +107,22 @@ export default {
text-transform: uppercase;
background-color: var(--v-secondary-base);
color: white;
font-size: .8em;
font-size: 0.8em;
}
.phase-notification {
text-transform: uppercase;
color: var(--v-secondary-base);
background-color: white;
font-size: 0.9em;
font-weight: bold;
.v-icon { color: var(--v-secondary-base); }
.v-icon {
color: var(--v-secondary-base);
}
p { margin: 0; }
p {
margin: 0;
}
}
</style>
......@@ -68,8 +68,10 @@ export default {
'tab.delegates': 'Beauftrage Stimmen',
'back.phase.limit': "Thema {0} konnte nicht zurückgesetzt werden: steht schon auf 'Ausarbeitung'",
'phase.change': 'Thema {0} wurde von {1} nach {2} verschoben.',
'phase.time': '{0} (Endet morgen am {1} um ca. {2} Uhr)',
'your.delegate': 'Derzeit stimmt für dich ab: {0}'
'phaseTimeLeft': 'Endet {0}',
'your.delegate': 'Derzeit stimmt für dich ab: {0}',
'cantCreateIdea': 'Diesem Thema können keine Ideen hinzugefügt werden.',
'notFound': 'Dieses Thema wurde leider nicht gefunden.'
},
TopicPhase: {
'wildIdeas': 'Wilde-Ideen-Phase',
......@@ -92,6 +94,7 @@ export default {
},
IdeaCreation: {
'title': 'Deine Idee',
'titleWithTopic': 'Deine Idee für das Thema "{0}"',
'name': 'Wie soll deine Idee heißen?',
'nameExample': 'z.B. bessere Ausstatung im Computerraum',
'suggestion': 'Was möchtest du vorschlagen?',
......@@ -99,6 +102,7 @@ export default {
'previewShow': 'Vorschau einblenden',
'selectCategory': 'Kann deine Idee einer der folgenden Kategorieren zugeordnet werden?',
'publish': 'Idee veröffentlichen',
'backToTopic': 'Zurück zum Thema',
'yourDelegates': 'Derzeit Stimmt für dich ab:'
},
UserProfile: {
......
......@@ -29,7 +29,7 @@ const router = new Router({
meta: { auth: true }
},
{
path: '/space/:spaceSlug/idea/create',
path: '/space/:spaceSlug/idea/create/:topicId?',
name: 'IdeaCreate',
component: IdeaEdit,
meta: { auth: true }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment