update (end of the main panel feature I think)

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

View file

@ -1,15 +1,27 @@
<template>
<div>
<div class="mb-2">
<p class="text-body">
<div class="mb-3 pb-1">
<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.
</p>
</v-alert>
</div>
<v-text-field
prepend-icon="person"
label="Personne responsable"
outlined
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
prepend-icon="call"
label="Numéro de téléphone"
@ -45,7 +57,22 @@
<script>
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: {}
}
</script>

View file

@ -1,5 +1,281 @@
<template>
<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>
</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>
<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-avatar size="200" style="border: 2px solid #95a5a6">
<img :src="$store.state.data.thumbnail.location" />
</v-avatar>
</v-col>
<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-row>
<v-row style="align-items: center;">
<v-col cols="12" sm="12" md="12">
<v-text-field
label="Nom de l'association"
outlined
prepend-icon="label"
v-model="$store.state.data.name" />
</v-col>
</v-row>
<v-text-field
label="Nom de l'association"
prepend-icon="tag"
outlined
v-model="$store.state.data.name" />
<v-select
label="Catégorie de l'association"
outlined
prepend-icon="category"
item-text="name"
item-value="_id"
v-model="$store.state.data.tag"
:items="$store.state.tags">
</v-select>
<v-textarea
outlined
prepend-icon="description"
label="Description ou résumé rapide"
placeholder="Courte description qui apparaitra sur la page d'accueil"
:rules="rules.descriptionShort"

View file

@ -8,10 +8,15 @@
</div>
<div
v-if="$store.state.data.pricing.length === 0"
class="mb-2 mt-2 d-flex justify-center">
<span class="text-gray">
Pas de tarifs ajoutés
</span>
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>
<v-row v-else>
<v-col
@ -40,16 +45,18 @@
<v-btn
@click="openDeleteModal(pricing)"
icon
outlined
color="error"
>
<v-icon>delete</v-icon>
<v-icon small>delete</v-icon>
</v-btn>
<v-btn
@click="openEditModal(pricing)"
icon
outlined
color="info"
>
<v-icon>edit</v-icon>
<v-icon small>edit</v-icon>
</v-btn>
</v-card-actions>
</v-card>

View file

@ -1,9 +1,27 @@
<template>
<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
class="schedule-item"
v-for="item in scheduleData"
v-for="item in $store.state.data.schedule"
:key="item.name"
>
<!--
@ -12,27 +30,222 @@
md="6"
lg="6"
-->
<v-card>
<v-card-title>
<div class="subheading font-weight-bold">{{ item.name }}</div>
<div class="text-subtitle-2">{{ item.description }}</div>
</v-card-title>
<v-card class="mt-4">
<div class="schedule-content">
<v-card-title class="flex-column justify-space-between">
<div class="schedule-title-container">
<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>
<v-list dense>
<v-list-item
v-for="when in item.when"
:key="when.day">
<v-list-item-content>{{ when.day }}</v-list-item-content>
<v-list-item-content class="align-end">
{{ when.from }} - {{ when.to }}
</v-list-item-content>
</v-list-item>
</v-list>
<div class="d-flex align-center schedule-days">
<v-list
v-if="Array.isArray(item.when) && item.when.length > 0"
dense
class="schedule-list">
<v-divider />
<div
v-for="when in item.when"
:key="when.day">
<v-list-item>
<v-list-item-content>
<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>
</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>
</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>
<style scoped>
.schedule-item {
width: 25em;
<style>
.schedule-content {
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>