Skip to content

Commit

Permalink
Merge pull request #14 from D-I-S-H/menu
Browse files Browse the repository at this point in the history
Filtering by tags and ingredients/allergens
  • Loading branch information
That-Thing authored Nov 9, 2024
2 parents 9c93b75 + 34be636 commit c796208
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 12 deletions.
10 changes: 10 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,13 @@ a.text-light:hover {
color: var(--color-text);
border-color: var(--color-secondary-hover);
}

.exclude-tag {
max-width: 10em; /* Adjust width as needed */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
124 changes: 112 additions & 12 deletions src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import axios from 'axios';
import { useToast } from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import MenuItemComponent from '../components/menuItemComponent.vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faFilter } from '@fortawesome/free-solid-svg-icons';
const API_URL = import.meta.env.VITE_APP_API_URL;
const $toast = useToast();
Expand All @@ -17,12 +19,19 @@ const selectedLocation = ref(localStorage.getItem('selectedLocation') || locatio
const selectedTime = ref(null); // Selected time
const selectedDate = ref(new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' })); // YYYY-MM-DD
// Pagination state
const currentPage = ref(1);
const itemsPerPage = 10;
// Search state
const searchQuery = ref(''); // Search query
const selectedLabels = ref([]); // Selected labels for filtering
const showFilters = ref(false); // Toggle state for filters
// Custom tag filter state
const excludeTags = ref([]); // List of tags to exclude
const tagInput = ref(''); // Input field for new tag
// Fetch locations from the API
axios.get(`${API_URL}/locations`)
Expand Down Expand Up @@ -112,53 +121,93 @@ const changeTime = (time) => {
currentPage.value = 1;
};
const uniqueLabels = computed(() => {
return [...new Set(menuItems.value.flatMap(item => item.labels || []))];
});
// Add new tag to excludeTags when pressing Enter
const addTag = () => {
const tag = tagInput.value.trim();
if (tag && !excludeTags.value.includes(tag)) {
excludeTags.value.push(tag);
}
tagInput.value = ''; // Clear input after adding the tag
};
// Remove a tag from excludeTags
const removeTag = (tag) => {
excludeTags.value = excludeTags.value.filter(t => t !== tag);
};
// Filter menu items based on selectedTime, searchQuery, selectedLabels, and excludeTags
const filteredMenuItems = computed(() => {
return menuItems.value
.filter(item => item.time === selectedTime.value)
.filter(item =>
(item.name.toLowerCase().includes(searchQuery.value.toLowerCase())) ||
item.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
((item.description || '').toLowerCase().includes(searchQuery.value.toLowerCase()))
)
.filter(item =>
// Check if item.labels contains all selected labels
selectedLabels.value.length === 0 || selectedLabels.value.every(label => (item.labels || []).includes(label))
)
.filter(item =>
// Exclude items that have any of the tags in excludeTags in either ingredients or allergens, case-insensitively and ignoring trailing asterisks
excludeTags.value.length === 0 || !excludeTags.value.some(tag => {
const lowercaseTag = tag.toLowerCase();
// Normalize ingredients and allergens by removing trailing asterisk and converting to lowercase
const ingredients = (item.ingredients || []).map(i => i.toLowerCase().replace(/\*$/, ''));
const allergens = (item.allergens || []).map(a => a.toLowerCase().replace(/\*$/, ''));
return ingredients.includes(lowercaseTag) || allergens.includes(lowercaseTag);
})
);
});
const paginationPages = computed(() => {
const pages = [];
const range = 2; // Number of pages to display before and after the current page
const range = 2;
for (let i = currentPage.value - range; i <= currentPage.value + range; i++) {
if (i > 0 && i <= totalPages.value) {
pages.push(i);
}
}
return pages;
});
const paginatedMenuItems = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage;
const end = start + itemsPerPage;
return filteredMenuItems.value.slice(start, end);
return filteredMenuItems.value.slice(start, start + itemsPerPage);
});
// Calculate total pages
const totalPages = computed(() => Math.ceil(filteredMenuItems.value.length / itemsPerPage));
// Change page
const changePage = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page;
}
};
const toggleLabel = (label) => {
const index = selectedLabels.value.indexOf(label);
if (index === -1) selectedLabels.value.push(label);
else selectedLabels.value.splice(index, 1);
};
</script>

<template>
<div class="container mt-5">
<div class="row">
<div class="col-lg-8 mb-3">
<!-- Filters card -->
<div class="card mb-3">
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-center justify-content-md-start">
<!-- Time select -->
<button
v-for="time in times"
:key="time"
Expand All @@ -168,10 +217,56 @@ const changePage = (page) => {
>
{{ time }}
</button>

<!-- Filter button -->
<button
class="btn btn-secondary ms-md-auto"
@click="showFilters = !showFilters"
>
<font-awesome-icon :icon="faFilter" />
{{ showFilters ? 'Hide Filters' : 'Filters' }}
</button>
</div>
<!-- Filters -->
<div v-if="showFilters" class="mt-3">
<h6>Filter by Labels</h6>
<div v-for="label in uniqueLabels" :key="label" class="form-check">
<input
class="form-check-input"
type="checkbox"
:value="label"
v-model="selectedLabels"
>
<label class="form-check-label">
{{ label }}
</label>
</div>
<!-- Exclude Tag Filter -->
<h6 class="mt-3">Exclude Ingredients/Allergens</h6>
<div class="d-flex gap-2 mb-3">
<input
type="text"
class="form-control"
placeholder="Type a tag and press Enter"
v-model="tagInput"
@keyup.enter="addTag"
/>
</div>
<div class="d-flex flex-wrap gap-2">
<span
v-for="tag in excludeTags"
:key="tag"
class="badge bg-danger exclude-tag"
@click="removeTag(tag)"
>
{{ tag }} <font-awesome-icon :icon="faTimes" />
</span>
</div>
</div>
</div>
</div>


<!-- Menu card -->
<div class="card">
<div class="card-body">
<div v-if="loading" class="text-center py-5">
Expand All @@ -197,6 +292,7 @@ const changePage = (page) => {
</div>
</div>
<div class="row row-cols-1 row-cols-md-2 g-4">
<!-- Generate MenuItemComponents for each menu item -->
<div
v-for="menuItem in paginatedMenuItems"
:key="menuItem.id"
Expand All @@ -205,6 +301,7 @@ const changePage = (page) => {
<MenuItemComponent :menuItem="menuItem" />
</div>
</div>
<!-- Pagination -->
<nav v-if="totalPages > 1" aria-label="Page navigation">
<ul class="pagination justify-content-center mt-3">
<li class="page-item" :class="{ 'disabled': currentPage === 1 }">
Expand Down Expand Up @@ -241,7 +338,7 @@ const changePage = (page) => {
</div>
</div>
</div>

<!-- Location select -->
<div class="col-lg-4 mb-3">
<div class="card">
<div class="card-body">
Expand Down Expand Up @@ -276,12 +373,15 @@ export default {
selectedTime,
changeTime,
searchQuery,
selectedLabels,
filteredMenuItems,
paginatedMenuItems,
currentPage,
totalPages,
changePage,
loading
loading,
uniqueLabels,
toggleLabel
};
}
};
Expand Down

0 comments on commit c796208

Please sign in to comment.