updated filtering args
This commit is contained in:
parent
3a8f69c4fd
commit
e5bf8a431c
|
@ -23,26 +23,36 @@ export type DefaultFilter = {
|
|||
};
|
||||
|
||||
/** Returns matching product tag from full list of tags */
|
||||
export const findProductId = (tags: Tag[], name: string): string =>
|
||||
export const _findProductId = (tags: Tag[], name: string): string =>
|
||||
tags.find((tag) => tag.name === name)?._id;
|
||||
|
||||
/** Checks whether product has a tagId that matches passed id string */
|
||||
export const productIncludesRef = (product: Product, id: string): boolean =>
|
||||
export const _productIncludesRef = (product: Product, id: string): boolean =>
|
||||
product.tags.map((tag) => tag._ref).includes(id) ? true : false;
|
||||
|
||||
/** Matches tags according to passed value */
|
||||
export const _matchesTag = (product: Product, { value, $tags }: TagArgs): boolean =>
|
||||
product?.tags ? productIncludesRef(product, findProductId($tags, value)) : true;
|
||||
product?.tags ? _productIncludesRef(product, _findProductId($tags, value)) : true;
|
||||
|
||||
/** Rating greater than what is passed in */
|
||||
export const _ratingHigher = (product: Product, { value }: RatingArgs) =>
|
||||
product.rating && avgRating(product.rating) >= Number(value);
|
||||
|
||||
export const _getSingleArgs = (name, rating, tag, products, tags) =>
|
||||
name === 'rating'
|
||||
? { type: 'rating', value: rating, products }
|
||||
: { type: 'tag', value: tag, products, tags };
|
||||
|
||||
export const _getDoubleArgs = (name, otherVal, tags) =>
|
||||
name === 'rating' ? { type: 'tag', value: otherVal, tags } : { type: 'rating', value: otherVal };
|
||||
|
||||
/** Public */
|
||||
|
||||
/** Default value for filter selectors, saved in store */
|
||||
export const defaultFilter = { selectedCat: 0, selectedRating: 0 } as DefaultFilter;
|
||||
|
||||
/** Filters products */
|
||||
export const filterProductsBy = ({ type, value, products, tags = [] }: FilterArgs) =>
|
||||
export const singleFilter = ({ type, value, products, tags = [] }: FilterArgs) =>
|
||||
products.filter((product) => {
|
||||
switch (type) {
|
||||
case 'tag':
|
||||
|
@ -52,3 +62,14 @@ export const filterProductsBy = ({ type, value, products, tags = [] }: FilterArg
|
|||
return _ratingHigher(product, { value });
|
||||
}
|
||||
});
|
||||
|
||||
/** Returns either filter by one or two */
|
||||
export const multiFilter = ({ name, otherVal, rating, tag, $tags, $products }) => {
|
||||
const filterArgs = _getSingleArgs(name, rating, tag, $products, $tags);
|
||||
const filterOne = singleFilter(filterArgs);
|
||||
|
||||
if (!otherVal) return filterOne;
|
||||
|
||||
const otherFilterArgs = _getDoubleArgs(name, otherVal, $tags);
|
||||
return singleFilter({ ...otherFilterArgs, products: filterOne });
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from './utils/parse';
|
||||
export * from './utils/avgRating';
|
||||
export * from './param';
|
||||
export * from './avgRating';
|
||||
export * from './filters';
|
||||
export * from './param';
|
||||
export * from './parse';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { normalize, parseSlug, parseName } from '.';
|
||||
import type { Product } from '$types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
/** Gets parameter from URL of field. Defaults to 'product' */
|
||||
export const getProdParam = (field = 'product'): string =>
|
||||
|
@ -46,3 +47,8 @@ export const goToProduct = ({ detail }) => {
|
|||
};
|
||||
|
||||
export const resetHistory = () => window.history.pushState(..._resetState());
|
||||
|
||||
export const setCurrProdFromParam = (
|
||||
currentProduct: Writable<Product | Record<string, unknown>>,
|
||||
products: Product[]
|
||||
) => currentProduct.set(findProdFromParam(getProdParam(), products));
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { Product } from '$types';
|
||||
|
||||
/** Removes casing and trims trailing/leading whitespace from string */
|
||||
export const normalize = (str: string): string => str.toLowerCase().trim();
|
||||
|
||||
|
@ -6,3 +8,7 @@ export const parseSlug = (slug: string): string => normalize(slug).replaceAll('-
|
|||
|
||||
/** Parses a name to return normalized slug with all spaces replaced with dashes */
|
||||
export const parseName = (name: string): string => normalize(name).replaceAll(' ', '-');
|
||||
|
||||
/** Product name includes value */
|
||||
export const includesName = (name: string, product: Product) =>
|
||||
normalize(product.name).includes(normalize(name));
|
|
@ -1,4 +1,4 @@
|
|||
import { filterProductsBy } from '$helpers';
|
||||
import { singleFilter } from '$helpers';
|
||||
import type { Product, Tag } from '$types';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { testProducts, testTags } from './data';
|
||||
|
@ -6,7 +6,7 @@ import { testProducts, testTags } from './data';
|
|||
describe('filterProducts', () => {
|
||||
test('filters products by passed category tag', () => {
|
||||
expect(
|
||||
filterProductsBy({
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'Exercise',
|
||||
|
@ -14,7 +14,7 @@ describe('filterProducts', () => {
|
|||
})
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
filterProductsBy({
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'Electronics',
|
||||
|
@ -25,7 +25,7 @@ describe('filterProducts', () => {
|
|||
|
||||
test('filter by tag returns nothing if tag does not exist', () => {
|
||||
expect(
|
||||
filterProductsBy({
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'grapes',
|
||||
|
@ -36,11 +36,11 @@ describe('filterProducts', () => {
|
|||
|
||||
test('filters ratings only greater than # passed in', () => {
|
||||
expect(
|
||||
filterProductsBy({ type: 'rating', products: testProducts as Product[], value: 3 })
|
||||
singleFilter({ type: 'rating', products: testProducts as Product[], value: 3 })
|
||||
).toHaveLength(3);
|
||||
|
||||
expect(
|
||||
filterProductsBy({ type: 'rating', products: testProducts as Product[], value: 5 })
|
||||
singleFilter({ type: 'rating', products: testProducts as Product[], value: 5 })
|
||||
).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let tag;
|
||||
import type { Ref } from '$types';
|
||||
import { tags, currentProduct, productsView, products, filters } from '$lib/stores';
|
||||
import { filterProductsBy, resetHistory } from '$helpers';
|
||||
import { singleFilter, resetHistory } from '$helpers';
|
||||
|
||||
export let tag: Ref;
|
||||
const tagName = $tags.find((dirTag) => dirTag._id === tag._ref).name;
|
||||
|
||||
const filter = () => {
|
||||
currentProduct.set({});
|
||||
productsView.set(
|
||||
filterProductsBy({ type: 'tag', value: tagName, products: $products, tags: $tags })
|
||||
singleFilter({ type: 'tag', value: tagName, products: $products, tags: $tags })
|
||||
);
|
||||
filters.set({ ...$filters, selectedCat: tagName });
|
||||
resetHistory();
|
||||
|
|
|
@ -1,46 +1,36 @@
|
|||
<script>
|
||||
import { productsView, products, tags, filters } from '$lib/stores';
|
||||
import { normalize, filterProductsBy } from '$helpers';
|
||||
import { normalize, multiFilter } from '$helpers';
|
||||
export let reset;
|
||||
let { selectedCat, selectedRating } = $filters;
|
||||
|
||||
const stars = ['⭐ ⬆', '⭐⭐ ⬆', '⭐⭐⭐ ⬆', '⭐⭐⭐⭐ ⬆', '⭐⭐⭐⭐⭐ ⬆'];
|
||||
|
||||
const filterProducts = (name, otherVal, { rating = '', tag = '', $tags = [] }) => {
|
||||
const { filterArgs, otherFilterArgs } =
|
||||
name === 'rating'
|
||||
? {
|
||||
filterArgs: { type: 'rating', value: rating, products: $products },
|
||||
otherFilterArgs: { type: 'tag', value: otherVal, tags: $tags }
|
||||
}
|
||||
: {
|
||||
filterArgs: { type: 'tag', value: tag, products: $products, tags: $tags },
|
||||
otherFilterArgs: { type: 'rating', value: otherVal }
|
||||
};
|
||||
|
||||
const filterOne = filterProductsBy(filterArgs);
|
||||
const filterBoth = filterProductsBy({ ...otherFilterArgs, products: filterOne });
|
||||
|
||||
return otherVal ? filterBoth : filterOne;
|
||||
};
|
||||
|
||||
const filter = ({ target: { name, value } }) => {
|
||||
if (Number(value) === 0) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
switch (normalize(name)) {
|
||||
case 'rating':
|
||||
productsView.set(
|
||||
filterProducts(name, selectedCat, { rating: value, tag: selectedCat, $tags })
|
||||
);
|
||||
break;
|
||||
case 'tag':
|
||||
productsView.set(
|
||||
filterProducts(name, selectedRating, { rating: selectedRating, tag: value, $tags })
|
||||
);
|
||||
break;
|
||||
}
|
||||
const ratingArgs = {
|
||||
name,
|
||||
otherVal: selectedCat,
|
||||
rating: value,
|
||||
tag: selectedCat,
|
||||
$tags,
|
||||
$products
|
||||
};
|
||||
const tagArgs = {
|
||||
name,
|
||||
otherVal: selectedRating,
|
||||
rating: selectedRating,
|
||||
tag: value,
|
||||
$tags,
|
||||
$products
|
||||
};
|
||||
|
||||
return normalize(name) === 'rating'
|
||||
? productsView.set(multiFilter(ratingArgs))
|
||||
: productsView.set(multiFilter(tagArgs));
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<script>
|
||||
import { products, productsView } from '$lib/stores';
|
||||
import { normalize } from '$helpers';
|
||||
import { includesName } from '$helpers';
|
||||
|
||||
const searchProducts = (e) => {
|
||||
productsView.set(
|
||||
$products.filter((product) => normalize(product.name).includes(normalize(e.target.value)))
|
||||
);
|
||||
};
|
||||
|
||||
const { container, input } = {
|
||||
container: 'h-12 ml-auto mr-auto flex justify-center items-center',
|
||||
input: 'outline outline-1 w-28 focus:outline-blue-600 p-1 text-sm'
|
||||
productsView.set($products.filter((product) => includesName(e.target.value, product)));
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<input on:input={searchProducts} class={input} placeholder="search" />
|
||||
<div class="container">
|
||||
<input on:input={searchProducts} class="input" placeholder="search" />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply h-12 ml-auto mr-auto flex justify-center items-center;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply outline outline-1 w-28 focus:outline-blue-600 p-1 text-sm;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,19 +33,22 @@
|
|||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { container, sortBar, sortTitle } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
sortBar: 'pl-4 p-2 flex justify-between',
|
||||
sortTitle: 'font-bold'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class={sortBar}>
|
||||
<div class={sortTitle}>Sort</div>
|
||||
<div class="container">
|
||||
<div class="sortBar">
|
||||
<div class="font-bold">Sort</div>
|
||||
</div>
|
||||
{#each sortOptions as option}
|
||||
<SortOption {option} {sort} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply flex flex-col text-sm h-auto mb-4 mr-6;
|
||||
}
|
||||
.sortBar {
|
||||
@apply pl-4 p-2 flex justify-between;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,26 +2,28 @@
|
|||
export let option;
|
||||
export let sort;
|
||||
let current = true;
|
||||
|
||||
const { sortOption, noPointer } = {
|
||||
sortOption: 'flex p-1 pl-5 mr-12 w-full flex justify-between',
|
||||
noPointer: 'pointer-events-none'
|
||||
};
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={sortOption}
|
||||
class="sortOption"
|
||||
on:click={(e) => {
|
||||
sort(e.target.name, current);
|
||||
current = !current;
|
||||
}}
|
||||
name={option}
|
||||
>
|
||||
<p class={noPointer}>{option}</p>
|
||||
<p class={noPointer}>{current ? '▲' : '▼'}</p>
|
||||
<p class="noPointer">{option}</p>
|
||||
<p class="noPointer">{current ? '▲' : '▼'}</p>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.sortOption {
|
||||
@apply p-1 pl-5 mr-12 w-full flex justify-between;
|
||||
}
|
||||
|
||||
.noPointer {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
button:hover {
|
||||
background-image: url('dither.gif');
|
||||
background-repeat: repeat;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script>
|
||||
const back = () => window.history.back()
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col w-screen text-2xl p-24 items-start'><p>This item could not be found!</p>
|
||||
<button on:click={back} class='outline outline-1 p-1 mt-2'>Go back?</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-screen text-2xl p-24 items-start">
|
||||
<p>This item could not be found!</p>
|
||||
<button on:click={() => window.history.back()} class="outline outline-1 p-1 mt-2">Go back?</button
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -21,5 +21,5 @@ export const emotions: Readable<Emotion[] | []> = readable(await fetchEmotionDat
|
|||
export const tags: Readable<Tag[] | []> = readable(await fetchTagData());
|
||||
|
||||
export const productsView: Writable<Product[]> = writable([]);
|
||||
export const currentProduct: Writable<Product | Record<string, never>> = writable({});
|
||||
export const currentProduct: Writable<Product | Record<string, unknown>> = writable({});
|
||||
export const filters = writable(defaultFilter);
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
import Grid from '$lib/Grid.svelte';
|
||||
import Feature from '$lib/Feature/Feature.svelte';
|
||||
import { products, productsView, currentProduct } from '$lib/stores';
|
||||
import { findProdFromParam, getProdParam, goToProduct, resetHistory } from '$helpers';
|
||||
import { getProdParam, goToProduct, resetHistory, setCurrProdFromParam } from '$helpers';
|
||||
|
||||
$: productsView.set($products);
|
||||
|
||||
const load = () => {
|
||||
currentProduct.set(findProdFromParam(getProdParam(), $products));
|
||||
setCurrProdFromParam(currentProduct, $products);
|
||||
if (getProdParam() && JSON.stringify($currentProduct) === '{}') resetHistory();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue