Skip to content

Add product filter and sort #1482

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

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
83 changes: 83 additions & 0 deletions components/Products/ProductsFilters.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<div class="w-full md:w-64 flex-shrink-0">
<div class="bg-white px-8 pb-8 sm:px-6 sm:pb-6 rounded-lg shadow-sm">
<div class="mb-8">
<h3 class="font-semibold mb-4">PRODUKT TYPE</h3>
<div class="space-y-2">
<CommonCheckbox
v-for="type in productTypes"
:key="type.id"
:id="type.id"
:label="type.name"
:checked="type.checked"
@change="() => toggleProductType(type.id)"
/>
</div>
</div>

<div class="mb-8">
<h3 class="font-semibold mb-4">PRIS</h3>
<CommonRangeSlider
id="price-range"
label="Pris"
:min="0"
:max="1000"
:value="priceRange[1]"
:startValue="priceRange[0]"
:disabled="false"
@input="(value) => setPriceRange([priceRange[0], value])"
/>
</div>

<div class="mb-8">
<h3 class="font-semibold mb-4">STØRRELSE</h3>
<div class="grid grid-cols-3 gap-2">
<CommonButton
v-for="size in sizes"
:key="size"
:selected="selectedSizes.includes(size)"
variant="filter"
@click="() => toggleSize(size)"
>
{{ size }}
</CommonButton>
</div>
</div>

<div class="mb-8">
<h3 class="font-semibold mb-4">FARGE</h3>
<div class="grid grid-cols-3 gap-2">
<CommonColorSwatch
v-for="color in colors"
:key="color.name"
:color="color.hex"
:title="color.name"
:class="{ 'ring-2 ring-offset-2 ring-gray-900': selectedColors.includes(color.name) }"
@click="() => toggleColor(color.name)"
style="cursor:pointer"
/>
</div>
</div>

<CommonButton variant="reset" class="mt-4 w-full" @click="resetFilters">
Resett filter
</CommonButton>
</div>
</div>
</template>

<script setup>
defineProps({
productTypes: Array,
toggleProductType: Function,
priceRange: Array,
setPriceRange: Function,
sizes: Array,
selectedSizes: Array,
toggleSize: Function,
colors: Array,
selectedColors: Array,
toggleColor: Function,
resetFilters: Function,
});
</script>
14 changes: 8 additions & 6 deletions components/Products/ProductsShowAll.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,23 @@
import ProductImage from "@/components/Products/ProductImage.vue";
import ProductPrice from "@/components/Products/ProductPrice.vue";

const props = defineProps({

Check failure

Code scanning / CodeQL

Assignment to constant Error

Assignment to variable props, which is
declared
constant.
categoryId: { type: String, required: false },
categorySlug: { type: String, required: false },
});

const config = useRuntimeConfig();

const products = computed(() => {
return (
allCategoryProducts.value?.productCategory?.products?.nodes ||
allProducts.value?.products?.nodes ||
[]
);
const props = defineProps({
products: {
type: Array,
required: true,
default: () => [],
},
});

const products = computed(() => props.products);

/**
* Returns the path and query parameters for a product link.
*
Expand Down
15 changes: 15 additions & 0 deletions components/Products/ProductsSort.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div class="flex items-center gap-2">
<label for="sort-select" class="text-base text-gray-700">Sortering:</label>
<select
id="sort-select"
class="min-w-[140px] border border-gray-200 rounded-lg px-3 py-2 text-base bg-gray-50 text-gray-700 focus:outline-none"
disabled
>
<option value="popular">Populær</option>
<option value="price-low">Pris: Lav til Høy</option>
<option value="price-high">Pris: Høy til Lav</option>
<option value="newest">Nyeste</option>
</select>
</div>
</template>
15 changes: 15 additions & 0 deletions components/common/Checkbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" class="form-checkbox h-4 w-4 text-blue-600 rounded" disabled />
<span class="text-sm">{{ label }}</span>
</label>
</template>

<script setup>
defineProps({
label: {
type: String,
default: '',
},
});
</script>
20 changes: 20 additions & 0 deletions components/common/ColorSwatch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<span
class="inline-block w-8 h-8 rounded-full border-2 border-white shadow"
:style="{ backgroundColor: color }"
:title="title"
></span>
</template>

<script setup>
defineProps({
color: {
type: String,
default: '#3b82f6', // Tailwind blue-500
},
title: {
type: String,
default: '',
},
});
</script>
15 changes: 15 additions & 0 deletions components/common/RangeSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div class="w-full">
<input
type="range"
min="0"
max="1000"
class="w-full accent-blue-600"
disabled
/>
<div class="flex justify-between text-sm mt-1">
<span>kr 0</span>
<span>kr 1000</span>
</div>
</div>
</template>
88 changes: 74 additions & 14 deletions pages/products.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,79 @@
<template>
<ProductsShowAll />
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col md:flex-row gap-8">
<ProductsFilters
:productTypes="productTypes"
:toggleProductType="toggleProductType"
:priceRange="priceRange"
:setPriceRange="setPriceRange"
:sizes="sizes"
:selectedSizes="selectedSizes"
:toggleSize="toggleSize"
:colors="colors"
:selectedColors="selectedColors"
:toggleColor="toggleColor"
:resetFilters="resetFilters"
/>
<div class="flex-1">
<div
class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-8"
>
<h1 class="text-xl sm:text-2xl font-medium text-center sm:text-left">
Produkter
</h1>
<ProductsSort v-model="sortBy" />
</div>
<ProductsShowAll
:sortBy="sortBy"
:selectedSizes="selectedSizes"
:selectedColors="selectedColors"
:priceRange="priceRange"
:productTypes="productTypes"
/>
</div>
</div>
</div>
</template>

<script setup>
useHead({
title: "Products",
titleTemplate: "%s - Nuxt 3 Woocommerce",
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{
hid: "description",
name: "description",
content: "Nuxt 3 Woocommerce",
},
],
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
});
const sortBy = ref("popular");
const selectedSizes = ref([]);
const selectedColors = ref([]);
const priceRange = ref([0, 1000]);
const productTypes = ref([
{ id: "clothing", name: "Clothing", checked: false },
{ id: "tshirts", name: "Tshirts", checked: false },
{ id: "uncategorized", name: "Uncategorized", checked: false },
]);
const sizes = ref(["Large"]);
const colors = ref([{ name: "Blue", hex: "#3b82f6" }]);

function toggleProductType(id) {
productTypes.value = productTypes.value.map((type) =>
type.id === id ? { ...type, checked: !type.checked } : type
);
}
function setPriceRange(newRange) {
priceRange.value = newRange;
}
function toggleSize(size) {
if (selectedSizes.value.includes(size)) {
selectedSizes.value = selectedSizes.value.filter((s) => s !== size);
} else {
selectedSizes.value = [...selectedSizes.value, size];
}
}
function toggleColor(color) {
if (selectedColors.value.includes(color)) {
selectedColors.value = selectedColors.value.filter((c) => c !== color);
} else {
selectedColors.value = [...selectedColors.value, color];
}
}
function resetFilters() {
selectedSizes.value = [];
selectedColors.value = [];
priceRange.value = [0, 1000];
productTypes.value = productTypes.value.map((type) => ({ ...type, checked: false }));
}
</script>
Loading