updated filtering args

This commit is contained in:
Xinrui Chen 2022-09-04 14:12:20 -07:00
parent 3a8f69c4fd
commit e5bf8a431c
14 changed files with 116 additions and 86 deletions

View File

@ -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 });
};

View File

@ -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';

View File

@ -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));

View File

@ -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));

View File

@ -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);
});
});

View File

@ -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();

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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);

View File

@ -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();
};