Skip to content

Commit

Permalink
Merge pull request #155 from karlomikus/develop
Browse files Browse the repository at this point in the history
Next
  • Loading branch information
karlomikus authored Jan 5, 2024
2 parents 0c0c8b9 + e89ccab commit 62415e7
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 64 deletions.
17 changes: 17 additions & 0 deletions src/ApiRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@ class ApiRequests
return this.parseResponse(jsonResp)
}

static async fetchRecommendedIngredients(query = {}) {
const q = this.generateBAQueryString(query, true)
const jsonResp = await this.getRequest(`/api/ingredients/recommend${q}`)

return jsonResp
}

/**
* =============================
* Shelf
Expand Down Expand Up @@ -724,6 +731,16 @@ class ApiRequests
return this.parseResponse(jsonResp)
}

static async downloadCollection(id) {
let url = `${this.getUrl()}/api/collections/${id}/share?type=csv`

const response = await fetch(url, {
headers: this.getHeaders(),
}).then(this.handleResponseErrors)

return await response.blob()
}

static async fetchSharedCollections(query = {}) {
const queryString = this.generateBAQueryString(query, true)
const jsonResp = await this.getRequest(`/api/collections/shared${queryString}`)
Expand Down
18 changes: 18 additions & 0 deletions src/components/Cocktail/CocktailIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Refinement id="tag" v-model="activeFilters.tags" :searchable="true" :title="$t('tag.tags')" :refinements="refineTags" @change="updateRouterPath"></Refinement>
<Refinement id="glass" v-model="activeFilters.glasses" :title="$t('glass-type.title')" :refinements="refineGlasses" @change="updateRouterPath"></Refinement>
<Refinement id="total-ingredients" v-model="activeFilters.total_ingredients" :title="$t('total.ingredients')" :refinements="refineIngredientsCount" type="radio" @change="updateRouterPath"></Refinement>
<Refinement id="missing-ingredients" v-model="activeFilters.missing_ingredients" :title="$t('missing-ingredients')" :refinements="refineMissingIngredients" type="radio" @change="updateRouterPath"></Refinement>
<Refinement id="user-rating" v-model="activeFilters.user_rating" :title="$t('your-rating')" :refinements="refineRatings" type="radio" @change="updateRouterPath"></Refinement>
<Refinement id="avg-rating" v-model="activeFilters.average_rating" :title="$t('avg-rating')" :refinements="refineRatings" type="radio" @change="updateRouterPath"></Refinement>
<button class="button button--dark sm-show" type="button" @click="showRefinements = false">{{ $t('cancel') }}</button>
Expand Down Expand Up @@ -155,6 +156,11 @@ export default {
{ name: '>= 5 ' + this.$t('ingredients.title'), active: false, id: '5' },
{ name: '>= 7 ' + this.$t('ingredients.title'), active: false, id: '7' },
],
missing_ingredients: [
{ name: '1 ' + this.$t('ingredients.title'), active: false, id: '1' },
{ name: '2 ' + this.$t('ingredients.title'), active: false, id: '2' },
{ name: '>= 3 ' + this.$t('ingredients.title'), active: false, id: '3' },
],
tags: [],
glasses: [],
methods: [],
Expand All @@ -177,6 +183,7 @@ export default {
average_rating: null,
abv: null,
total_ingredients: null,
missing_ingredients: null,
user_shelves: [],
users: []
}
Expand Down Expand Up @@ -277,6 +284,15 @@ export default {
}
})
},
refineMissingIngredients() {
return this.availableRefinements.missing_ingredients.map(m => {
return {
id: m.id,
value: m.id,
name: m.name
}
})
},
refineUsers() {
return this.availableRefinements.members.map(m => {
return {
Expand Down Expand Up @@ -390,6 +406,7 @@ export default {
this.activeFilters.favorites = state.filter && state.filter.favorites ? state.filter.favorites : null
this.activeFilters.is_public = state.filter && state.filter.is_public ? state.filter.is_public : null
this.activeFilters.total_ingredients = state.filter && state.filter.total_ingredients ? state.filter.total_ingredients : null
this.activeFilters.missing_ingredients = state.filter && state.filter.missing_ingredients ? state.filter.missing_ingredients : null
this.activeFilters.user_rating = state.filter && state.filter.user_rating_min ? state.filter.user_rating_min : null
this.activeFilters.average_rating = state.filter && state.filter.average_rating_min ? state.filter.average_rating_min : null
this.searchQuery = state.filter && state.filter.name ? state.filter.name : null
Expand Down Expand Up @@ -424,6 +441,7 @@ export default {
user_rating_min: this.activeFilters.user_rating ? this.activeFilters.user_rating : null,
average_rating_min: this.activeFilters.average_rating ? this.activeFilters.average_rating : null,
total_ingredients: this.activeFilters.total_ingredients ? this.activeFilters.total_ingredients : null,
missing_ingredients: this.activeFilters.missing_ingredients ? this.activeFilters.missing_ingredients : null,
tag_id: this.activeFilters.tags.length > 0 ? this.activeFilters.tags.join(',') : null,
glass_id: this.activeFilters.glasses.length > 0 ? this.activeFilters.glasses.join(',') : null,
cocktail_method_id: this.activeFilters.methods.length > 0 ? this.activeFilters.methods.join(',') : null,
Expand Down
77 changes: 52 additions & 25 deletions src/components/Collections/CollectionIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,31 @@
</template>
</PageHeader>
<OverlayLoader v-if="isLoading" />
<div v-if="collections.length > 0" class="block-container block-container--padded">
<div v-if="collections.length > 0">
<SubscriptionCheck v-if="collections.length >= 3">Subscribe to "Mixologist" plan to create unlimited collections!</SubscriptionCheck>
<table class="table">
<thead>
<tr>
<th>{{ $t('name') }} / {{ $t('description') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="collection in collections" :key="collection.id">
<td>
<RouterLink :to="{ name: 'cocktails', query: { 'filter[collection_id]': collection.id } }">{{ collection.name }}</RouterLink>
<br>
<small>{{ collection.cocktails.length }} {{ $t('cocktails.title') }} &middot; {{ collection.description ? overflowText(collection.description, 100) : 'n/a' }}</small>
</td>
<td style="text-align: right;">
<a class="list-group__action" href="#" @click.prevent="shareCollection(collection)">{{ $t('share.title') }}</a>
&middot;
<a class="list-group__action" href="#" @click.prevent="openDialog($t('collections.edit'), collection)">{{ $t('edit') }}</a>
&middot;
<a class="list-group__action" href="#" @click.prevent="deleteCollection(collection)">{{ $t('remove') }}</a>
</td>
</tr>
</tbody>
</table>
<div class="collections">
<div class="block-container block-container--padded block-container--hover collections__collection" v-for="collection in collections" :key="collection.id">
<RouterLink class="collections__collection__title" :to="{ name: 'cocktails', query: { 'filter[collection_id]': collection.id } }">{{ collection.name }}</RouterLink>
<br>
<div class="collections__collection__content">
{{ collection.cocktails.length }} {{ $t('cocktails.title') }}
<template v-if="collection.is_bar_shared">
&middot; {{ $t('collection-shared') }}
</template>
<br>
{{ $t('description') }}: {{ collection.description ? overflowText(collection.description, 100) : 'n/a' }}
</div>
<div class="collections__collection__action">
<a class="list-group__action" href="#" @click.prevent="shareCollection(collection)">{{ $t('share.title') }}</a>
&middot;
<a class="list-group__action" href="#" @click.prevent="download(collection)">{{ $t('download') }} CSV</a>
&middot;
<a class="list-group__action" href="#" @click.prevent="openDialog($t('collections.edit'), collection)">{{ $t('edit') }}</a>
&middot;
<a class="list-group__action" href="#" @click.prevent="deleteCollection(collection)">{{ $t('remove') }}</a>
</div>
</div>
</div>
</div>
<EmptyState v-else>
<template #icon>
Expand Down Expand Up @@ -128,6 +127,12 @@ export default {
})
})
},
download(collection) {
ApiRequests.downloadCollection(collection.id).then(data => {
var file = window.URL.createObjectURL(data);
window.location.assign(file);
})
},
overflowText(input, len) {
if (!input) {
return input
Expand All @@ -138,3 +143,25 @@ export default {
}
}
</script>
<style scoped>
.collections {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--gap-size-2);
}
@media (max-width: 450px) {
.collections {
grid-template-columns: 1fr;
}
}
.collections__collection__title {
font-size: 1.25rem;
font-weight: var(--fw-bold);
}
.collections__collection__action {
margin-top: 1rem;
}
</style>
7 changes: 7 additions & 0 deletions src/components/Settings/ProfileForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
<input v-model="user.is_shelf_public" :value="true" type="checkbox">
<span>{{ $t('profile-public-shelf') }}</span>
</label>
<label class="form-checkbox">
<input v-model="user.use_parent_as_substitute" :value="true" type="checkbox">
<span>{{ $t('profile-use-parent-as-substitute') }}</span>
</label>
</div>
</template>
<h3 class="form-section-title">{{ $t('data') }}</h3>
Expand Down Expand Up @@ -78,6 +82,7 @@ export default {
isLoading: false,
user: {
is_shelf_public: false,
use_parent_as_substitute: false,
},
currentLocale: this.$i18n.locale
}
Expand All @@ -92,6 +97,7 @@ export default {
if (this.appState.bar.id) {
const barMembership = data.memberships.filter(m => m.bar_id == this.appState.bar.id)
this.user.is_shelf_public = barMembership.length > 0 ? barMembership[0].is_shelf_public : false
this.user.use_parent_as_substitute = barMembership.length > 0 ? barMembership[0].use_parent_as_substitute : false
}
this.isLoading = false
}).catch(e => {
Expand Down Expand Up @@ -120,6 +126,7 @@ export default {
if (appState.bar.id) {
postData.bar_id = appState.bar.id
postData.is_shelf_public = this.user.is_shelf_public
postData.use_parent_as_substitute = this.user.use_parent_as_substitute
}
ApiRequests.updateUser(postData).then(data => {
Expand Down
125 changes: 87 additions & 38 deletions src/components/Shelf/ShelfIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,26 +106,36 @@
</template>
</EmptyState>
</div>
<!-- <div class="list-grid__col" v-if="favoriteIngredients.length > 0">
<h3 class="page-subtitle">{{ $t('your-favorite-ingredients') }}</h3>
<IngredientListContainer>
<IngredientListItem v-for="ingredient in favoriteIngredients" :ingredient="ingredient" :key="ingredient.id">
<template v-slot:content>
<RouterLink :to="{ name: 'cocktails', query: { 'filter[ingredient_id]': ingredient.id, 'filter[favorites]': true } }">
{{ $t('shelf.used-in-cocktails', {total: ingredient.total}) }}
</RouterLink>
</template>
</IngredientListItem>
<RouterLink :to="{ name: 'ingredients' }">{{ $t('view-all') }}</RouterLink>
</IngredientListContainer>
</div>
<div class="list-grid">
<div class="list-grid__col" v-if="stats.top_rated_cocktails.length > 0">
<h3 class="page-subtitle">{{ $t('top-rated-cocktails') }}</h3>
<div class="list-grid__container">
<RouterLink :to="{ name: 'cocktails.show', params: { id: cocktail.cocktail_slug } }" class="shelf-stats-count block-container block-container--hover" v-for="cocktail in stats.top_rated_cocktails" :key="cocktail.id">
<h4>{{ cocktail.name }}</h4>
<small>{{ $t('avg-rating') }}: {{ cocktail.avg_rating }} &middot; {{ $t('votes') }}: {{ cocktail.votes }}</small>
</RouterLink>
</div>
</div>
<div class="list-grid__col" v-if="stats.most_popular_ingredients.length > 0">
<h3 class="page-subtitle">{{ $t('most-popular-ingredients') }}</h3>
<div class="list-grid__container">
<RouterLink :to="{ name: 'ingredients.show', params: { id: ingredient.ingredient_slug } }" class="shelf-stats-count block-container block-container--hover" v-for="ingredient in stats.most_popular_ingredients" :key="ingredient.id">
<h4>{{ ingredient.name }}</h4>
<small>{{ ingredient.cocktails_count }} {{ $t('cocktails.title') }}</small>
</RouterLink>
</div>
</div>
<div class="list-grid__col" v-if="recommendedIngredients.length > 0">
<h3 class="page-subtitle">{{ $t('recommended-ingredients') }}</h3>
<div class="block-recommended">
You can make <strong>{{ shelfPercent }}</strong> of bar cocktails. Add one of the following ingredients to you shelf to increase your cocktail options:
<template v-for="(ing, index) in recommendedIngredients" :key="ing.id">
<RouterLink :to="{ name: 'ingredients.show', params: { id: ing.slug } }">{{ ing.name }}</RouterLink>
<template v-if="index + 1 !== recommendedIngredients.length"> &middot; </template>
</template>
</div>
</div>
<div class="list-grid__col">
<h3 class="page-subtitle">{{ $t('cocktails-top-rated') }}</h3>
<CocktailListContainer v-slot="observer">
<CocktailListItem v-for="cocktail in topRatedCocktails" :cocktail="cocktail" :key="cocktail.id" :observer="observer" />
<RouterLink :to="{ name: 'cocktails', query: { 'sort': '-created_at' } }">{{ $t('view-all') }}</RouterLink>
</CocktailListContainer>
</div> -->
</div>
</template>

Expand Down Expand Up @@ -160,14 +170,19 @@ export default {
shoppingListIngredients: [],
favoriteIngredients: [],
topRatedCocktails: [],
recommendedIngredients: [],
maxItems: 5,
stats: {},
stats: {
top_rated_cocktails: [],
most_popular_ingredients: [],
},
loaders: {
favorites: false,
cocktails: false,
list: false,
stats: false,
favorite_ingredients: false,
recommended: false,
}
}
},
Expand All @@ -177,6 +192,7 @@ export default {
this.loaders.favorites = true
this.loaders.cocktails = true
this.loaders.stats = true
this.loaders.recommended = true
ApiRequests.fetchCocktails({ 'filter[favorites]': true, per_page: this.maxItems, sort: '-favorited_at' }).then(resp => {
this.loaders.favorites = false
Expand All @@ -199,28 +215,24 @@ export default {
ApiRequests.fetchStats().then(data => {
this.loaders.stats = false
this.stats = data
// this.loaders.favorite_ingredients = true;
// const ingredientsToFilter = this.stats.your_top_ingredients.map(i => i.ingredient_id).join(',');
// ApiRequests.fetchIngredients({ 'filter[id]': ingredientsToFilter, per_page: 5 }).then(favIngredients => {
// this.loaders.favorite_ingredients = false;
// this.stats.your_top_ingredients.forEach(i => {
// this.favoriteIngredients.push(Object.assign({ total: i.cocktails_count }, favIngredients.filter(fav => fav.id == i.ingredient_id)[0]));
// })
// });
// this.loaders.favorite_ingredients = true;
// const cocktailsToFilter = this.stats.top_rated_cocktails.map(c => c.cocktail_id).join(',');
// ApiRequests.fetchCocktails({ 'filter[id]': cocktailsToFilter, per_page: 5 }).then(topCocktails => {
// this.loaders.favorite_ingredients = false;
// this.stats.top_rated_cocktails.forEach(c => {
// this.topRatedCocktails.push(topCocktails.data.filter(top => top.id == c.cocktail_id)[0]);
// })
// });
}).catch(() => {
this.loaders.stats = false
this.$toast.error(this.$t('shelf.toasts.stats-error'))
})
this.loaders.recommended = true
ApiRequests.fetchRecommendedIngredients().then(resp => {
this.loaders.recommended = false
this.recommendedIngredients = resp.data
}).catch(() => {
this.loaders.recommended = false
this.$toast.error(this.$t('shelf.toasts.shelf-error'))
})
},
computed: {
shelfPercent() {
return Number(this.stats.total_shelf_cocktails / this.stats.total_cocktails).toLocaleString(undefined,{style: 'percent', minimumFractionDigits:2});;
}
},
methods: {
fetchShoppingList() {
Expand Down Expand Up @@ -290,4 +302,41 @@ export default {
opacity: .7;
font-size: .8rem;
}
.list-grid__container {
display: flex;
flex-direction: column;
gap: var(--gap-size-2);
}
.shelf-stats-count {
padding: var(--gap-size-2) var(--gap-size-3);
display: flex;
flex-direction: column;
text-decoration: none;
}
.shelf-stats-count h4 {
font-size: 1.15rem;
font-family: var(--font-heading);
font-weight: var(--fw-bold);
}
.shelf-stats-count small {
color: var(--clr-gray-500);
}
.block-recommended {
background-color: #F0EFEB;
padding: var(--gap-size-3);
border-radius: var(--radius-3);
}
.dark-theme .block-recommended {
background-color: #271820;
}
.dark-theme .shelf-stats-count small {
color: var(--clr-gray-400);
}
</style>
Loading

0 comments on commit 62415e7

Please sign in to comment.