Frontend filter menu
This commit is contained in:
parent
c725d5154e
commit
ca3811a48b
|
@ -5,7 +5,7 @@ async function create(
|
|||
viewId: number,
|
||||
projectTagId: number,
|
||||
filterType: number,
|
||||
tagOptionId: number
|
||||
tagOptionId: number | null
|
||||
): Promise<number | null> {
|
||||
const response = await api.post(`/v1/filters`, {
|
||||
view_id: viewId,
|
||||
|
@ -27,7 +27,7 @@ async function update(
|
|||
viewId: number,
|
||||
projectTagId: number,
|
||||
filterType: number,
|
||||
tagOptionId: number
|
||||
tagOptionId: number | null
|
||||
): Promise<boolean> {
|
||||
const response = await api.put(`/v1/filters/${filterId}`, {
|
||||
view_id: viewId,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import type Filter from '$lib/types/Filter';
|
||||
import FilterMenuItem from './FilterMenuItem.svelte';
|
||||
import Menu from './Menu.svelte';
|
||||
|
||||
export let isOpen = false;
|
||||
export let filters: Filter[] = [];
|
||||
</script>
|
||||
|
||||
<Menu bind:isOpen>
|
||||
{#each filters as filter}
|
||||
<FilterMenuItem {filter} />
|
||||
{/each}
|
||||
<FilterMenuItem />
|
||||
</Menu>
|
||||
|
||||
<style lang="less">
|
||||
</style>
|
|
@ -0,0 +1,155 @@
|
|||
<script lang="ts">
|
||||
import currentView from '$lib/stores/currentView';
|
||||
import Filter from '$lib/types/Filter';
|
||||
import ProjectTag, { projectTags } from '$lib/types/ProjectTag';
|
||||
import type TagOption from '$lib/types/TagOption';
|
||||
import Menu from './Menu.svelte';
|
||||
|
||||
export let filter: Filter | null = null;
|
||||
|
||||
let isProjectTagOpen = false;
|
||||
let isFilterTypeOpen = false;
|
||||
let isOptionOpen = false;
|
||||
|
||||
async function selectProjectTag(projectTag: ProjectTag) {
|
||||
if (!$currentView) return;
|
||||
if (!filter) {
|
||||
filter = await $currentView?.addFilter(projectTag, 0, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter.projectTag.id !== projectTag.id) {
|
||||
const res = await filter.setProjectTag(projectTag);
|
||||
if (!res) return;
|
||||
currentView.reload();
|
||||
}
|
||||
isProjectTagOpen = false;
|
||||
}
|
||||
|
||||
async function selectFilterType(filterType: number) {
|
||||
if (!filter) return;
|
||||
|
||||
if (filter.filterType !== filterType) {
|
||||
const res = await filter.setFilterType(filterType);
|
||||
if (!res) return;
|
||||
currentView.reload();
|
||||
}
|
||||
isFilterTypeOpen = false;
|
||||
}
|
||||
|
||||
async function selectOption(option: TagOption) {
|
||||
if (!filter) return;
|
||||
|
||||
if (filter.tagOption !== option) {
|
||||
const res = await filter.setTagOption(option);
|
||||
if (!res) return;
|
||||
currentView.reload();
|
||||
}
|
||||
isOptionOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="item">
|
||||
<div>
|
||||
<div
|
||||
class="part"
|
||||
on:click={() => (isProjectTagOpen = !isProjectTagOpen)}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') isProjectTagOpen = !isProjectTagOpen;
|
||||
}}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
{#if filter}
|
||||
{filter.projectTag.title}
|
||||
{/if}
|
||||
</div>
|
||||
<Menu bind:isOpen={isProjectTagOpen}>
|
||||
{#each $projectTags as projectTag}
|
||||
<button on:click={() => selectProjectTag(projectTag)}>
|
||||
{projectTag.title}
|
||||
</button>
|
||||
{/each}
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="part"
|
||||
on:click={() => (isFilterTypeOpen = !isFilterTypeOpen)}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') isFilterTypeOpen = !isFilterTypeOpen;
|
||||
}}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
{#if filter}
|
||||
{filter.filterType}
|
||||
{/if}
|
||||
</div>
|
||||
{#if filter}
|
||||
<Menu bind:isOpen={isFilterTypeOpen}>
|
||||
<button on:click={() => selectFilterType(0)}> is </button>
|
||||
<button
|
||||
on:click={() => {
|
||||
selectFilterType(1);
|
||||
isFilterTypeOpen = false;
|
||||
}}
|
||||
>
|
||||
is not
|
||||
</button>
|
||||
</Menu>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="part"
|
||||
on:click={() => (isOptionOpen = !isOptionOpen)}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') isOptionOpen = !isOptionOpen;
|
||||
}}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
{#if filter && filter.tagOption}
|
||||
{filter.tagOption.value}
|
||||
{/if}
|
||||
</div>
|
||||
{#if filter && filter.projectTag}
|
||||
<Menu bind:isOpen={isOptionOpen}>
|
||||
{#each filter.projectTag.options as option}
|
||||
<button on:click={() => selectOption(option)}>
|
||||
{option.value}
|
||||
</button>
|
||||
{/each}
|
||||
</Menu>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.part {
|
||||
min-width: 50px;
|
||||
height: 30px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
padding: 0 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff2;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 100px;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
margin: 2px 5px;
|
||||
}
|
||||
</style>
|
|
@ -7,10 +7,12 @@
|
|||
import type ProjectTag from '$lib/types/ProjectTag';
|
||||
import { projectTags } from '$lib/types/ProjectTag';
|
||||
import { get } from 'svelte/store';
|
||||
import FilterMenu from '../menu/FilterMenu.svelte';
|
||||
|
||||
export let project: Project;
|
||||
let groupMenuOpen = false;
|
||||
let sortMenuOpen = false;
|
||||
let filterMenuOpen = false;
|
||||
|
||||
async function setGroup(projectTag: ProjectTag): Promise<boolean> {
|
||||
const view = get(currentView);
|
||||
|
@ -54,7 +56,10 @@
|
|||
/>
|
||||
</div>
|
||||
<button class:disabled={true}>Sub-group</button>
|
||||
<button class:disabled={true}>Filter</button>
|
||||
<div>
|
||||
<button on:click={() => (filterMenuOpen = !filterMenuOpen)}>Filter</button>
|
||||
<FilterMenu bind:isOpen={filterMenuOpen} filters={$currentView?.filters} />
|
||||
</div>
|
||||
<div>
|
||||
<button on:click={() => (sortMenuOpen = !sortMenuOpen)} class:defined={$currentView?.sortTag}>
|
||||
Sort
|
||||
|
|
|
@ -10,14 +10,14 @@ export default class Filter {
|
|||
private _view: View;
|
||||
private _projectTag: ProjectTag;
|
||||
private _filterType: number;
|
||||
private _tagOption: TagOption;
|
||||
private _tagOption: TagOption | null;
|
||||
|
||||
private constructor(
|
||||
id: number,
|
||||
view: View,
|
||||
projectTag: ProjectTag,
|
||||
filterType: number,
|
||||
tagOption: TagOption
|
||||
tagOption: TagOption | null
|
||||
) {
|
||||
this._id = id;
|
||||
this._view = view;
|
||||
|
@ -42,7 +42,7 @@ export default class Filter {
|
|||
return this._filterType;
|
||||
}
|
||||
|
||||
get tagOption(): TagOption {
|
||||
get tagOption(): TagOption | null {
|
||||
return this._tagOption;
|
||||
}
|
||||
|
||||
|
@ -50,9 +50,9 @@ export default class Filter {
|
|||
view: View,
|
||||
projectTag: ProjectTag,
|
||||
filterType: number,
|
||||
tagOption: TagOption
|
||||
tagOption: TagOption | null
|
||||
): Promise<Filter | null> {
|
||||
const id = await filtersApi.create(view.id, projectTag.id, filterType, tagOption.id);
|
||||
const id = await filtersApi.create(view.id, projectTag.id, filterType, tagOption?.id || null);
|
||||
if (!id) return null;
|
||||
|
||||
return new Filter(id, view, projectTag, filterType, tagOption);
|
||||
|
@ -62,6 +62,54 @@ export default class Filter {
|
|||
return await filtersApi.delete(this.id);
|
||||
}
|
||||
|
||||
async setProjectTag(projectTag: ProjectTag): Promise<boolean> {
|
||||
const res = await filtersApi.update(
|
||||
this.id,
|
||||
this.view.id,
|
||||
projectTag.id,
|
||||
this.filterType,
|
||||
this.tagOption?.id || null
|
||||
);
|
||||
|
||||
if (!res) return false;
|
||||
|
||||
this._projectTag = projectTag;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setFilterType(filterType: number): Promise<boolean> {
|
||||
const res = await filtersApi.update(
|
||||
this.id,
|
||||
this.view.id,
|
||||
this.projectTag.id,
|
||||
filterType,
|
||||
this.tagOption?.id || null
|
||||
);
|
||||
|
||||
if (!res) return false;
|
||||
|
||||
this._filterType = filterType;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setTagOption(tagOption: TagOption | null): Promise<boolean> {
|
||||
const res = await filtersApi.update(
|
||||
this.id,
|
||||
this.view.id,
|
||||
this.projectTag.id,
|
||||
this.filterType,
|
||||
tagOption?.id || null
|
||||
);
|
||||
|
||||
if (!res) return false;
|
||||
|
||||
this._tagOption = tagOption;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static parseAll(json: any): Filter[];
|
||||
static parseAll(json: any, view: View | null): Filter[];
|
||||
|
||||
|
@ -100,11 +148,7 @@ export default class Filter {
|
|||
}
|
||||
|
||||
const tagOption = projectTag.options.find((option) => option.id === json.option_id);
|
||||
if (!tagOption) {
|
||||
toastAlert('Failed to parse filter: tagOption not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Filter(json.id, view, projectTag, json.filter_type, tagOption);
|
||||
return new Filter(json.id, view, projectTag, json.filter_type, tagOption || null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ export default class View {
|
|||
return this._sortDirection;
|
||||
}
|
||||
|
||||
get filters(): Filter[] {
|
||||
return this._filters;
|
||||
}
|
||||
|
||||
static fromId(id: number): View | null {
|
||||
for (const view of get(views)) {
|
||||
if (view.id === id) {
|
||||
|
@ -152,13 +156,17 @@ export default class View {
|
|||
return true;
|
||||
}
|
||||
|
||||
async addFilter(projectTag: ProjectTag, filterType: number, option: TagOption): Promise<boolean> {
|
||||
async addFilter(
|
||||
projectTag: ProjectTag,
|
||||
filterType: number,
|
||||
option: TagOption | null
|
||||
): Promise<Filter | null> {
|
||||
const filter = await Filter.create(this, projectTag, filterType, option);
|
||||
if (!filter) return false;
|
||||
if (!filter) return null;
|
||||
|
||||
this._filters = [...this._filters, filter];
|
||||
|
||||
return true;
|
||||
return filter;
|
||||
}
|
||||
|
||||
async removeFilter(filter: Filter): Promise<boolean> {
|
||||
|
|
Loading…
Reference in New Issue