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

@ -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>
@ -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-col cols="12" sm="12" md="12">
<v-text-field <v-text-field
label="Nom de l'association" label="Nom de l'association"
prepend-icon="tag"
outlined outlined
prepend-icon="label"
v-model="$store.state.data.name" /> v-model="$store.state.data.name" />
</v-col>
</v-row> <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 <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">
<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="subheading font-weight-bold">{{ item.name }}</div>
<div class="text-subtitle-2">{{ item.description }}</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-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
class="schedule-list">
<v-divider />
<div
v-for="when in item.when" v-for="when in item.when"
:key="when.day"> :key="when.day">
<v-list-item-content>{{ when.day }}</v-list-item-content> <v-list-item>
<v-list-item-content class="align-end"> <v-list-item-content>
{{ when.from }} - {{ when.to }} <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-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-list-item>
<v-divider />
</div>
</v-list> </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"