Filter cards
This commit is contained in:
parent
ca3811a48b
commit
2092125fc5
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue