Filter cards

This commit is contained in:
Brieuc Dubois 2024-01-10 19:01:53 +01:00
parent ca3811a48b
commit 2092125fc5
4 changed files with 128 additions and 40 deletions

View File

@ -1,18 +1,20 @@
<script lang="ts"> <script lang="ts">
import currentView from '$lib/stores/currentView';
import type Filter from '$lib/types/Filter'; import type Filter from '$lib/types/Filter';
import FilterMenuItem from './FilterMenuItem.svelte'; import FilterMenuItem from './FilterMenuItem.svelte';
import Menu from './Menu.svelte'; import Menu from './Menu.svelte';
export let isOpen = false; export let isOpen = false;
export let filters: Filter[] = [];
</script> </script>
<Menu bind:isOpen> {#if $currentView}
{#each filters as filter} <Menu bind:isOpen>
<FilterMenuItem {filter} /> {#each $currentView.filters as filter}
{/each} <FilterMenuItem {filter} />
<FilterMenuItem /> {/each}
</Menu> <FilterMenuItem />
</Menu>
{/if}
<style lang="less"> <style lang="less">
</style> </style>

View File

@ -3,6 +3,7 @@
import Filter from '$lib/types/Filter'; import Filter from '$lib/types/Filter';
import ProjectTag, { projectTags } from '$lib/types/ProjectTag'; import ProjectTag, { projectTags } from '$lib/types/ProjectTag';
import type TagOption from '$lib/types/TagOption'; import type TagOption from '$lib/types/TagOption';
import TrashIcon from '../icons/TrashIcon.svelte';
import Menu from './Menu.svelte'; import Menu from './Menu.svelte';
export let filter: Filter | null = null; export let filter: Filter | null = null;
@ -14,7 +15,8 @@
async function selectProjectTag(projectTag: ProjectTag) { async function selectProjectTag(projectTag: ProjectTag) {
if (!$currentView) return; if (!$currentView) return;
if (!filter) { if (!filter) {
filter = await $currentView?.addFilter(projectTag, 0, null); await $currentView?.addFilter(projectTag, 0, null);
currentView.reload();
return; return;
} }
@ -47,6 +49,14 @@
} }
isOptionOpen = false; isOptionOpen = false;
} }
async function deleteFilter() {
if (!filter) return;
const res = await $currentView?.removeFilter(filter);
if (!res) return;
currentView.reload();
}
</script> </script>
<div class="item"> <div class="item">
@ -83,7 +93,11 @@
role="button" role="button"
> >
{#if filter} {#if filter}
{filter.filterType} {#if filter.filterType === 0}
is
{:else if filter.filterType === 1}
is not
{/if}
{/if} {/if}
</div> </div>
{#if filter} {#if filter}
@ -124,6 +138,19 @@
</Menu> </Menu>
{/if} {/if}
</div> </div>
{#if filter}
<div
class="delete"
tabindex="0"
role="button"
on:click={() => deleteFilter()}
on:keydown={(e) => {
if (e.key === 'Enter') deleteFilter();
}}
>
<TrashIcon size={20} />
</div>
{/if}
</div> </div>
<style lang="less"> <style lang="less">
@ -134,11 +161,15 @@
.part { .part {
min-width: 50px; min-width: 50px;
}
.part,
.delete {
height: 30px; height: 30px;
margin: 5px; margin: 5px;
text-align: center; text-align: center;
line-height: 30px; line-height: 30px;
padding: 0 5px; padding: 0 2px;
&:hover { &:hover {
background-color: #fff2; background-color: #fff2;
@ -146,6 +177,10 @@
} }
} }
.delete {
line-height: 35px;
}
button { button {
min-width: 100px; min-width: 100px;
text-align: left; text-align: left;

View File

@ -57,8 +57,13 @@
</div> </div>
<button class:disabled={true}>Sub-group</button> <button class:disabled={true}>Sub-group</button>
<div> <div>
<button on:click={() => (filterMenuOpen = !filterMenuOpen)}>Filter</button> <button
<FilterMenu bind:isOpen={filterMenuOpen} filters={$currentView?.filters} /> on:click={() => (filterMenuOpen = !filterMenuOpen)}
class:defined={$currentView?.filters && $currentView?.filters.length > 0}
>
Filter
</button>
<FilterMenu bind:isOpen={filterMenuOpen} />
</div> </div>
<div> <div>
<button on:click={() => (sortMenuOpen = !sortMenuOpen)} class:defined={$currentView?.sortTag}> <button on:click={() => (sortMenuOpen = !sortMenuOpen)} class:defined={$currentView?.sortTag}>

View File

@ -1,57 +1,103 @@
<script lang="ts"> <script lang="ts">
import currentView from '$lib/stores/currentView'; import currentView from '$lib/stores/currentView';
import { cards } from '$lib/types/Card'; import Card, { cards } from '$lib/types/Card';
import type Filter from '$lib/types/Filter';
import type Project from '$lib/types/Project'; import type Project from '$lib/types/Project';
import type ProjectTag from '$lib/types/ProjectTag';
import { projectTags } from '$lib/types/ProjectTag';
import type TagOption from '$lib/types/TagOption';
import type View from '$lib/types/View';
import Column from './Column.svelte'; import Column from './Column.svelte';
import Header from './Header.svelte'; import Header from './Header.svelte';
export let project: Project; export let project: Project;
$: allCards = $cards; function cardComparator(a: Card, b: Card, sortTag: ProjectTag | null, sortDirection: number) {
if (!sortTag) return 0;
const aTag = a.cardTags.find((t) => t.projectTag === sortTag);
const bTag = b.cardTags.find((t) => t.projectTag === sortTag);
if (!aTag) return -sortDirection;
if (!bTag) return sortDirection;
const aValue = aTag.value || aTag.option?.value || '';
const bValue = bTag.value || bTag.option?.value || '';
return aValue < bValue ? sortDirection : -sortDirection;
}
function passFilters(card: Card, filters: Filter[]): boolean {
for (const projectTag of $projectTags) {
let is: TagOption[] = [];
const cardTag = card.cardTags.find((t) => t.projectTag === projectTag);
for (const filter of filters) {
if (projectTag !== filter.projectTag) continue;
if (!filter.tagOption) continue;
if (filter.filterType === 0) {
is.push(filter.tagOption);
} else if (filter.filterType === 1) {
if (cardTag?.option === filter.tagOption) return false;
}
}
if (is.length > 0) {
if (!cardTag) return false;
if (!is.some((o) => o === cardTag?.option)) return false;
}
}
return true;
}
function extractColumnCards(view: View | null, cards: Card[], tagOption: TagOption | null) {
if (!view) return cards;
const filteredCards = cards.filter((c) => passFilters(c, view.filters));
const primaryTag = view.primaryTag;
if (!primaryTag) return filteredCards;
if (!tagOption) {
return filteredCards.filter((c) => !c.cardTags.map((t) => t.projectTag).includes(primaryTag));
}
const rightOptionCards = filteredCards.filter((c) => {
const tag = c.cardTags.find((t) => t.projectTag === primaryTag);
if (!tag) return false;
return tag.option?.id === tagOption.id;
});
const sortedCards = rightOptionCards.sort((a, b) =>
cardComparator(a, b, view.sortTag, $currentView?.sortDirection || 1)
);
return sortedCards;
}
</script> </script>
{#if project} {#if project}
<section> <section>
{#if $currentView} {#if $currentView}
<Header {project} /> <Header {project} />
{#if $cards && allCards} {#if $cards}
<div class="grid"> <div class="grid">
{#if $currentView.primaryTag} {#if $currentView.primaryTag}
{#each $currentView.primaryTag.options as option} {#each $currentView.primaryTag.options as option}
<Column <Column
{option} {option}
primaryTag={$currentView.primaryTag} primaryTag={$currentView.primaryTag}
columnCards={allCards columnCards={extractColumnCards($currentView, $cards, option)}
.filter((c) => c.cardTags.map((t) => t.option).includes(option))
.sort((a, b) => {
if (!$currentView?.sortTag) return 0;
const aTag = a.cardTags.find((t) => t.projectTag === $currentView?.sortTag);
const bTag = b.cardTags.find((t) => t.projectTag === $currentView?.sortTag);
if (!aTag) return -($currentView?.sortDirection || 1);
if (!bTag) return $currentView?.sortDirection || 1;
const aValue = aTag.value || aTag.option?.value || '';
const bValue = bTag.value || bTag.option?.value || '';
return aValue < bValue
? $currentView?.sortDirection || 1
: -($currentView?.sortDirection || 1);
})}
{project} {project}
/> />
{/each} {/each}
{/if} {/if}
<Column <Column
primaryTag={$currentView.primaryTag} primaryTag={$currentView.primaryTag}
columnCards={$currentView.primaryTag != null columnCards={extractColumnCards($currentView, $cards, null)}
? (() => {
const primaryTag = $currentView.primaryTag;
return allCards.filter(
(c) => !c.cardTags.map((t) => t.projectTag).includes(primaryTag)
);
})()
: allCards}
{project} {project}
/> />
</div> </div>