Bug/general fixes (#450)

* Fix asset link

* remove unused var

* fix no meal-plan returned

* cleanup redundant code

* Fix dates off in UI

* quick set dark/light mode

* user image fixes

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-05-31 18:44:20 -08:00 committed by GitHub
parent 785ab184af
commit 22d9309112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 218 additions and 16352 deletions

16343
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -160,7 +160,7 @@ export const recipeAPI = {
}, },
recipeAssetPath(recipeSlug, assetName) { recipeAssetPath(recipeSlug, assetName) {
return `api/media/recipes/${recipeSlug}/assets/${assetName}`; return `/api/media/recipes/${recipeSlug}/assets/${assetName}`;
}, },
/** Create comment in the Database /** Create comment in the Database

View File

@ -113,6 +113,11 @@ export const userAPI = {
const response = await apiReq.delete(API_ROUTES.usersIdFavoritesSlug(id, slug)); const response = await apiReq.delete(API_ROUTES.usersIdFavoritesSlug(id, slug));
return response.data; return response.data;
}, },
userProfileImage(id) {
if (!id || id === undefined) return;
return `/api/users/${id}/image`;
},
}; };
const deleteErrorText = response => { const deleteErrorText = response => {

View File

@ -67,12 +67,13 @@
</v-card-text> </v-card-text>
<v-row align="center" justify="end"> <v-row align="center" justify="end">
<v-card-actions class="mr-5"> <v-card-actions class="mr-5">
<v-btn color="success" @click="random" v-if="planDays.length > 0" text> <TheButton edit @click="random" v-if="planDays.length > 0" text>
<template v-slot:icon>
mdi-dice-multiple
</template>
{{ $t("general.random") }} {{ $t("general.random") }}
</v-btn> </TheButton>
<v-btn color="success" @click="save" text :disabled="planDays.length == 0"> <TheButton create @click="save" :disabled="planDays.length == 0" />
{{ $t("general.save") }}
</v-btn>
</v-card-actions> </v-card-actions>
</v-row> </v-row>
</v-card> </v-card>
@ -105,6 +106,9 @@ export default {
}, },
watch: { watch: {
startDate(val) {
console.log(val);
},
dateDif() { dateDif() {
this.planDays = []; this.planDays = [];
for (let i = 0; i < this.dateDif; i++) { for (let i = 0; i < this.dateDif; i++) {
@ -132,22 +136,19 @@ export default {
return this.$store.getters.getCurrentGroup; return this.$store.getters.getCurrentGroup;
}, },
actualStartDate() { actualStartDate() {
return Date.parse(this.startDate); if (!this.startDate) return null;
return Date.parse(this.startDate.replaceAll("-", "/"));
}, },
actualEndDate() { actualEndDate() {
return Date.parse(this.endDate); if (!this.endDate) return null;
return Date.parse(this.endDate.replaceAll("-", "/"));
}, },
dateDif() { dateDif() {
let startDate = new Date(this.startDate); if (!this.actualEndDate || !this.actualStartDate) return null;
let endDate = new Date(this.endDate); let dateDif = (this.actualEndDate - this.actualStartDate) / (1000 * 3600 * 24) + 1;
console.log(startDate, endDate);
let dateDif = (endDate - startDate) / (1000 * 3600 * 24) + 1;
if (dateDif < 1) { if (dateDif < 1) {
return null; return null;
} }
return dateDif; return dateDif;
}, },
startComputedDateFormatted() { startComputedDateFormatted() {
@ -179,22 +180,17 @@ export default {
}, },
random() { random() {
this.usedRecipes = [1]; this.usedRecipes = [1];
this.planDays.forEach((element, index) => { this.planDays.forEach((_, index) => {
let recipe = this.getRandom(this.filteredRecipes); let recipe = this.getRandom(this.filteredRecipes);
this.planDays[index]["meals"][0]["slug"] = recipe.slug; this.planDays[index]["meals"][0]["slug"] = recipe.slug;
this.planDays[index]["meals"][0]["name"] = recipe.name; this.planDays[index]["meals"][0]["name"] = recipe.name;
this.usedRecipes.push(recipe); this.usedRecipes.push(recipe);
}); });
}, },
processTime(index) {
let dateText = new Date(this.actualStartDate.valueOf() + 1000 * 3600 * 24 * index);
return dateText;
},
getDate(index) { getDate(index) {
const dateObj = this.processTime(index); const dateObj = new Date(this.actualStartDate.valueOf() + 1000 * 3600 * 24 * index);
return utils.getDateAsPythonDate(dateObj); return utils.getDateAsPythonDate(dateObj);
}, },
async save() { async save() {
const mealBody = { const mealBody = {
group: this.groupSettings.name, group: this.groupSettings.name,
@ -209,7 +205,6 @@ export default {
this.endDate = null; this.endDate = null;
} }
}, },
formatDate(date) { formatDate(date) {
if (!date) return null; if (!date) return null;
@ -227,12 +222,9 @@ export default {
const nextEndDate = new Date(nextMonday); const nextEndDate = new Date(nextMonday);
nextEndDate.setDate(nextEndDate.getDate() + 4); nextEndDate.setDate(nextEndDate.getDate() + 4);
this.startDate = nextMonday.toISOString().substr(0, 10); this.startDate = utils.getDateAsPythonDate(nextMonday);
this.endDate = utils.getDateAsPythonDate(nextEndDate);
this.endDate = nextEndDate.toISOString().substr(0, 10);
}, },
}, },
}; };
</script> </script>
<style></style>

View File

@ -91,7 +91,7 @@ export default {
this.hideImage == false; this.hideImage == false;
}, },
getProfileImage(id) { getProfileImage(id) {
return `api/users/${id}/image`; return api.users.userProfileImage(id);
}, },
editComment(id) { editComment(id) {
this.$set(this.editKeys, id, true); this.$set(this.editKeys, id, true);

View File

@ -21,9 +21,7 @@
<v-btn color="error" icon @click="deleteAsset(i)" top> <v-btn color="error" icon @click="deleteAsset(i)" top>
<v-icon>{{ $globals.icons.delete }}</v-icon> <v-icon>{{ $globals.icons.delete }}</v-icon>
</v-btn> </v-btn>
<v-btn color="primary" icon @click="copyLink(item.name, item.fileName)" top> <TheCopyButton :copy-text="copyLink(item.fileName)" />
<v-icon>mdi-content-copy</v-icon>
</v-btn>
</div> </div>
</v-list-item-action> </v-list-item-action>
</v-list-item> </v-list-item>
@ -60,6 +58,7 @@
</template> </template>
<script> <script>
import TheCopyButton from "@/components/UI/Buttons/TheCopyButton";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import BaseDialog from "@/components/UI/Dialogs/BaseDialog"; import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
import { api } from "@/api"; import { api } from "@/api";
@ -67,6 +66,7 @@ export default {
components: { components: {
BaseDialog, BaseDialog,
TheUploadBtn, TheUploadBtn,
TheCopyButton,
}, },
props: { props: {
slug: String, slug: String,
@ -130,13 +130,9 @@ export default {
deleteAsset(index) { deleteAsset(index) {
this.value.splice(index, 1); this.value.splice(index, 1);
}, },
copyLink(name, fileName) { copyLink(fileName) {
const assetLink = api.recipes.recipeAssetPath(this.slug, fileName); const assetLink = api.recipes.recipeAssetPath(this.slug, fileName);
const copyText = `![${name}](${assetLink})`; return `<img src="${this.baseURL}${assetLink}" height="100%" width="100%"> </img>`;
navigator.clipboard.writeText(copyText).then(
() => console.log("Copied", copyText),
() => console.log("Copied Failed", copyText)
);
}, },
}, },
}; };

View File

@ -4,6 +4,7 @@
:small="small" :small="small"
:x-small="xSmall" :x-small="xSmall"
:loading="loading" :loading="loading"
:disabled="disabled"
@click="$emit('click')" @click="$emit('click')"
:outlined="btnStyle.outlined" :outlined="btnStyle.outlined"
:text="btnStyle.text" :text="btnStyle.text"
@ -49,6 +50,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
disabled: {
type: Boolean,
default: false,
},
// Styles // Styles
small: { small: {
type: Boolean, type: Boolean,

View File

@ -18,6 +18,9 @@
</div> </div>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon class="mr-1" small @click="isDark = !isDark">
<v-icon v-text="isDark ? 'mdi-weather-night' : 'mdi-weather-sunny'"> </v-icon>
</v-btn>
<div v-if="!isMobile" style="width: 350px;"> <div v-if="!isMobile" style="width: 350px;">
<SearchBar :show-results="true" @selected="navigateFromSearch" :max-width="isMobile ? '100%' : '450px'" /> <SearchBar :show-results="true" @selected="navigateFromSearch" :max-width="isMobile ? '100%' : '450px'" />
</div> </div>
@ -67,12 +70,25 @@ export default {
isMobile() { isMobile() {
return this.$vuetify.breakpoint.name === "xs"; return this.$vuetify.breakpoint.name === "xs";
}, },
isDark: {
get() {
return this.$store.getters.getIsDark;
},
set() {
let setVal = "dark";
if (this.isDark) {
setVal = "light";
}
this.$store.commit("setDarkMode", setVal);
},
},
}, },
methods: { methods: {
navigateFromSearch(slug) { navigateFromSearch(slug) {
this.$router.push(`/recipe/${slug}`); this.$router.push(`/recipe/${slug}`);
}, },
openSidebar() { openSidebar() {
console.log(this.isDarkMode);
this.$refs.theSidebar.toggleSidebar(); this.$refs.theSidebar.toggleSidebar();
}, },
}, },

View File

@ -2,19 +2,7 @@
<div class="d-print-none no-print"> <div class="d-print-none no-print">
<v-navigation-drawer v-model="showSidebar" width="180px" clipped app> <v-navigation-drawer v-model="showSidebar" width="180px" clipped app>
<template v-slot:prepend> <template v-slot:prepend>
<v-list-item two-line v-if="isLoggedIn" to="/admin/profile"> <UserAvatar v-if="isLoggedIn" :user="user" />
<v-list-item-avatar color="accent" class="white--text">
<img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
<div v-else>
{{ initials }}
</div>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title> {{ user.fullName }}</v-list-item-title>
<v-list-item-subtitle> {{ user.admin ? $t("user.admin") : $t("user.user") }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item dense v-if="isLoggedIn" :to="`/user/${user.id}/favorites`"> <v-list-item dense v-if="isLoggedIn" :to="`/user/${user.id}/favorites`">
<v-list-item-icon> <v-list-item-icon>
@ -46,7 +34,7 @@
mdi-heart mdi-heart
</v-icon> </v-icon>
</v-list-item-icon> </v-list-item-icon>
<v-list-item-title> {{$t('about.support')}} </v-list-item-title> <v-list-item-title> {{ $t("about.support") }} </v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item to="/admin/about"> <v-list-item to="/admin/about">
<v-list-item-icon class="mr-3 pt-1"> <v-list-item-icon class="mr-3 pt-1">
@ -78,10 +66,14 @@
</template> </template>
<script> <script>
import UserAvatar from "@/components/User/UserAvatar";
import { initials } from "@/mixins/initials"; import { initials } from "@/mixins/initials";
import { user } from "@/mixins/user"; import { user } from "@/mixins/user";
import axios from "axios"; import axios from "axios";
export default { export default {
components: {
UserAvatar,
},
mixins: [initials, user], mixins: [initials, user],
data() { data() {
return { return {

View File

@ -0,0 +1,61 @@
<template>
<v-list-item two-line to="/admin/profile">
<v-list-item-avatar color="accent" class="white--text">
<v-img :src="profileImage" v-if="!noImage" />
<div v-else>
{{ initials }}
</div>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title> {{ user.fullName }}</v-list-item-title>
<v-list-item-subtitle> {{ user.admin ? $t("user.admin") : $t("user.user") }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
<script>
import { initials } from "@/mixins/initials";
import axios from "axios";
import { api } from "@/api";
export default {
mixins: [initials],
props: {
user: {
type: Object,
},
},
data() {
return {
noImage: false,
profileImage: "",
};
},
watch: {
async user() {
this.setImage();
},
},
methods: {
async setImage() {
const userImageURL = api.users.userProfileImage(this.user.id);
if (await this.imageExists(userImageURL)) {
this.noImage = false;
this.profileImage = userImageURL;
} else {
this.noImage = true;
}
},
async imageExists(url) {
const response = await axios.get(url).catch(() => {
this.noImage = true;
return { status: 404 };
});
return response.status !== 404;
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -73,8 +73,7 @@
</v-virtual-scroll> </v-virtual-scroll>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <TheButton class="ml-auto mt-1 mb-n1" create @click="createTheme" />
<TheButton class="mt-1 mb-n1" create @click="createTheme" />
</v-card-actions> </v-card-actions>
</template> </template>
</StatCard> </StatCard>

View File

@ -134,7 +134,7 @@ export default {
computed: { computed: {
userProfileImage() { userProfileImage() {
return `api/users/${this.user.id}/image`; return api.users.userProfileImage(this.user.id);
}, },
}, },

View File

@ -17,7 +17,7 @@ function inDarkMode(payload) {
const state = { const state = {
activeTheme: {}, activeTheme: {},
darkMode: "light", darkMode: "light", // light, dark, system
isDark: false, isDark: false,
isLoggedIn: false, isLoggedIn: false,
token: "", token: "",

View File

@ -6,10 +6,10 @@ import { store } from "@/store";
export const utils = { export const utils = {
recipe: recipe, recipe: recipe,
generateUniqueKey(item, index) { generateUniqueKey(item, index) {
const uniqueKey = `${item}-${index}`; return `${item}-${index}`;
return uniqueKey;
}, },
getDateAsPythonDate(dateObject) { getDateAsPythonDate(dateObject) {
if (!dateObject) return null;
const month = dateObject.getMonth() + 1; const month = dateObject.getMonth() + 1;
const day = dateObject.getDate(); const day = dateObject.getDate();
const year = dateObject.getFullYear(); const year = dateObject.getFullYear();

View File

@ -23,6 +23,28 @@ def get_all_meals(
return db.groups.get_meals(session, current_user.group) return db.groups.get_meals(session, current_user.group)
@router.get("/this-week", response_model=MealPlanOut)
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
""" Returns the meal plan data for this week """
plans = db.groups.get_meals(session, current_user.group)
print(plans)
if plans:
return plans[0]
@router.get("/today", tags=["Meal Plan"])
def get_today(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
"""
Returns the recipe slug for the meal scheduled for today.
If no meal is scheduled nothing is returned
"""
group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name")
recipe = get_todays_meal(session, group_in_db)
if recipe:
return recipe
@router.get("/{id}", response_model=MealPlanOut) @router.get("/{id}", response_model=MealPlanOut)
def get_meal_plan( def get_meal_plan(
id, id,
@ -86,27 +108,6 @@ def delete_meal_plan(
raise HTTPException(status.HTTP_400_BAD_REQUEST) raise HTTPException(status.HTTP_400_BAD_REQUEST)
@router.get("/this-week", response_model=MealPlanOut)
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
""" Returns the meal plan data for this week """
plans = db.groups.get_meals(session, current_user.group)
if plans:
return plans[0]
@router.get("/today", tags=["Meal Plan"])
def get_today(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
"""
Returns the recipe slug for the meal scheduled for today.
If no meal is scheduled nothing is returned
"""
group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name")
recipe = get_todays_meal(session, group_in_db)
if recipe:
return recipe
@router.get("/today/image", tags=["Meal Plan"]) @router.get("/today/image", tags=["Meal Plan"])
def get_todays_image(session: Session = Depends(generate_session), group_name: str = "Home"): def get_todays_image(session: Session = Depends(generate_session), group_name: str = "Home"):
""" """

View File

@ -91,7 +91,7 @@ async def get_user_image(id: str):
for recipe_image in user_dir.glob("profile_image.*"): for recipe_image in user_dir.glob("profile_image.*"):
return FileResponse(recipe_image) return FileResponse(recipe_image)
else: else:
return False raise HTTPException(status.HTTP_404_NOT_FOUND)
@router.post("/{id}/image", dependencies=[Depends(get_current_user)]) @router.post("/{id}/image", dependencies=[Depends(get_current_user)])