Skip to content

Commit

Permalink
fix(search): filters and design (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
BrocksiNet authored Jun 6, 2023
1 parent b294182 commit c7c5fa7
Show file tree
Hide file tree
Showing 12 changed files with 549 additions and 355 deletions.
8 changes: 5 additions & 3 deletions packages/composables/src/useListing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,15 +467,17 @@ export function createListingComposable<ELEMENTS_TYPE>({
const appliedFilters = Object.assign({}, getCurrentFilters.value, filter, {
query: getCurrentFilters.value.search,
});
_storeAppliedListing.value.currentFilters = appliedFilters;
if (_storeAppliedListing.value) {
_storeAppliedListing.value.currentFilters = appliedFilters;
}
return search(appliedFilters);
};

const resetFilters = () => {
const defaultFilters = Object.assign(
{
manufacturer: [],
properties: [],
manufacturer: "",
properties: "",
price: { min: 0, max: 0 },
search: getCurrentFilters.value.search,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ watch(enter, (value) => {
{{ $t("search.see") }}
<span v-if="getTotal !== 1">{{ $t("search.all") }}</span>
{{ getTotal }}
<span>{{ $tc("search.result", getTotal) }}</span>
<span>{{ $t("search.result", getTotal) }}</span>
</NuxtLink>
<div v-else>{{ $t("search.noResults") }}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup lang="ts">
import { ListingFilter } from "@shopware-pwa/types";
import ListingFiltersPrice from "./ListingFiltersPrice.vue";
import ListingFiltersProperties from "./ListingFiltersProperties.vue";
import ListingFiltersRating from "./ListingFiltersRating.vue";
import ListingFiltersShippingFree from "./ListingFiltersShippingFree.vue";
const emit = defineEmits<{
(
e: "selectFilterValue",
{ code, value }: { code: string; value: string }
): void;
}>();
const props = defineProps<{
filter: ListingFilter;
selectedFilters: {
[key: string]: object;
};
}>();
const cmsMap = () => {
const map: {
[key: string]: object;
} = {
manufacturer: ListingFiltersProperties,
properties: ListingFiltersProperties,
price: ListingFiltersPrice,
rating: ListingFiltersRating,
"shipping-free": ListingFiltersShippingFree,
};
return map[props.filter?.code];
};
</script>
<template>
<div>
<component
:is="cmsMap()"
:filter="filter"
:selected-filters="selectedFilters"
@select-value="emit('selectFilterValue', $event)"
/>
</div>
</template>
185 changes: 149 additions & 36 deletions templates/vue-demo-store/components/listing-filters/ListingFilters.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,168 @@
<script setup lang="ts">
import { ListingFilterCode } from "@shopware-pwa/types";
import ListingFilter from "./ListingFilter.vue";
import {
ComputedRef,
UnwrapNestedRefs,
computed,
provide,
reactive,
} from "vue";
const { getAvailableFilters, setCurrentFilters, resetFilters } = useListing({
const {
filtersToQuery,
getCurrentFilters,
getCurrentSortingOrder,
getInitialFilters,
search,
} = useListing({
listingType: "productSearchListing",
});
const mapComponent = (code: ListingFilterCode): string => {
const components: { [key: string]: string } = {
manufacturer: "ListingFiltersManufacturer",
properties: "ListingFiltersProperties",
price: "ListingFiltersPrice",
rating: "ListingFiltersRating",
"shipping-free": "ListingFiltersShippingFree",
};
const route = useRoute();
const router = useRouter();
return components[code];
};
// @ToDo: clean up the "any" inside searchSelectedFilters
/* eslint-disable */
const searchSelectedFilters: UnwrapNestedRefs<{
[key: string]: any;
}> = reactive<{
manufacturer: Set<any>;
properties: Set<any>;
"min-price": string | number | undefined;
"max-price": string | number | undefined;
rating: string | number | undefined;
"shipping-free": boolean | undefined;
}>({
manufacturer: new Set(),
properties: new Set(),
"min-price": undefined,
"max-price": undefined,
rating: undefined,
"shipping-free": undefined,
});
/* eslint-enable */
const dirty = ref(false);
const searchCriteriaForRequest: ComputedRef<{
[code: string]: string | string[] | number | number[] | boolean | undefined;
}> = computed(() => ({
// turn Set to array and then into string with | separator
manufacturer: [...searchSelectedFilters.manufacturer]?.join("|"),
// turn Set to array and then into string with | separator
properties: [...searchSelectedFilters.properties]?.join("|"),
"min-price": searchSelectedFilters["min-price"],
"max-price": searchSelectedFilters["max-price"],
order: getCurrentSortingOrder.value,
"shipping-free": searchSelectedFilters["shipping-free"],
rating: searchSelectedFilters["rating"],
}));
const updateFilter = (filter: {
for (const param in route.query) {
if (searchSelectedFilters.hasOwnProperty(param)) {
if (
searchSelectedFilters[param] &&
typeof searchSelectedFilters[param] === "object"
) {
(route.query[param] as unknown as string)
.split("|")
.forEach((element) => {
searchSelectedFilters[param].add(element);
});
} else {
searchSelectedFilters[param] = route.query[param];
}
}
}
const onOptionSelectToggle = async ({
code,
value,
}: {
code: string;
value: string | boolean | number;
value: string;
}) => {
setCurrentFilters({ [filter.code]: filter.value });
dirty.value = true;
if (!["properties", "manufacturer"].includes(code)) {
searchSelectedFilters[code] = value;
} else {
if (searchSelectedFilters[code]?.has(value)) {
searchSelectedFilters[code]?.delete(value);
} else {
searchSelectedFilters[code]?.add(value);
}
}
await search({
...searchCriteriaForRequest.value,
...route.query,
});
await router.push({
query: {
query: route.query.query,
...filtersToQuery(searchCriteriaForRequest.value),
},
});
};
const handleReset = () => {
dirty.value = false;
resetFilters();
const clearFilters = async () => {
searchSelectedFilters.manufacturer.clear();
searchSelectedFilters.properties.clear();
searchSelectedFilters["min-price"] = undefined;
searchSelectedFilters["max-price"] = undefined;
searchSelectedFilters["rating"] = undefined;
searchSelectedFilters["shipping-free"] = undefined;
search({
...route.query,
...filtersToQuery(searchCriteriaForRequest.value),
});
await router.push({
query: {
query: route.query.query,
...filtersToQuery(searchCriteriaForRequest.value),
},
});
};
async function invokeCleanFilters() {
await search({
...route.query,
});
clearFilters();
}
const selectedOptionIds = computed(() => [
...searchSelectedFilters.properties,
...searchSelectedFilters.manufacturer,
]);
provide("selectedOptionIds", selectedOptionIds);
</script>

<template>
<div class="flex flex-wrap gap-5">
<component
:is="mapComponent(filter.code)"
v-for="filter in getAvailableFilters"
:key="filter.code"
:filter="filter"
@select-value="updateFilter"
/>

<button
v-show="dirty"
class="justify-center py-2 px-6 border border-transparent shadow-sm text-md font-medium rounded-md text-white bg-black hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
type="button"
@click="handleReset"
>
{{ $t("listing.resetFilters") }}
</button>
<div v-if="getInitialFilters.length" class="flex flex-wrap">
<div
v-for="filter in getInitialFilters"
:key="`${filter?.id || filter?.code}`"
class="mb-2 w-full"
>
<ListingFilter
class="relative"
:selected-filters="getCurrentFilters"
:filter="filter"
@select-filter-value="onOptionSelectToggle"
/>
</div>
<div v-if="selectedOptionIds.length !== 0" class="mx-auto mt-4 mb-2">
<button
class="w-full justify-center py-2 px-6 border border-transparent shadow-sm text-md font-medium rounded-md text-white bg-black hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
type="button"
@click="invokeCleanFilters"
>
{{ $t("search.resetFilters") }}
<span
class="w-6 h-6 i-carbon-close-filled inline-block align-middle ml-2"
></span>
</button>
</div>
</div>
</div>
</template>

This file was deleted.

Loading

0 comments on commit c7c5fa7

Please sign in to comment.