update (end of the main panel feature I think)

This commit is contained in:
Matthieu Bessat 2020-07-15 15:21:53 +02:00
parent 4791bd8037
commit 79a24f6ae7
15 changed files with 814 additions and 71 deletions

View file

@ -15,6 +15,7 @@
"vue-apitator": "^0.0.16", "vue-apitator": "^0.0.16",
"vue-class-component": "^7.2.3", "vue-class-component": "^7.2.3",
"vue-croppa": "^1.3.8", "vue-croppa": "^1.3.8",
"vue-input-facade": "^1.3.1",
"vue-property-decorator": "^8.4.2", "vue-property-decorator": "^8.4.2",
"vue-router": "^3.2.0", "vue-router": "^3.2.0",
"vuetify": "^2.2.11", "vuetify": "^2.2.11",

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Forum virtuel des associaitons</title> <title>Forum virtuel des associations</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">

View file

@ -1,7 +1,7 @@
<template> <template>
<v-dialog <v-dialog
v-model="enabled" v-model="enabled"
max-width="500px"> :max-width="modalSize">
<v-card> <v-card>
<v-card-title> <v-card-title>
{{ caption }} {{ caption }}
@ -13,8 +13,8 @@
v-model="plugin" v-model="plugin"
:show-remove-button="false" :show-remove-button="false"
canvas-color="white" canvas-color="white"
:width="size" :width="width"
:height="size" :height="height"
prevent-white-space prevent-white-space
:placeholder-font-size="22" :placeholder-font-size="22"
@file-size-exceed="handleFileSizeExceed" @file-size-exceed="handleFileSizeExceed"
@ -22,27 +22,29 @@
placeholder="Choisissez une image" placeholder="Choisissez une image"
></croppa> ></croppa>
</v-layout> </v-layout>
<v-layout justify-center wrap> <v-layout justify-center>
<v-btn <v-btn
class="mr-2"
dark dark
outlined outlined
color="indigo" color="indigo"
@click="rotate()"> @click="rotate()">
Tourner 90° <v-icon>rotate_right</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
class="mr-2"
dark dark
outlined outlined
color="indigo" color="indigo"
@click="rotate(-1)"> @click="rotate(-1)">
Tourner -90° <v-icon>rotate_left</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
dark dark
outlined outlined
color="purple" color="purple"
@click="clear"> @click="clear">
Supprimer <v-icon>clear</v-icon>
</v-btn> </v-btn>
</v-layout> </v-layout>
</v-card-text> </v-card-text>
@ -72,14 +74,25 @@ export default {
name: 'AvatarEditor', name: 'AvatarEditor',
data: () => ({ data: () => ({
enabled: false, enabled: false,
plugin: {}, plugin: {}
size: 400
}), }),
props: { props: {
caption: { caption: {
type: String, type: String,
default: 'Modifier votre logo' default: 'Modifier votre logo'
}, },
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 400
},
modalSize: {
type: Number,
default: 500
},
loading: { loading: {
type: Boolean, type: Boolean,
default: false default: false
@ -130,7 +143,6 @@ export default {
} }
.canvas-container canvas { .canvas-container canvas {
border: 1px solid #bdc3c7; border: 1px solid #bdc3c7;
border-radius: 50%;
} }
@media screen and (max-width: 959px) { @media screen and (max-width: 959px) {
.canvas-container canvas { .canvas-container canvas {

View file

@ -14,4 +14,4 @@
d="M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z"
/> />
</svg> </svg>
</template> </template>

View file

@ -14,4 +14,4 @@
d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z" d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"
/> />
</svg> </svg>
</template> </template>

View file

@ -14,4 +14,4 @@
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
/> />
</svg> </svg>
</template> </template>

View file

@ -6,9 +6,9 @@
dark dark
flat flat
> >
<v-toolbar-title>Gestion de l'association We Robot</v-toolbar-title> <v-toolbar-title>{{ $store.state.delegateAdminName }}</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn outlined> <v-btn outlined class="mr-3">
Publier Publier
</v-btn> </v-btn>
<v-btn icon @click="logout()"> <v-btn icon @click="logout()">
@ -45,7 +45,7 @@
</v-toolbar> </v-toolbar>
<v-container fluid> <v-container fluid>
<v-row class="justify-center"> <v-row class="justify-center">
<v-col cols="12" sm="12" md="8"> <v-col cols="12" sm="12" md="8" lg="7" xl="5">
<router-view></router-view> <router-view></router-view>
<div class="mt-3 d-flex justify-end"> <div class="mt-3 d-flex justify-end">
<v-btn color="success" :loading="isSaving" @click="save()">Sauvegarder</v-btn> <v-btn color="success" :loading="isSaving" @click="save()">Sauvegarder</v-btn>
@ -66,7 +66,7 @@
<p> <p>
Vous n'êtes pas encore connecté à l'interface de modification de votre association, veuillez copier-coller la clée qui vous a été envoyé par e-mail dans la boîte ci-dessous. Vous n'êtes pas encore connecté à l'interface de modification de votre association, veuillez copier-coller la clée qui vous a été envoyé par e-mail dans la boîte ci-dessous.
</p> </p>
<v-text-field <v-text-field
v-on:keydown.enter="submitForm()" v-on:keydown.enter="submitForm()"
autofocus autofocus
label="Clée" label="Clée"
@ -99,11 +99,13 @@ export default {
created () { created () {
this.init() this.init()
}, },
watch: {
$route (to, from) {
this.selectRoute(to)
}
},
mounted () { mounted () {
const path = this.$route.path.split('/') this.selectRoute(this.$route)
const name = path[path.length - 1]
const routes = ['', 'gallery', 'presentation', 'schedule', 'pricing', 'contact']
this.tab = routes.indexOf(name)
/** /**
* this is very ugly I kown * this is very ugly I kown
*/ */
@ -121,6 +123,12 @@ export default {
// }, 3000) // }, 3000)
}, },
methods: { methods: {
selectRoute (route) {
const path = route.path.split('/')
const name = path[path.length - 1]
const routes = ['', 'gallery', 'presentation', 'schedule', 'pricing', 'contact']
this.tab = routes.indexOf(name)
},
init () { init () {
this.enabled = false this.enabled = false
@ -154,8 +162,15 @@ export default {
clearTimeout(this.loadingHandle) clearTimeout(this.loadingHandle)
this.loading = false this.loading = false
this.enabled = true this.enabled = true
const organization = res.data.data const organization = res.data.data.organization
const tags = res.data.data.tags
if (organization.proposedVersion.tag !== undefined && organization.proposedVersion.tag !== null) {
organization.proposedVersion.tag = organization.proposedVersion.tag._id
}
this.$store.commit('SET_DELEGATE_ADMIN_NAME', organization.adminName)
this.$store.commit('SET_DELEGATE_EMAIL', organization.email)
this.$store.commit('SET_DATA', organization.proposedVersion) this.$store.commit('SET_DATA', organization.proposedVersion)
this.$store.commit('SET_TAGS', tags)
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(this.$refs.tabs.callSlider, 200) setTimeout(this.$refs.tabs.callSlider, 200)
setTimeout(this.$refs.tabs.callSlider, 400) setTimeout(this.$refs.tabs.callSlider, 400)
@ -185,6 +200,16 @@ export default {
delete i._id delete i._id
return i return i
}) })
data.schedule = data.schedule.map(i => {
delete i._id
if (Array.isArray(i.when) && i.when.length > 0) {
i.when = i.when.map(w => {
delete w._id
return w
})
}
return i
})
this.$apitator.put('/delegate', data, { withAuth: true }).then(() => { this.$apitator.put('/delegate', data, { withAuth: true }).then(() => {
this.isSaving = false this.isSaving = false
this.$store.commit('ADD_ALERT', { this.$store.commit('ADD_ALERT', {

View file

@ -21,7 +21,6 @@ const routes: Array<RouteConfig> = [
{ {
path: '/admin', path: '/admin',
name: 'AdminLayout',
component: () => import(/* webpackChunkName: "adminLayout" */ '../layouts/Admin.vue'), component: () => import(/* webpackChunkName: "adminLayout" */ '../layouts/Admin.vue'),
children: [ children: [
{ {
@ -39,7 +38,6 @@ const routes: Array<RouteConfig> = [
{ {
path: '/delegate', path: '/delegate',
name: 'DelegateLayout',
component: () => import(/* webpackChunkName: "delegateLayout" */ '../layouts/Delegate.vue'), component: () => import(/* webpackChunkName: "delegateLayout" */ '../layouts/Delegate.vue'),
children: [ children: [
{ {

View file

@ -11,6 +11,7 @@ export default new Vuex.Store({
enabled: false enabled: false
}, },
title: '', title: '',
tags: [],
data: { data: {
name: '', name: '',
descriptionShort: '', descriptionShort: '',
@ -31,7 +32,9 @@ export default new Vuex.Store({
email: '', email: '',
phone: '' phone: ''
} }
} },
delegateAdminName: '',
delegateEmail: ''
}, },
mutations: { mutations: {
SET_TITLE (state, payload) { SET_TITLE (state, payload) {
@ -51,6 +54,15 @@ export default new Vuex.Store({
if (payload !== null) { if (payload !== null) {
state.data = { ...state.data, ...payload } state.data = { ...state.data, ...payload }
} }
},
SET_TAGS (state, payload) {
state.tags = payload
},
SET_DELEGATE_ADMIN_NAME (state, payload) {
state.delegateAdminName = payload
},
SET_DELEGATE_EMAIL (state, payload) {
state.delegateEmail = payload
} }
}, },
actions: { actions: {

View file

@ -1,15 +1,27 @@
<template> <template>
<div> <div>
<div class="mb-2"> <div class="mb-3 pb-1">
<p class="text-body"> <v-alert
border="left"
colored-border
type="info"
elevation="1"
>
Ici vous pouvez préciser quelques manières de contacter votre association, aucun des champs indiqués n'est obligatoire. Ici vous pouvez préciser quelques manières de contacter votre association, aucun des champs indiqués n'est obligatoire.
</p> </v-alert>
</div> </div>
<v-text-field <v-text-field
prepend-icon="person" prepend-icon="person"
label="Personne responsable" label="Personne responsable"
outlined outlined
v-model="$store.state.data.contacts.person" /> v-model="$store.state.data.contacts.person" />
<v-text-field
prepend-icon="alternate_email"
label="Email"
:rules="rules.email"
outlined
v-model="$store.state.data.contacts.email" />
<v-text-field <v-text-field
prepend-icon="call" prepend-icon="call"
label="Numéro de téléphone" label="Numéro de téléphone"
@ -45,7 +57,22 @@
<script> <script>
export default { export default {
data: () => ({}), data: () => ({
rules: {
email: [
v => /.+@.+\..+/.test(v) || "L'email doit être valide"
],
facebook: [
v => /(http(s)?:\/\/)?(www\.)?facebook\.com\/(\S+){3,}/.test(v) || 'Ce champs doit être une url facebook valide'
],
twitter: [
v => /(http(s)?:\/\/)?(www\.)?twitter\.com\/(\S+){3,}/.test(v) || 'Ce champs doit être une url twitter valide'
],
instagram: [
v => /(http(s)?:\/\/)?(www\.)?instagram\.com\/(\S+){3,}/.test(v) || 'Ce champs doit être une url instagram valide'
]
}
}),
methods: {} methods: {}
} }
</script> </script>

View file

@ -1,5 +1,281 @@
<template> <template>
<div> <div>
Gallery <!-- COVER START -->
<div
class="cover-common cover-container"
:style="coverStyle">
</div>
<div class="cover-common cover-upper">
<v-btn
@click="$refs.avatarEditor.toggle()"
color="white"
>Changer la couverture</v-btn>
</div>
<AvatarEditor
ref="avatarEditor"
:loading="coverLoading"
caption="Modifier votre couverture"
:width="960"
:height="300"
:modal-size="1000"
@submitted="handleAvatarEditorSubmitted"
/>
<!-- COVER END -->
<div class="d-flex justify-end mt-3 mb-3">
<v-btn color="primary" outlined @click="addMediaModal = true">
<v-icon left>add</v-icon>
Ajouter une image
</v-btn>
</div>
<div
v-if="$store.state.data.gallery.length === 0"
class="mb-8 mt-8">
<v-alert
border="left"
color="grey darken-2"
outlined
type="info"
>
Aucun medias n'ont été ajoutés pour l'instant
</v-alert>
</div>
<v-row v-else>
<v-col
v-for="(media, index) in $store.state.data.gallery"
:key="media._id"
cols="12"
xs="12"
sm="6"
md="6"
lg="4"
>
<v-card>
<v-img
:src="media.location"
height="200"
class="grey darken-4"
></v-img>
<v-card-actions>
<v-btn
icon
color="gray"
small
:disabled="index === 0"
@click="shiftLeftMedia(media)"
>
<v-icon>keyboard_arrow_left</v-icon>
</v-btn>
<v-btn
icon
color="gray"
small
:disabled="index + 1 === $store.state.data.gallery.length"
@click="shiftRightMedia(media)"
>
<v-icon>keyboard_arrow_right</v-icon>
</v-btn>
<v-spacer />
<v-btn icon color="error" small @click="openDeleteMediaModal(media)">
<v-icon small>delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-dialog v-model="addMediaModal" max-width="600px">
<v-card>
<v-card-title>
Ajouter un ou des médias dans la gallerie
</v-card-title>
<v-card-text>
<div class="mt-2">
<v-file-input
v-model="files"
chips
multiple
show-size
counter
label="Sélectionnez les images ou vidéos à importer">
</v-file-input>
</div>
</v-card-text>
<v-card-actions>
<v-btn text color="primary" @click="addMediaModal = false">
Fermer
</v-btn>
<v-spacer />
<v-btn text color="primary" @click="uploadMedias()" :loading="uploadLoading">
Téléverser
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="deleteMediaModal" max-width="500px">
<v-card>
<v-card-title>Êtes vous sur de vouloir supprimer ce media ?</v-card-title>
<v-card-actions>
<v-btn color="primary" text @click="deleteMediaModal = false">
Fermer
</v-btn>
<v-btn color="error" text @click="deleteMedia()">
Supprimer
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
<script>
import AvatarEditor from '../../components/AvatarEditor'
export default {
components: { AvatarEditor },
data: () => ({
coverLoading: false,
addMediaModal: false,
uploadLoading: false,
files: [],
deleteMediaModal: false,
mediaToDelete: {}
}),
computed: {
coverStyle () {
if (this.$store.state.data.cover === undefined || this.$store.state.data.cover === null) {
return {}
}
return { backgroundImage: 'url(' + this.$store.state.data.cover.location + ')' }
}
},
methods: {
handleAvatarEditorSubmitted (blob) {
const form = new FormData()
form.append('file', blob, blob.filename)
this.coverLoading = true
this.$apitator.post('/delegate/cover', form, { withAuth: true }).then(res => {
this.coverLoading = false
this.$store.commit('SET_DATA', { cover: res.data.cover })
this.$refs.avatarEditor.finish()
this.$store.commit('ADD_ALERT', {
color: 'success',
text: 'Couverture mise à jour !'
})
}).catch(() => {
this.$store.commit('ADD_ALERT', {
color: 'error',
text: 'Impossible de mettre à jour la couverture'
})
this.coverLoading = false
})
},
loadFile (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = (event) => {
resolve(['file', event.target.result, file.name])
}
reader.onerror = () => {
console.log(reader.error)
reject(reader.error)
}
})
},
uploadMedias () {
this.uploadLoading = true
const promises = this.files.map(file => this.loadFile(file))
Promise.all(promises).then(results => {
const formData = new FormData()
results.forEach(r => formData.append(r[0], new Blob([new Uint8Array(r[1])]), r[2]))
this.$apitator.post('/delegate/medias', formData, { withAuth: true }).then(res => {
this.$store.commit('SET_DATA', { gallery: res.data.data.gallery })
this.$store.commit('ADD_ALERT', {
color: 'success',
text: 'Les médias ont été téléversés'
})
this.uploadLoading = false
this.addMediaModal = false
this.files = []
}).catch(() => {
this.coverLoading = false
this.$store.commit('ADD_ALERT', {
color: 'error',
text: 'Impossible de téleverser ces fichiers'
})
})
}).catch(() => {
this.$store.commit('ADD_ALERT', {
color: 'error',
text: 'Erreur de chargement des fichiers'
})
})
},
openDeleteMediaModal (media) {
this.deleteMediaModal = true
this.mediaToDelete = media
},
deleteMedia () {
this.deleteMediaModal = false
let gallery = this.$store.state.data.gallery
gallery = gallery.filter(media => this.mediaToDelete._id !== media._id)
this.$store.commit('SET_DATA', { gallery })
},
shiftLeftMedia (media) {
const originalGallery = this.$store.state.data.gallery
const index = originalGallery.indexOf(media)
if (index === 0) {
return
}
const gallery = originalGallery.map((m, i) => {
if (i === index - 1) {
return originalGallery[index]
}
if (i === index) {
return originalGallery[index - 1]
}
return m
})
this.$store.commit('SET_DATA', { gallery })
},
shiftRightMedia (media) {
const originalGallery = this.$store.state.data.gallery
const index = originalGallery.indexOf(media)
if (index === originalGallery.length - 1) {
return
}
const gallery = originalGallery.map((m, i) => {
if (i === index + 1) {
return originalGallery[index]
}
if (i === index) {
return originalGallery[index + 1]
}
return m
})
this.$store.commit('SET_DATA', { gallery })
}
}
}
</script>
<style scoped>
.cover-common {
height: 16em;
border-radius: .5em;
}
.cover-container {
background-size: cover;
}
.cover-upper {
display: flex;
justify-content: center;
align-items: center;
margin-top: -16em;
background-color: rgba(41, 128, 185, 0.4);
}
</style>

View file

@ -1,28 +1,37 @@
<template> <template>
<div> <div>
<v-row class="mb-5"> <v-row class="mb-6 pb-2">
<v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;"> <v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;">
<v-avatar size="200" style="border: 2px solid #95a5a6"> <v-avatar size="200" style="border: 2px solid #95a5a6">
<img :src="$store.state.data.thumbnail.location" /> <img :src="$store.state.data.thumbnail.location" />
</v-avatar> </v-avatar>
</v-col> </v-col>
<v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;"> <v-col cols="12" sm="12" md="6" style="align-items: center; justify-content: center; display: flex;">
<v-btn @click="$refs.avatarEditor.toggle()" color="primary">Changer le logo</v-btn> <v-btn @click="$refs.avatarEditor.toggle()" color="primary" outlined>
Changer le logo
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<v-row style="align-items: center;"> <v-text-field
<v-col cols="12" sm="12" md="12"> label="Nom de l'association"
<v-text-field prepend-icon="tag"
label="Nom de l'association" outlined
outlined v-model="$store.state.data.name" />
prepend-icon="label"
v-model="$store.state.data.name" /> <v-select
</v-col> label="Catégorie de l'association"
</v-row> outlined
prepend-icon="category"
item-text="name"
item-value="_id"
v-model="$store.state.data.tag"
:items="$store.state.tags">
</v-select>
<v-textarea <v-textarea
outlined outlined
prepend-icon="description"
label="Description ou résumé rapide" label="Description ou résumé rapide"
placeholder="Courte description qui apparaitra sur la page d'accueil" placeholder="Courte description qui apparaitra sur la page d'accueil"
:rules="rules.descriptionShort" :rules="rules.descriptionShort"

View file

@ -8,10 +8,15 @@
</div> </div>
<div <div
v-if="$store.state.data.pricing.length === 0" v-if="$store.state.data.pricing.length === 0"
class="mb-2 mt-2 d-flex justify-center"> class="mb-8 mt-8">
<span class="text-gray"> <v-alert
Pas de tarifs ajoutés border="left"
</span> color="grey darken-2"
outlined
type="info"
>
Aucune catégorie horaires ajoutées pour le moment
</v-alert>
</div> </div>
<v-row v-else> <v-row v-else>
<v-col <v-col
@ -40,16 +45,18 @@
<v-btn <v-btn
@click="openDeleteModal(pricing)" @click="openDeleteModal(pricing)"
icon icon
outlined
color="error" color="error"
> >
<v-icon>delete</v-icon> <v-icon small>delete</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn
@click="openEditModal(pricing)" @click="openEditModal(pricing)"
icon icon
outlined
color="info" color="info"
> >
<v-icon>edit</v-icon> <v-icon small>edit</v-icon>
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View file

@ -1,9 +1,27 @@
<template> <template>
<div> <div>
<div class="d-flex"> <div class="d-flex justify-end">
<v-btn @click="openAddCategoryModal()" color="teal" outlined>
<v-icon left>add</v-icon>
Ajouter une catégorie
</v-btn>
</div>
<div
v-if="$store.state.data.schedule.length === 0"
class="mb-8 mt-8">
<v-alert
border="left"
color="grey darken-2"
outlined
type="info"
>
Aucune catégorie horaires ajoutées pour le moment
</v-alert>
</div>
<div v-else>
<div <div
class="schedule-item" class="schedule-item"
v-for="item in scheduleData" v-for="item in $store.state.data.schedule"
:key="item.name" :key="item.name"
> >
<!-- <!--
@ -12,27 +30,222 @@
md="6" md="6"
lg="6" lg="6"
--> -->
<v-card> <v-card class="mt-4">
<v-card-title> <div class="schedule-content">
<div class="subheading font-weight-bold">{{ item.name }}</div> <v-card-title class="flex-column justify-space-between">
<div class="text-subtitle-2">{{ item.description }}</div> <div class="schedule-title-container">
</v-card-title> <div class="subheading font-weight-bold">{{ item.name }}</div>
<div class="text-body-1">{{ item.description }}</div>
</div>
<div class="mt-5 schedule-actions">
<div class="mr-3">
<v-btn outlined icon small color="info" class="mr-2" @click="openEditCategoryModal(item)">
<v-icon small>edit</v-icon>
</v-btn>
<v-btn outlined icon small color="error" @click="openDeleteCategoryModal(item)">
<v-icon small>delete</v-icon>
</v-btn>
</div>
<v-btn
@click="openAddWhenModal(item)"
outlined
small
color="success">
Ajouter un horaire
</v-btn>
</div>
</v-card-title>
<v-divider></v-divider> <div class="d-flex align-center schedule-days">
<v-list
<v-list dense> v-if="Array.isArray(item.when) && item.when.length > 0"
<v-list-item dense
v-for="when in item.when" class="schedule-list">
:key="when.day"> <v-divider />
<v-list-item-content>{{ when.day }}</v-list-item-content> <div
<v-list-item-content class="align-end"> v-for="when in item.when"
{{ when.from }} - {{ when.to }} :key="when.day">
</v-list-item-content> <v-list-item>
</v-list-item> <v-list-item-content>
</v-list> <div class="d-flex justify-space-between">
<div>{{ when.day }}</div>
<div>{{ when.from }} - {{ when.to }}</div>
</div>
</v-list-item-content>
<v-list-item-action>
<div class="d-flex justify-space-between">
<v-btn icon color="info" @click="openEditWhenModal(when)">
<v-icon small>
edit
</v-icon>
</v-btn>
<v-btn icon color="error" @click="openDeleteWhenModal(when)">
<v-icon small>delete</v-icon>
</v-btn>
</div>
</v-list-item-action>
</v-list-item>
<v-divider />
</div>
</v-list>
<div v-else class="grey--text text--darken-1">
Pas d'interval horaires pour cette catégorie pour l'instant
</div>
</div>
</div>
</v-card> </v-card>
</div> </div>
</div> </div>
<v-dialog
max-width="500px"
v-model="categoryModal">
<v-card>
<v-card-title v-text="modalTitle + ' une catégorie horaire'" />
<v-card-text>
<v-text-field
v-model="category.name"
label="Nom"
hint="Requis"
/>
<v-text-field
v-model="category.description"
label="Description"
hint="Optionel"
/>
</v-card-text>
<v-card-actions>
<v-btn @click="categoryModal = false" text color="primary">
Fermer
</v-btn>
<v-spacer />
<v-btn
@click="saveCategory()"
text
color="success"
:disabled="category.name.length < 3"
>
Valider
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
max-width="500px"
v-model="deleteCategoryModal">
<v-card>
<v-card-title>
Voulez vous vraiment supprimer cette catégorie horaire ?
</v-card-title>
<v-card-actions>
<v-btn @click="deleteCategoryModal = false" text color="primary">
Fermer
</v-btn>
<v-spacer />
<v-btn @click="deleteCategory()" text color="error">
Supprimer
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
max-width="500px"
v-model="whenModal">
<v-card>
<v-card-title v-text="modalTitle + ' un interval horaire'" />
<v-card-text>
<v-select
prepend-icon="event"
v-model="when.day"
:items="days"
label="Sélectionnez un jour"
></v-select>
<v-menu
ref="fromMenu"
v-model="fromMenu"
:close-on-content-click="false"
:return-value.sync="when.from"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="when.from"
label="Heure de début"
prepend-icon="schedule"
readonly
v-bind="attrs"
v-on="on"
></v-text-field>
</template>
<v-time-picker v-model="when.from" format="24hr">
<v-btn text color="primary" @click="fromMenu = false">Fermer</v-btn>
<v-spacer></v-spacer>
<v-btn text color="primary" @click="$refs.fromMenu.save(when.from)">OK</v-btn>
</v-time-picker>
</v-menu>
<v-menu
ref="toMenu"
v-model="toMenu"
:close-on-content-click="false"
:return-value.sync="when.to"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="when.to"
label="Heure de fin"
prepend-icon="schedule"
readonly
v-bind="attrs"
v-on="on"
></v-text-field>
</template>
<v-time-picker v-model="when.to" format="24hr">
<v-btn text color="primary" @click="toMenu = false">Fermer</v-btn>
<v-spacer></v-spacer>
<v-btn
text
color="primary"
@click="$refs.toMenu.save(when.to)">OK</v-btn>
</v-time-picker>
</v-menu>
</v-card-text>
<v-card-actions>
<v-btn @click="categoryModal = false" text color="primary">
Fermer
</v-btn>
<v-spacer />
<v-btn
@click="saveWhen()"
text
color="success"
:disabled="when.day.length < 4 || when.to.length !== 5 || when.from.length !== 5">
Valider
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
max-width="500px"
v-model="deleteWhenModal">
<v-card>
<v-card-title>
Voulez vous vraiment supprimer cette interval horaire ?
</v-card-title>
<v-card-actions>
<v-btn @click="deleteWhenModal = false" text color="primary">
Fermer
</v-btn>
<v-spacer />
<v-btn @click="deleteWhen()" text color="error">
Supprimer
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
@ -103,13 +316,171 @@ export default {
} }
] ]
} }
],
mode: '',
categoryModal: false,
category: {
name: '',
description: ''
},
deleteCategoryModal: false,
toDeleteCategory: {},
oldEditCategory: {},
whenModal: false,
when: {
day: '',
from: '',
to: ''
},
deleteWhenModal: false,
toDeleteWhen: {},
oldEditWhen: {},
fromMenu: false,
toMenu: false,
days: [
'Lundi',
'Mardi',
'Mercredi',
'Jeudi',
'Vendredi',
'Samedi'
] ]
}) }),
computed: {
modalTitle () { return this.mode === 'add' ? 'Ajouter' : 'Editer' }
},
methods: {
openAddCategoryModal () {
this.mode = 'add'
this.categoryModal = true
},
openEditCategoryModal (item) {
this.category = item
this.oldEditCategory = item
this.mode = 'edit'
this.categoryModal = true
},
openDeleteCategoryModal (item) {
this.deleteCategoryModal = true
this.toDeleteCategory = item
},
saveCategory () {
let schedule = this.$store.state.data.schedule
if (this.mode === 'add') {
this.category._id = Date.now().toString()
schedule.push(this.category)
} else {
schedule = schedule.map(i => i.name === this.oldEditCategory.name && i.description === this.oldEditCategory.description ? this.category : i)
}
this.category = {
name: '',
description: ''
}
this.$store.commit('SET_DATA', { schedule })
this.categoryModal = false
},
deleteCategory () {
let schedule = this.$store.state.data.schedule
schedule = schedule.filter(item => item.name !== this.toDeleteCategory.name && item.description !== this.toDeleteCategory.description)
this.$store.commit('SET_DATA', { schedule })
this.deleteCategoryModal = false
},
openAddWhenModal (parent) {
this.mode = 'add'
this.whenParent = parent
this.whenModal = true
},
openEditWhenModal (item) {
this.when = item
this.oldEditWhen = item
this.mode = 'edit'
this.whenModal = true
},
openDeleteWhenModal (item) {
this.deleteWhenModal = true
this.toDeleteWhen = item
},
saveWhen () {
let schedule = this.$store.state.data.schedule
if (this.mode === 'add') {
this.when._id = Date.now().toString()
schedule = schedule.map(i => {
if (this.whenParent._id === i._id) {
if (!Array.isArray(i.when)) {
i.when = []
}
i.when.push(this.when)
}
return i
})
} else {
schedule = schedule.map(i => {
if (this.whenParent._id === i._id) {
i.when = i.when.map(w => w._id === this.when._id ? this.when : w)
}
return i
})
}
this.when = {
day: '',
from: '10:00',
to: '11:00'
}
this.$store.commit('SET_DATA', { schedule })
this.whenModal = false
},
deleteWhen () {
let schedule = this.$store.state.data.schedule
schedule = schedule.map(i => {
if (this.whenParent._id === i._id) {
i.when = i.when.filter(w => w._id !== this.toDeleteWhen._id)
}
return i
})
this.$store.commit('SET_DATA', { schedule })
this.deleteWhenModal = false
}
}
} }
</script> </script>
<style scoped> <style>
.schedule-item { .schedule-content {
width: 25em; display: grid;
grid-template-columns: 1fr 1fr;
}
.schedule-title-container {
width: 100%;
word-break: break-word;
}
.schedule-list {
width: 100%;
}
.schedule-actions {
display: flex;
align-items: center;
width: 100%;
}
.schedule-actions div:first-child {
display: flex;
align-items: center;
}
@media (max-width: 900px) {
.schedule-content {
grid-template-columns: 1fr;
}
.schedule-days {
margin-right: 0;
}
.schedule-actions {
justify-content: space-between;
}
.schedule-actions div:first-child {
margin: 0;
}
} }
</style> </style>

View file

@ -9120,6 +9120,11 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
vue-input-facade@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/vue-input-facade/-/vue-input-facade-1.3.1.tgz#87cdca62f75fcdf8205fc7345e4f6a560f4daaa8"
integrity sha512-8tgWiBObwVT3v9XD9OruwPDdWFwbTxaGdtUd1vXqAwNMZceETxZdfsgkMjUtwT1u2tCY3Ptu09WNDPwiwa4huw==
vue-loader@^15.9.2: vue-loader@^15.9.2:
version "15.9.3" version "15.9.3"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.3.tgz#0de35d9e555d3ed53969516cac5ce25531299dda" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.3.tgz#0de35d9e555d3ed53969516cac5ce25531299dda"