Implement vuex, New mobile view design

This commit is contained in:
Michael Götz 2018-04-14 16:25:37 +02:00
parent 586ae29549
commit ced89650f6
24 changed files with 651 additions and 309 deletions

View File

@ -7,7 +7,9 @@
</b-col> </b-col>
<b-col cols="8" class="text-center"> <b-col cols="8" class="text-center">
<!--{% block headline %}{% endblock %}--> <!--{% block headline %}{% endblock %}-->
<h2>BaStA</h2> <h2>
<router-link :to="{name: 'home'}">BaStA</router-link>
</h2>
</b-col> </b-col>
<b-col cols="2" class="text-right"> <b-col cols="2" class="text-right">
<top-menu></top-menu> <top-menu></top-menu>
@ -15,7 +17,7 @@
</b-row> </b-row>
<b-row id="content"> <b-row id="content">
<b-col> <b-col cols="12">
<router-view/> <router-view/>
</b-col> </b-col>
</b-row> </b-row>
@ -37,8 +39,11 @@
</b-col> </b-col>
</b-row> </b-row>
<b-row class="text-center pb-2 bg-dark font-white p-2"> <b-row class="text-center pb-2 bg-dark font-white p-2">
<b-col> <b-col cols="4" class="text-left">
© Copyright 2018, Michael Götz <small>Version 0.1.1</small>
</b-col>
<b-col cols="8" class="text-right">
<small>© Copyright 2018, Michael Götz</small>
</b-col> </b-col>
</b-row> </b-row>
</footer> </footer>
@ -99,4 +104,9 @@
.font-white { .font-white {
color: #ffffff; color: #ffffff;
} }
#content {
background-color: #343a40;
color: white;
}
</style> </style>

View File

@ -12,11 +12,13 @@ export default {
}, },
// Send a request to the login URL and save the returned JWT // Send a request to the login URL and save the returned JWT
login(creds) { login(creds, store) {
console.log(store);
console.log('LOGIN'); console.log('LOGIN');
window.axios.post(API_ROOT.concat('/token-auth/token/create/'), creds) window.axios.post(API_ROOT.concat('/token-auth/token/create/'), creds)
.then((response) => { .then((response) => {
this.user.authenticated = true; this.user.authenticated = true;
store.dispatch('login').then();
console.log(response); console.log(response);
localStorage.setItem('user', JSON.stringify(response)); localStorage.setItem('user', JSON.stringify(response));
// Redirect to a specified route // Redirect to a specified route
@ -32,21 +34,18 @@ export default {
}, },
// To log out // To log out
logout: function (redirect) { logout: function (store) {
window.axios.post(API_ROOT.concat('/token-auth/token/destroy/'),) window.axios.post(API_ROOT.concat('/token-auth/token/destroy/'),)
.then((response) => { .then((response) => {
this.user.authenticated = false; this.user.authenticated = false;
store.dispatch('logout').then();
localStorage.removeItem('user'); localStorage.removeItem('user');
console.log(response); console.log(response);
// Redirect to a specified route
window.axios.defaults.headers.common = { window.axios.defaults.headers.common = {
'Authorization': '', 'Authorization': '',
}; };
location.reload(); location.reload();
if (redirect) {
router.push({name: 'home'});
// router.go(redirect)
}
}) })
.catch(function (error) { .catch(function (error) {
console.log(error); console.log(error);

View File

@ -1,5 +1,5 @@
<template> <template>
<div id="home"> <b-col id="home">
<b-row id="home"> <b-row id="home">
<b-col cols="12" sm="6" lg="6" xl="6"> <b-col cols="12" sm="6" lg="6" xl="6">
<icon-link :config="foodConfig"></icon-link> <icon-link :config="foodConfig"></icon-link>
@ -16,7 +16,7 @@
<icon-link :config="linksConfig"></icon-link> <icon-link :config="linksConfig"></icon-link>
</b-col> </b-col>
</b-row> </b-row>
</div> </b-col>
</template> </template>
<script> <script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <b-col cols="12">
<b-tabs> <b-tabs>
<b-tab title="Account Details" active> <b-tab title="Account Details" active>
<account-details :user="user"></account-details> <account-details :user="user"></account-details>
@ -8,7 +8,7 @@
<food></food> <food></food>
</b-tab> </b-tab>
</b-tabs> </b-tabs>
</div> </b-col>
</template> </template>
<script> <script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="!isAuthenticated()"> <div v-if="!isAuthenticated()" class="col col-12">
<b-row class="justify-content-center text-center ml-md-auto mt-2"> <b-row class="justify-content-center text-center ml-md-auto mt-2">
<b-col cols="11" sm="10" md="7" lg="5" xl="4" class="border pb-2 pt-2"> <b-col cols="11" sm="10" md="7" lg="5" xl="4" class="border pb-2 pt-2">
<h2>Login</h2> <h2>Login</h2>
@ -38,7 +38,7 @@
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
<div v-else> <div v-else class="col col-12">
<b-row class="justify-content-center text-center ml-md-auto mt-2"> <b-row class="justify-content-center text-center ml-md-auto mt-2">
<b-col cols="11" sm="10" md="7" lg="5" xl="4" class="border pb-2 pt-2"> <b-col cols="11" sm="10" md="7" lg="5" xl="4" class="border pb-2 pt-2">
Du wurdest erfolgreich eingeloggt. Du wurdest erfolgreich eingeloggt.
@ -65,7 +65,7 @@
methods: { methods: {
onSubmit(evt) { onSubmit(evt) {
evt.preventDefault(); evt.preventDefault();
authentication.login({"username": this.form.username, "password": this.form.password}, -1); authentication.login({"username": this.form.username, "password": this.form.password}, this.$store);
}, },
isAuthenticated: function () { isAuthenticated: function () {
if (authentication.authenticated()) { if (authentication.authenticated()) {

View File

@ -14,7 +14,7 @@
name: "Logout", name: "Logout",
created() { created() {
if (Auth.authenticated()) { if (Auth.authenticated()) {
Auth.logout(); Auth.logout(this.$store);
} }
setTimeout(function () { setTimeout(function () {
router.go(-1); router.go(-1);

View File

@ -1,7 +1,6 @@
<template> <template>
<div> <div>
<div v-if="!showFormular"> <div v-if="!showFormular" class="row p-1">
<b-row class="p-1">
<b-col cols="12" sm="12" md="12" lg="8" xl="8" class="p-3 bg-light text-dark"> <b-col cols="12" sm="12" md="12" lg="8" xl="8" class="p-3 bg-light text-dark">
<h3>{{user.username}}</h3> <h3>{{user.username}}</h3>
<p><strong>Vorname:</strong> {{ user.first_name }}</p> <p><strong>Vorname:</strong> {{ user.first_name }}</p>
@ -16,9 +15,8 @@
<p>Last Login: {{ user.last_login | formatDate}}</p> <p>Last Login: {{ user.last_login | formatDate}}</p>
</div> </div>
</div> </div>
</b-row>
</div> </div>
<div v-if="showFormular"> <div v-if="showFormular" class="row">
<account-formular :user="user"></account-formular> <account-formular :user="user"></account-formular>
<b-button :pressed.sync="showFormular" variant="primary">{{this.formularButtonLabel}}</b-button> <b-button :pressed.sync="showFormular" variant="primary">{{this.formularButtonLabel}}</b-button>
</div> </div>

View File

@ -2,10 +2,10 @@
<div> <div>
<h4>Essen</h4> <h4>Essen</h4>
<div class="row p-1"> <div class="row p-1">
<div v-for="food in foods" class="col col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 p-2"> <div v-for="food in foods" class="col col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 p-2 bg-light text-dark">
<b-row class="food border p-2"> <b-row class="food p-2">
<b-col cols="4" class="image"> <b-col cols="4" class="image">
<a v-if="food.image" :href="food.image.thumb"> <a v-if="food.image" :href="food.image.image">
<img :src="food.image.thumb" class="media-object" alt="Bild" width="100%"> <img :src="food.image.thumb" class="media-object" alt="Bild" width="100%">
</a> </a>
<a v-else :href="defaultImageUrl"> <a v-else :href="defaultImageUrl">

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="menu"> <div class="menu">
<div class="p-3 border border-dark rounded bg-light text-dark"> <div class="p-1 bg-light text-dark">
<div v-if="menu"> <div v-if="menu.date">
<p>{{ menu.date | formatDateWithWeekday}}</p> <p>{{ menu.date | formatDateWithWeekday}}</p>
<ul class="border"> <ul class="">
<li v-for="food in menu.menu" :data-food="food.id" :data-rating="food.rating" class="food-item media mb-2"> <li v-for="food in menu.menu" :data-food="food.id" :data-rating="food.rating" class="food-item media mb-2">
<single-menu :food="food" :defaultImageUrl="defaultImageUrl"></single-menu> <single-menu :food="food"></single-menu>
</li> </li>
</ul> </ul>
</div> </div>
@ -21,14 +21,15 @@
export default { export default {
name: "DayMenu", name: "DayMenu",
props: ['menu', 'failMessage', 'defaultImageUrl'], props: ['menu', 'failMessage'],
components: {SingleMenu}, components: {SingleMenu},
} }
</script> </script>
<style scoped> <style scoped>
ul { .menu, ul {
padding: 0; padding: 0;
} }
</style> </style>

View File

@ -1,44 +1,56 @@
<template> <template>
<b-row> <b-row>
<b-col cols="12" class="text-center border pt-2"> <b-col cols="12">
<b-row align-v="center"> <b-row align-v="center" class="bg-light text-dark">
<b-col cols="1"></b-col> <b-col cols="12" class="mb-0 mt-2 text-center">
<b-col cols="10">
<h4>Detailansicht</h4>
<h5>{{food.name}}</h5> <h5>{{food.name}}</h5>
</b-col> </b-col>
<b-col cols="1">
<router-link :to="{name: 'food'}">Back</router-link>
</b-col>
</b-row> </b-row>
</b-col> <div v-if="!mobile" class="row">
<b-col cols="7" class="border"> <b-col cols="8">
<b-row> <b-row>
<b-col class="border pt-2"> <b-col cols="12">
<allergens-overview :allergens="food.allergens"></allergens-overview> <food-picture></food-picture>
</b-col> </b-col>
</b-row> </b-row>
<b-row> <b-row>
<b-col class="border pt-2"> <b-col cols="12">
<price-overview :student="food.price_student" :employee="food.price_employee" <comments-overview></comments-overview>
:guest="food.price_guest"></price-overview>
</b-col>
</b-row>
<b-row>
<b-col class="border pt-2">
<rating-combined v-on:updateFood="loadFood" :globalRating="food.rating" :foodId="food_id"></rating-combined>
</b-col>
</b-row>
<b-row>
<b-col class="border pt-2">
<comments-overview :comments="comments"></comments-overview>
</b-col> </b-col>
</b-row> </b-row>
</b-col> </b-col>
<b-col cols="5" class="border">
<food-picture :image="food.image" :userFoodImage="userFoodImage"></food-picture> <b-col cols="4">
<food-picture-upload v-on:updateImage="loadUserImage"></food-picture-upload> <b-row class="m-0">
<comment-box v-on:updateFood="loadFood" :foodId="food_id"></comment-box> <b-col cols="12" class="bg-light text-dark mb-2 mt-2 pt-2 pb-2">
<rating-combined></rating-combined>
</b-col>
<b-col cols="12" class="bg-light text-dark mb-2 pt-2 pb-2">
<price-overview></price-overview>
</b-col>
<b-col cols="12" class="bg-light text-dark pt-2 pb-2">
<allergens-overview></allergens-overview>
</b-col>
</b-row>
</b-col>
</div>
<div v-else class="row">
<b-col cols="12">
<food-picture></food-picture>
</b-col>
<b-col cols="12" class="bg-light text-dark mb-2 mt-2 pt-2 pb-2">
<rating-combined></rating-combined>
</b-col>
<b-col cols="12" class="bg-light text-dark mb-2 pt-2 pb-2">
<price-overview></price-overview>
</b-col>
<b-col cols="12" class="bg-light text-dark mb-2 pt-2 pb-2">
<allergens-overview></allergens-overview>
</b-col>
<b-col cols="12" class="bg-light text-dark pt-2 pb-2">
<comments-overview></comments-overview>
</b-col>
</div>
</b-col> </b-col>
</b-row> </b-row>
</template> </template>
@ -50,8 +62,6 @@
import PriceOverview from "@/components/food/utils/PriceOverview"; import PriceOverview from "@/components/food/utils/PriceOverview";
import RatingCombined from "@/components/food/utils/RatingCombined"; import RatingCombined from "@/components/food/utils/RatingCombined";
import FoodPicture from "@/components/food/utils/FoodPicture"; import FoodPicture from "@/components/food/utils/FoodPicture";
import FoodPictureUpload from "@/components/food/utils/PictureUpload";
import CommentBox from "@/components/food/utils/CommentBox";
import CommentsOverview from "@/components/food/utils/CommentsOverview"; import CommentsOverview from "@/components/food/utils/CommentsOverview";
@ -62,65 +72,34 @@
PriceOverview, PriceOverview,
RatingCombined, RatingCombined,
FoodPicture, FoodPicture,
FoodPictureUpload, CommentsOverview,
CommentBox,
CommentsOverview
}, },
data() { data() {
return { return {
food_id: '', mobile: false,
food: {},
comments: '',
show: true,
images: '',
form: {
file: '',
},
userFoodImage: '',
}; };
}, created() { }, created() {
this.loadFood(); this.addMobileChecker();
this.loadUserImage(); this.$store
.dispatch('loadDetailedFood', {foodId: this.$route.params.id})
.then();
this.$store.dispatch('loadDefaultImageLocation').then();
},
computed: {
food() {
return this.$store.getters.getDetailedFood;
},
}, },
methods: { methods: {
loadFood() {
window.axios.get(CONFIG.API_ROOT_FOOD
.concat('/meals/').concat(this.$route.params.id))
.then(response => {
// JSON responses are automatically parsed.
console.log(JSON.parse(JSON.stringify(response.data)));
this.food = response.data;
this.food_id = this.food.id;
})
.catch(e => {
});
window.axios.get(CONFIG.API_ROOT_FOOD
.concat('/meals/').concat(this.$route.params.id).concat('/comments'))
.then(response => {
console.log(JSON.parse(JSON.stringify(response.data)));
this.comments = response.data;
})
.catch(e => {
});
},
loadUserImage() {
if (authentication.authenticated()) {
let url = CONFIG.API_ROOT_ACCOUNT + '/food/images/?food_id=' + this.$route.params.id;
window.axios
.get(url)
.then(response => {
console.log('LOG IMAGE');
if (response.data.length > 0) {
this.userFoodImage = response.data[0].image.image;
}
})
.catch()
}
},
isAuthenticated: function () { isAuthenticated: function () {
return authentication.authenticated(); return authentication.authenticated();
}, },
addMobileChecker() {
this.mobile = window.innerWidth < 768;
window.addEventListener('resize', () => {
this.mobile = window.innerWidth < 768;
}, true);
},
} }
} }
</script> </script>

View File

@ -1,12 +1,31 @@
<template> <template>
<b-col>
<b-row> <b-row>
<div class="col col-6" v-for="location in locations"> <b-col class="food-overview">
<tab-menu :title="location.short" :location="location.id" :defaultImageUrl="defaultImageUrl"></tab-menu> <div v-if="!mobile" class="row">
<div v-for="location in locations" class="tab-menu-wrapper col col-6">
<h3>{{ location.short }} </h3>
<tab-menu :title="location.short" :location="location.id"></tab-menu>
</div>
</div>
<div v-else class="row">
<div v-for="location in locations" class="col col-12">
<b-card no-body class="mt-3 rounded-0">
<b-card-header header-tag="header" class="p-0" role="tab">
<b-btn block class="rounded-0" href="#" v-b-toggle="location.id" variant="light">
{{ location.short }}
</b-btn>
</b-card-header>
<b-collapse :id="location.id" accordion="my-accordion" role="tabpanel">
<b-card-body class="p-0 m-0">
<tab-menu :title="location.short" :location="location.id"></tab-menu>
</b-card-body>
</b-collapse>
</b-card>
</div>
<div class="clearfix m-3"></div>
</div> </div>
</b-row>
</b-col> </b-col>
</b-row>
</template> </template>
<script> <script>
@ -19,34 +38,32 @@
components: {TabMenu}, components: {TabMenu},
data() { data() {
return { return {
defaultImageUrl: '', mobile: false,
locations: [],
} }
}, },
computed: {
locations() {
return this.$store.getters.getStudWuerzburgLocations;
},
},
created() { created() {
axios.get(CONFIG.API_ROOT_FOOD.concat('/menus/locations')) this.addMobileChecker();
.then(response => { this.$store.dispatch('loadLocations').then();
// JSON responses are automatically parsed. this.$store.dispatch('loadDefaultImageLocation').then();
console.log(JSON.parse(JSON.stringify(response.data))); },
this.locations = response.data; methods: {
}) addMobileChecker() {
.catch(e => { this.mobile = window.innerWidth < 768;
console.error(e) window.addEventListener('resize', () => {
}); this.mobile = window.innerWidth < 768;
}, true);
axios.get(CONFIG.API_ROOT_FOOD.concat('/meals/images/default')) },
.then(response => {
// JSON responses are automatically parsed.
console.log(JSON.parse(JSON.stringify(response.data)));
this.defaultImageUrl = response.data.image;
})
.catch(e => {
console.error(e)
});
} }
} }
</script> </script>
<style scoped> <style scoped>
.tab-menu-wrapper {
padding: 0;
}
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<b-col> <b-col>
<b-row> <b-row>
<b-col cols="3"> <b-col cols="3" class="p-0">
<a v-if="food.image" :href="food.image.thumb" :data-lightbox="title" <a v-if="food.image" :href="food.image.thumb" :data-lightbox="title"
:data-title="food.name"> :data-title="food.name">
<img :src="food.image.thumb" class="media-object" alt="Bild" width="80px"> <img :src="food.image.thumb" class="media-object" alt="Bild" width="80px">
@ -13,10 +13,16 @@
alt="Bild" width="80px"> alt="Bild" width="80px">
</a> </a>
</b-col> </b-col>
<b-col cols="7" class="media-body"> <b-col cols="9" class="media-body">
<b-row>
<b-col cols="12" sm="12" md="12" lg="12" xl="12">
<div class="food-name"> <div class="food-name">
<router-link :to="{name: 'food-detail', params:{id: food.id}}">{{ food.name }}</router-link> <router-link :to="{name: 'food-detail', params:{id: food.id}}">{{ food.name }}</router-link>
</div> </div>
</b-col>
</b-row>
<b-row>
<b-col cols="8" sm="8" md="8" lg="8" xl="8">
<fa-rating :glyph="star" <fa-rating :glyph="star"
:spacing="-1" :spacing="-1"
:inactive-color="'#cfcfcf'" :inactive-color="'#cfcfcf'"
@ -29,7 +35,12 @@
v-model="userRating"> v-model="userRating">
</fa-rating> </fa-rating>
</b-col> </b-col>
<div v-if="food.price_student" class="col-2"><span class="float-right">{{ food.price_student }}</span></div> <div v-if="food.price_student" class="col col-4">
<span class="float-right">{{ food.price_student }} </span>
</div>
</b-row>
</b-col>
</b-row> </b-row>
</b-col> </b-col>
</template> </template>
@ -43,21 +54,27 @@
export default { export default {
name: "SingleMenu", name: "SingleMenu",
props: ['food', 'title', 'defaultImageUrl'], props: ['food', 'title'],
components: {FaRating}, components: {FaRating},
data() { data() {
return { return {
userRating: this.food.rating, userRating: this.food.rating,
star: '', star: '',
}; };
}, created() { },
computed: {
defaultImageUrl() {
return this.$store.getters.getDefaultImageLocation
},
},
created() {
// register the icon // register the icon
this.star = Star; this.star = Star;
// console.log(this.food.rating); // console.log(this.food.rating);
// this.userRating = this.food.rating; // this.userRating = this.food.rating;
}, },
watch: { watch: {
userRating: function (newRating) { userRating: function (newRating, oldRating) {
if (authentication.authenticated()) { if (authentication.authenticated()) {
let url = CONFIG.API_ROOT_FOOD.concat('/meals/').concat(this.food.id).concat('/rating'); let url = CONFIG.API_ROOT_FOOD.concat('/meals/').concat(this.food.id).concat('/rating');
window.axios window.axios
@ -68,6 +85,7 @@
} }
) )
.catch(); .catch();
console.log(oldRating);
} else { } else {
router.push({name: 'login'}) router.push({name: 'login'})
} }

View File

@ -1,12 +1,11 @@
<template> <template>
<div> <div class="tab-menu m-0 p-1">
<h3>{{ title }} </h3>
<b-tabs> <b-tabs>
<b-tab title="Daily" active> <b-tab title="Heute" active title-link-class="rounded-0">
<day-menu :menu="dayMenu" :defaultImageUrl="defaultImageUrl"></day-menu> <day-menu :menu="dayMenu"></day-menu>
</b-tab> </b-tab>
<b-tab title="Weekly"> <b-tab title="Woche" title-link-class="rounded-0">
<week-menu :menus="weekMenus" :defaultImageUrl="defaultImageUrl"></week-menu> <week-menu :menus="weekMenus"></week-menu>
</b-tab> </b-tab>
</b-tabs> </b-tabs>
</div> </div>
@ -20,7 +19,7 @@
export default { export default {
name: "TabMenu", name: "TabMenu",
props: ['title', 'location', 'defaultImageUrl'], props: ['title', 'location'],
components: {DayMenu, WeekMenu}, components: {DayMenu, WeekMenu},
data() { data() {
return { return {
@ -38,8 +37,6 @@
.concat('&startdate=').concat(today_yyyymmdd) .concat('&startdate=').concat(today_yyyymmdd)
.concat('&enddate=').concat(today_yyyymmdd)) .concat('&enddate=').concat(today_yyyymmdd))
.then(response => { .then(response => {
// JSON responses are automatically parsed.
console.log(JSON.parse(JSON.stringify(response.data))); console.log(JSON.parse(JSON.stringify(response.data)));
if (response.data.length == 1) { if (response.data.length == 1) {
this.dayMenu = response.data[0]; this.dayMenu = response.data[0];
@ -79,5 +76,8 @@
</script> </script>
<style scoped> <style scoped>
.tab-menu {
margin: 5px;
padding: 10px;
}
</style> </style>

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="p-3 border border-dark rounded bg-light text-dark"> <div class="p-0 bg-light text-dark">
<div v-if="menus"> <div v-if="menus.length > 0">
<div v-for="menu in menus" class="menu"> <div v-for="menu in menus" class="menu-wrapper">
<day-menu :menu="menu" :defaultImageUrl="defaultImageUrl"></day-menu> <day-menu :menu="menu"></day-menu>
</div> </div>
</div> </div>
<p v-else>Keine Daten gefunden.</p> <p v-else>Keine Daten gefunden.</p>
@ -14,11 +14,18 @@
export default { export default {
name: "WeekMenu", name: "WeekMenu",
props: ['menus', 'failMessage', 'defaultImageUrl'], props: ['menus', 'failMessage'],
components: {DayMenu: DayMenu}, components: {DayMenu: DayMenu},
} }
</script> </script>
<style scoped> <style scoped>
.menu-wrapper:first-child {
border-top: none;
}
.menu-wrapper {
border: 1px solid rgba(0, 0, 0, 0.125);
margin-bottom: 5px;
}
</style> </style>

View File

@ -1,23 +1,58 @@
<template> <template>
<div> <div v-if="!mobile">
<h5>Allergene</h5> <h5>Allergene</h5>
<div v-if="allergens.length == 0">
<div v-if="allergens"> Keine Angaben gefunden.
</div>
<div v-else>
<ul> <ul>
<li v-for="allergen in allergens">{{ allergen.name }}</li> <li v-for="allergen in allergens">{{ allergen.name }}</li>
</ul> </ul>
</div> </div>
</div>
<div v-else> <div v-else>
<b-card no-body class="mb-1 rounded-0">
<b-card-header header-tag="header" class="p-0" role="tab">
<b-btn block href="#" v-b-toggle.allergensBox variant="light">Allergene</b-btn>
</b-card-header>
<b-collapse id="allergensBox" accordion="allergensBox-accordion" role="tabpanel">
<b-card-body>
<div v-if="allergens.length == 0">
Keine Angaben gefunden. Keine Angaben gefunden.
</div> </div>
<div v-else>
<ul>
<li v-for="allergen in allergens">{{ allergen.name }}</li>
</ul>
</div>
</b-card-body>
</b-collapse>
</b-card>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "AllergensOverview", name: "AllergensOverview",
props: ['allergens'], data() {
return {mobile: false};
},
computed: {
allergens() {
return this.$store.getters.getDetailedFoodUserAllergens
},
},
created() {
this.addMobileChecker();
},
methods: {
addMobileChecker() {
this.mobile = window.innerWidth < 768;
window.addEventListener('resize', () => {
this.mobile = window.innerWidth < 768;
}, true);
},
}
} }
</script> </script>

View File

@ -1,6 +1,11 @@
<template> <template>
<div v-if="isAuthenticated()"> <div v-if="isAuthenticated()">
<h5>Kommentare</h5> <b-card no-body class="mb-1 rounded-0">
<b-card-header header-tag="header" class="p-0" role="tab">
<b-btn block href="#" v-b-toggle.commentBox variant="light">Kommentieren</b-btn>
</b-card-header>
<b-collapse id="commentBox" accordion="commentBox-accordion" role="tabpanel">
<b-card-body>
<b-form @submit="onSubmit"> <b-form @submit="onSubmit">
<b-form-group id="commentShort" <b-form-group id="commentShort"
label="Überschrift, Zusammenfassung" label="Überschrift, Zusammenfassung"
@ -22,8 +27,11 @@
:max-rows="6"> :max-rows="6">
</b-form-textarea> </b-form-textarea>
</b-form-group> </b-form-group>
<b-button type="submit" variant="primary">Submit</b-button> <b-button type="submit" variant="primary">Speichern</b-button>
</b-form> </b-form>
</b-card-body>
</b-collapse>
</b-card>
</div> </div>
</template> </template>
@ -33,7 +41,6 @@
export default { export default {
name: "CommentBox", name: "CommentBox",
props: ['foodId'],
data() { data() {
return { return {
form: { form: {
@ -42,19 +49,24 @@
} }
}; };
}, },
computed: {
foodID() {
return this.$store.getters.getDetailedFoodID
},
},
created() { created() {
this.getUserComment(); this.getUserComment();
}, },
watch: { watch: {
foodId: function () { foodID: function () {
this.getUserComment(); this.getUserComment();
}, },
}, },
methods: { methods: {
onSubmit(evt) { onSubmit(evt) {
evt.preventDefault(); evt.preventDefault();
if (authentication.authenticated() && this.foodId) { if (authentication.authenticated() && this.foodID) {
let url = CONFIG.API_ROOT_FOOD + '/meals/' + this.foodId + '/comment'; let url = CONFIG.API_ROOT_FOOD + '/meals/' + this.$route.params.id + '/comment';
let jsonData = { let jsonData = {
"title": this.form.commentShort, "title": this.form.commentShort,
"description": this.form.commentText "description": this.form.commentText
@ -62,7 +74,7 @@
window.axios window.axios
.post(url, jsonData) .post(url, jsonData)
.then(response => { .then(response => {
this.$emit('updateFood'); this.$store.dispatch('loadDetailedFood', {foodId: this.$route.params.id}).then();
}) })
.catch() .catch()
} }
@ -71,12 +83,11 @@
return authentication.authenticated(); return authentication.authenticated();
}, },
getUserComment: function () { getUserComment: function () {
if (authentication.authenticated() && this.foodId) { if (authentication.authenticated() && this.foodID) {
let url = CONFIG.API_ROOT_ACCOUNT + '/food/comments/?food_id=' + this.foodId; let url = CONFIG.API_ROOT_ACCOUNT + '/food/comments/?food_id=' + this.$route.params.id;
window.axios window.axios
.get(url) .get(url)
.then(response => { .then(response => {
console.log(response.data);
if (response.data.length > 0) { if (response.data.length > 0) {
this.form.commentText = response.data[0].description; this.form.commentText = response.data[0].description;
this.form.commentShort = response.data[0].title; this.form.commentShort = response.data[0].title;

View File

@ -1,19 +1,33 @@
<template> <template>
<b-row> <b-row class="bg-light text-dark m-0">
<b-col> <b-col class="pt-2">
<h5>Kommentare</h5> <h5>Kommentare</h5>
<comment-box></comment-box>
</b-col> </b-col>
<div v-for="comment in comments" class="col col-12 border"> <div v-for="comment in comments" class="col col-12">
<h5>{{comment.title}}</h5> <b-card :title="comment.title">
<p>{{comment.description}}</p> <p class="card-text">
{{comment.description}}
</p>
</b-card>
<p></p>
</div> </div>
</b-row> </b-row>
</template> </template>
<script> <script>
import CommentBox from "@/components/food/utils/CommentBox";
export default { export default {
name: "CommentsOverview", name: "CommentsOverview",
props: ['comments'], components: {
CommentBox
},
computed: {
comments() {
return this.$store.getters.getDetailedFoodComments
}
},
} }
</script> </script>

View File

@ -1,39 +1,43 @@
<template> <template>
<div>
<food-picture-upload id="pic-upload"></food-picture-upload>
<div v-if="userFoodImage"> <div v-if="userFoodImage">
<b-img :src="userFoodImage" fluid-grow alt="Fluid-Grow image"/> <b-img :src="userFoodImage" fluid-grow alt="Fluid-Grow image"/>
</div> </div>
<div v-else-if="image"> <div v-else-if="foodImage">
<b-img :src="image" fluid-grow alt="Fluid-Grow image"/> <b-img :src="foodImage" fluid-grow alt="Fluid-Grow image"/>
</div> </div>
<div v-else> <div v-else>
<b-img img :src="defaultImage" fluid-grow alt="Fluid-Grow image"/> <b-img img :src="defaultImage" fluid-grow alt="Fluid-Grow image"/>
</div> </div>
</div>
</template> </template>
<script> <script>
import * as CONFIG from '../../../config' import FoodPictureUpload from "@/components/food/utils/PictureUpload";
export default { export default {
name: "FoodPicture", name: "FoodPicture",
props: ['image', 'userFoodImage'], components: {FoodPictureUpload},
data() { computed: {
return { defaultImage() {
defaultImage: '', return this.$store.getters.getDefaultImageLocation
}; },
foodImage() {
return this.$store.getters.getDetailedFoodImage
},
userFoodImage() {
return this.$store.getters.getDetailedFoodUserImage
}, },
created() {
window.axios
.get(CONFIG.API_ROOT_FOOD.concat('/meals/images/default'))
.then(response => {
console.log(JSON.parse(JSON.stringify(response.data)));
this.defaultImage = response.data.image;
})
.catch(e => {
});
}, },
} }
</script> </script>
<style scoped> <style scoped>
#pic-upload {
position: absolute;
right: 10px;
top: 10px;
margin-right: 10px;
}
</style> </style>

View File

@ -1,47 +1,86 @@
<template> <template>
<div v-if="isAuthenticated()"> <div v-if="isAuthenticated()">
<h4>Bilder</h4> <div v-if="showFileUpload">
<p>Bild senden</p> <div class="fileUpload">
<b-form @submit="onSubmit"> <label class="inputLabel" for="file">
<b-form-file v-model="form.file" :state="Boolean(form.file)" placeholder="Choose a file..."></b-form-file> <icon :name="'upload'" :scale="1" class="inputLabel-icon"></icon>
<b-button type="submit" variant="primary">Submit</b-button> </label>
</b-form> <input v-on:change="onSubmit" type="file" name="file" id="file" class="inputfile" ref="userFile"/>
</div>
</div>
<div v-else-if="showProgressBar" class="progress-bar-wrapper">
<b-progress :value="uploadProgress" :max="100" variant="success" height="10px"
class="w-100"></b-progress>
</div>
<div v-else class="success-response">
<icon :name="'check-circle'" :scale="2" class="success"></icon>
</div>
</div> </div>
</template> </template>
<script> <script>
import * as CONFIG from '../../../config' import * as CONFIG from '../../../config'
import authentication from '../../../authentication' import authentication from '../../../authentication'
import Icon from 'vue-awesome/components/Icon'
import 'vue-awesome/icons/check-circle';
import 'vue-awesome/icons/upload';
export default { export default {
name: "PictureUpload", name: "PictureUpload",
props: ['image'], components: {Icon},
data() { data() {
return { return {
form: { showFileUpload: true,
file: '', showProgressBar: false,
} uploadProgress: 0,
}; };
}, },
watch: {
file: function (newForm) {
console.log('Pic Changed');
this.onSubmit();
},
},
watch: {
file: (newFile) => {
this.onSubmit()
}
},
methods: { methods: {
isAuthenticated: function () { isAuthenticated: function () {
return authentication.authenticated(); return authentication.authenticated();
}, },
onSubmit(evt) { onSubmit() {
evt.preventDefault(); this.showFileUpload = false;
var formData = new FormData(); this.showProgressBar = true;
console.log(this.form.file); let url = CONFIG.API_ROOT_FOOD + '/meals/' + this.$route.params.id + '/image';
// append Blob/File object let config = {
formData.append('image', this.form.file, this.form.file.name); onUploadProgress: (progressEvent) => {
console.log(Math.round(progressEvent.loaded / progressEvent.total * 100));
this.uploadProgress = Math.round(progressEvent.loaded / progressEvent.total * 100);
}
};
let formData = new FormData();
console.log(this.$refs.userFile.files);
formData.append('image', this.$refs.userFile.files[0], this.$refs.userFile.files[0].name);
window.axios.post(CONFIG.API_ROOT_FOOD window.axios
.concat('/meals/').concat(this.$route.params.id).concat('/image'), formData) .post(url, formData, config)
.then(response => { .then(response => {
// JSON responses are automatically parsed. console.log(response.data.image);
this.$emit('updateImage'); this.$store
.dispatch('setUserImage', {image: response.data.image})
.then();
this.showProgressBar = false;
this.showFileUpload = false;
this.uploadProgress = 0;
setTimeout(() => {
this.showFileUpload = true;
}, 5000);
}) })
.catch(e => { .catch(e => {
console.log(JSON.parse(JSON.stringify(e.response))); console.log(e.response);
}); });
}, },
} }
@ -49,5 +88,56 @@
</script> </script>
<style scoped> <style scoped>
.success {
color: #57d25f;
}
.inputfile {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.inputfile + label, .inputLabel {
cursor: pointer; /* "hand" cursor */
}
.success-response {
background-color: #ffffff;
padding: 5px;
height: 42px;
}
.progress-bar-wrapper {
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
background-color: #ffffff;
width: 60px;
}
.inputLabel, .progress-bar-wrapper {
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
background-color: #ffffff;
}
.inputLabel:hover {
outline: 1px dotted #000;
outline: -webkit-focus-ring-color auto 5px;
}
.inputLabel-icon {
color: #303030;
}
.inputfile:focus + label, .inputLabel:focus {
outline: 1px dotted #000;
outline: -webkit-focus-ring-color auto 5px;
}
</style> </style>

View File

@ -3,18 +3,18 @@
<h5>Preise</h5> <h5>Preise</h5>
<div class="student"> <div class="student">
Student: Student:
<span v-if="student">{{ student }}</span> <span v-if="prices.student">{{ prices.student }} </span>
<span v-else>Keine Angaben gefunden</span> <span v-else>n/a</span>
</div> </div>
<div class="price-employee"> <div class="price-employee">
Employee: Mitarbeiter:
<span v-if="employee">{{ employee }}</span> <span v-if="prices.employee">{{ prices.employee }} </span>
<span v-else>Keine Angaben gefunden</span> <span v-else>n/a</span>
</div> </div>
<div class="price-guest"> <div class="price-guest">
Guest: Gast:
<span v-if="guest">{{ guest }}</span> <span v-if="prices.guest">{{ prices.guest }} </span>
<span v-else>Keine Angaben gefunden</span> <span v-else>n/a</span>
</div> </div>
</div> </div>
</template> </template>
@ -22,7 +22,11 @@
<script> <script>
export default { export default {
name: "PriceOverview", name: "PriceOverview",
props: ['student', 'employee', 'guest'], computed: {
prices() {
return this.$store.getters.getDetailedFoodPrices
}
}
} }
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div id="rating" class="mt-3"><strong>Rating:</strong> <div id="rating" class=""><strong>Rating:</strong>
<fa-rating :glyph="star" <fa-rating :glyph="star"
:spacing="-1" :spacing="-1"
:inactive-color="'#cfcfcf'" :inactive-color="'#cfcfcf'"
@ -14,7 +14,7 @@
v-model="globalRating"> v-model="globalRating">
</fa-rating> </fa-rating>
</div> </div>
<div v-if="isAuthenticated()" id="user-rating" class="mt-3"><strong>Dein Rating:</strong> <div v-if="isAuthenticated()" id="user-rating" class=""><strong>Dein Rating:</strong>
<fa-rating :glyph="star" <fa-rating :glyph="star"
:spacing="-1" :spacing="-1"
:inactive-color="'#cfcfcf'" :inactive-color="'#cfcfcf'"
@ -40,7 +40,6 @@
export default { export default {
name: "RatingCombined", name: "RatingCombined",
components: {FaRating}, components: {FaRating},
props: ['globalRating', 'foodId'],
data() { data() {
return { return {
star: Star, star: Star,
@ -48,24 +47,32 @@
loaded: false, loaded: false,
}; };
}, },
computed: {
foodID() {
return this.$store.getters.getDetailedFoodID
},
globalRating() {
return this.$store.getters.getDetailedFoodRating
},
},
created() { created() {
if (window.authentication.authenticated() && this.foodId) { if (window.authentication.authenticated() && this.foodID) {
this.loadUserRating(); this.loadUserRating();
this.loaded = true; this.loaded = true;
} }
}, },
watch: { watch: {
foodId: function () { foodID: function () {
this.loadUserRating(); this.loadUserRating();
this.loaded = true; this.loaded = true;
}, },
userRating: function (newRating) { userRating: function (newRating) {
if (authentication.authenticated() && this.loaded) { if (authentication.authenticated() && this.loaded) {
let url = CONFIG.API_ROOT_FOOD.concat('/meals/').concat(this.foodId).concat('/rating'); let url = CONFIG.API_ROOT_FOOD + '/meals/' + this.$route.params.id + '/rating';
window.axios window.axios
.post(url, {rating: newRating}) .post(url, {rating: newRating})
.then(response => { .then(response => {
this.$emit('updateFood'); this.$store.dispatch('loadDetailedFood', {foodId: this.$route.params.id}).then()
} }
) )
.catch(); .catch();
@ -79,16 +86,12 @@
return authentication.authenticated(); return authentication.authenticated();
}, },
loadUserRating: function () { loadUserRating: function () {
console.log('LOAD USER RATING');
if (authentication.authenticated()) { if (authentication.authenticated()) {
console.log(this.foodId); let url = CONFIG.API_ROOT_ACCOUNT + '/food/ratings/?food_id=' + this.$route.params.id;
let url = CONFIG.API_ROOT_ACCOUNT.concat('/food/ratings/?food_id=').concat(this.foodId);
window.axios window.axios
.get(url) .get(url)
.then(response => { .then(response => {
if (response.data.length > 0) { if (response.data.length > 0) {
console.log('RATING');
console.log(response.data);
this.userRating = response.data[0].rating; this.userRating = response.data[0].rating;
} }
} }

View File

@ -11,7 +11,7 @@
</strong> </strong>
</div> </div>
<div class="mobile-nav-item"> <div class="mobile-nav-item">
<router-link to="food">Food</router-link> <router-link :to="{name: 'food'}">Food</router-link>
</div> </div>
<div class="mobile-nav-item"> <div class="mobile-nav-item">
<a class="menu-item" href="#">Events</a> <a class="menu-item" href="#">Events</a>

View File

@ -1,6 +1,8 @@
// The Vue build version to load with the `import` command // The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias. // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex';
import {store} from './store.js';
import App from './App' import App from './App'
import router from './router' import router from './router'
import BootstrapVue from 'bootstrap-vue' import BootstrapVue from 'bootstrap-vue'
@ -11,6 +13,7 @@ import axios from 'axios'
import authentication from "./authentication"; import authentication from "./authentication";
import Vuelidate from 'vuelidate' import Vuelidate from 'vuelidate'
Vue.use(Vuex);
Vue.use(Vuelidate); Vue.use(Vuelidate);
Vue.use(BootstrapVue); Vue.use(BootstrapVue);
Vue.use(authentication.authenticated()); Vue.use(authentication.authenticated());
@ -25,7 +28,8 @@ Vue.filter('formatDate', function (value) {
Vue.filter('formatDateWithWeekday', function (value) { Vue.filter('formatDateWithWeekday', function (value) {
if (value) { if (value) {
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; // var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var days = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
let date = new Date(value); let date = new Date(value);
return days[date.getDay()] + ', ' + date.getDate() + '.' + (date.getMonth() + 1) + '.' + date.getFullYear() return days[date.getDay()] + ', ' + date.getDate() + '.' + (date.getMonth() + 1) + '.' + date.getFullYear()
} }
@ -61,6 +65,7 @@ axios.defaults.xsrfHeaderName = 'HTTP_X_CSRFTOKEN';
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,
store,
components: {App}, components: {App},
template: '<App/>' template: '<App/>'
}); });

View File

@ -1,10 +1,157 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import * as CONFIG from './config'
Vue.use(Vuex); Vue.use(Vuex);
export const store = new Vuex.Store({ export const store = new Vuex.Store({
state: { state: {
authenticated: false, user: {
authenticated: true,
},
foodAppStudWue: {
locations: [],
defaultImage: '',
currentFood: {},
currentFoodComments: [],
currentFoodUserImage: '',
},
},
getters: {
isAuthenticated: state => {
return state.user.authenticated
},
getStudWuerzburgLocations: state => {
return state.foodAppStudWue.locations
},
getDefaultImageLocation: state => {
return state.foodAppStudWue.defaultImage
},
getDetailedFood: state => {
return state.foodAppStudWue.currentFood
},
getDetailedFoodID: state => {
return state.foodAppStudWue.currentFood.id
},
getDetailedFoodRating: state => {
return state.foodAppStudWue.currentFood.rating
},
getDetailedFoodImage: state => {
return state.foodAppStudWue.currentFood.image
},
getDetailedFoodUserImage: state => {
return state.foodAppStudWue.currentFoodUserImage
},
getDetailedFoodUserAllergens: state => {
return state.foodAppStudWue.currentFood.allergens
},
getDetailedFoodPrices: state => {
return {
employee: state.foodAppStudWue.currentFood.price_employee,
guest: state.foodAppStudWue.currentFood.price_guest,
student: state.foodAppStudWue.currentFood.price_student,
} }
},
getDetailedFoodComments: state => {
return state.foodAppStudWue.currentFoodComments
},
},
mutations: {
logout(state) {
state.user.authenticated = false;
},
login(state) {
state.user.authenticated = true;
},
loadLocations(state) {
window.axios.get(CONFIG.API_ROOT_FOOD.concat('/menus/locations'))
.then(response => {
state.foodAppStudWue.locations = response.data;
})
.catch(e => {
console.error(e)
});
},
loadDefaultImageLocation(state) {
window.axios.get(CONFIG.API_ROOT_FOOD.concat('/meals/images/default'))
.then(response => {
state.foodAppStudWue.defaultImage = response.data.image;
})
.catch(e => {
console.error(e)
});
},
loadDetailedFood(state, {foodId}) {
window.axios.get(CONFIG.API_ROOT_FOOD
.concat('/meals/').concat(foodId))
.then(response => {
state.foodAppStudWue.currentFood = response.data;
})
.catch(e => {
});
window.axios.get(CONFIG.API_ROOT_FOOD
.concat('/meals/').concat(foodId).concat('/comments'))
.then(response => {
state.foodAppStudWue.currentFoodComments = response.data;
})
.catch(e => {
});
if (state.user.authenticated) {
state.foodAppStudWue.currentFoodUserImage = '';
let url = CONFIG.API_ROOT_ACCOUNT + '/food/images/?food_id=' + foodId;
window.axios
.get(url)
.then(response => {
if (response.data.length > 0) {
state.foodAppStudWue.currentFoodUserImage = response.data[0].image.image;
}
})
.catch(e => {
console.error(e)
})
}
},
loadUserImage(state, {foodId}) {
if (state.user.authenticated) {
let url = CONFIG.API_ROOT_ACCOUNT + '/food/images/?food_id=' + foodId;
window.axios
.get(url)
.then(response => {
if (response.data.length > 0) {
state.foodAppStudWue.currentFoodUserImage = response.data[0].image.image;
}
})
.catch(e => {
console.error(e)
})
}
},
setUserImage(state, {image}) {
state.foodAppStudWue.currentFoodUserImage = image;
}
},
actions: {
login(context) {
context.commit('login')
},
logout(context) {
context.commit('logout')
},
loadLocations(context) {
context.commit('loadLocations')
},
loadDefaultImageLocation(context) {
context.commit('loadDefaultImageLocation')
},
loadDetailedFood(context, foodId) {
context.commit('loadDetailedFood', foodId)
},
loadUserImage(context, foodId) {
context.commit('loadUserImage', foodId)
},
setUserImage(context, image) {
context.commit('setUserImage', image)
},
},
}); });