Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next #155

Merged
merged 6 commits into from
Jan 5, 2024
Merged

Next #155

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading