TSK-1404 Improve sprint filter (#3164)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2023-05-11 14:26:17 +07:00
committed by GitHub
parent 2e6181acf1
commit bbf81ada7c
11 changed files with 339 additions and 74 deletions
@@ -0,0 +1,168 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, DocumentQuery, FindResult, Ref, SortingOrder } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import presentation, { getClient } from '@hcengineering/presentation'
import { Project, Sprint, SprintStatus } from '@hcengineering/tracker'
import ui, { deviceOptionsStore, Icon, Label, CheckBox, Loading, resizeObserver } from '@hcengineering/ui'
import view, { Filter } from '@hcengineering/view'
import { createEventDispatcher, onMount } from 'svelte'
import tracker from '../../plugin'
import { sprintStatusAssets } from '../../types'
import SprintTitlePresenter from './SprintTitlePresenter.svelte'
export let _class: Ref<Class<Doc>>
export let space: Ref<Project> | undefined = undefined
export let filter: Filter
export let onChange: (e: Filter) => void
filter.modes = filter.modes ?? [view.filter.FilterObjectIn, view.filter.FilterObjectNin]
filter.mode = filter.mode ?? filter.modes[0]
const dispatch = createEventDispatcher()
let search: string = ''
let phTraslate: string = ''
let searchInput: HTMLInputElement
$: translate(presentation.string.Search, {}).then((res) => {
phTraslate = res
})
let values: Sprint[] = []
let objectsPromise: Promise<FindResult<Sprint>> | undefined = undefined
let selectedValues: Set<Ref<Sprint> | undefined | null> = new Set()
const client = getClient()
async function getValues (search: string): Promise<void> {
if (objectsPromise) {
await objectsPromise
}
values = []
selectedValues.clear()
const spaceQuery: DocumentQuery<Sprint> = space ? { space } : {}
const resultQuery: DocumentQuery<Sprint> =
search !== ''
? {
label: { $like: '%' + search + '%' }
}
: {}
objectsPromise = client.findAll(
tracker.class.Sprint,
{ ...resultQuery, ...spaceQuery },
{
sort: { label: SortingOrder.Ascending }
}
)
const res = await objectsPromise
values = res
selectedValues = new Set(res.map((p) => p._id).filter((p) => filter.value.includes(p)))
if (filter.value.includes(undefined) || filter.value.includes(null)) {
selectedValues.add(undefined)
}
objectsPromise = undefined
}
function isSelected (value: Ref<Sprint> | undefined, values: Set<Ref<Sprint> | undefined | null>): boolean {
return values.has(value)
}
function toggle (value: Ref<Sprint> | undefined): void {
if (isSelected(value, selectedValues)) {
selectedValues.delete(value)
} else {
selectedValues.add(value)
}
selectedValues = selectedValues
filter.value = Array.from(selectedValues)
onChange(filter)
}
function getStatusItem (status: SprintStatus, docs: Sprint[]): Sprint[] {
return docs.filter((p) => p.status === status)
}
function getStatuses (): SprintStatus[] {
const res = Array.from(Object.values(SprintStatus).filter((v) => !isNaN(Number(v)))) as SprintStatus[]
return res
}
onMount(() => {
if (searchInput && !$deviceOptionsStore.isMobile) searchInput.focus()
})
getValues(search)
</script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="header">
<input
bind:this={searchInput}
type="text"
bind:value={search}
on:change={() => {
getValues(search)
}}
placeholder={phTraslate}
/>
</div>
<div class="scroll">
<div class="box">
{#if objectsPromise}
<Loading />
{:else}
<button
class="menu-item"
on:click={() => {
toggle(undefined)
}}
>
<div class="flex clear-mins">
<div class="check pointer-events-none">
<CheckBox checked={isSelected(undefined, selectedValues)} primary />
</div>
<Label label={ui.string.NotSelected} />
</div>
</button>
{#each getStatuses() as group}
{@const status = sprintStatusAssets[group]}
{@const items = getStatusItem(group, values)}
{#if items.length > 0}
<div class="flex-row-center p-1">
<Icon icon={status.icon} size={'small'} />
<div class="ml-2">
<Label label={status.label} />
</div>
</div>
{#each items as doc}
<button
class="menu-item"
on:click={() => {
toggle(doc._id)
}}
>
<div class="flex clear-mins">
<div class="check pointer-events-none">
<CheckBox checked={isSelected(doc._id, selectedValues)} primary />
</div>
<SprintTitlePresenter value={doc} />
</div>
</button>
{/each}
{/if}
{/each}
{/if}
</div>
</div>
</div>
@@ -20,7 +20,7 @@
export let value: WithLookup<Sprint>
export let shouldShowAvatar: boolean = true
export let onClick: () => void | undefined
export let onClick: (() => void) | undefined = undefined
export let disabled = false
export let inline: boolean = false
@@ -13,42 +13,29 @@
// limitations under the License.
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { Sprint, SprintStatus } from '@hcengineering/tracker'
import { SprintStatus } from '@hcengineering/tracker'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import tracker from '../../plugin'
import SprintStatusSelector from './SprintStatusSelector.svelte'
export let value: Sprint
export let isEditable: boolean = true
export let shouldShowLabel: boolean = false
export let value: SprintStatus
export let onChange: ((value: SprintStatus | undefined) => void) | undefined = undefined
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'large'
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = '100%'
const client = getClient()
const handleComponentStatusChanged = async (newStatus: SprintStatus | undefined) => {
if (!isEditable || newStatus === undefined || value.status === newStatus) {
return
}
await client.update(value, { status: newStatus })
}
$: isEditable = onChange !== undefined
</script>
{#if value}
<SprintStatusSelector
{kind}
{size}
{width}
{justify}
{isEditable}
{shouldShowLabel}
showTooltip={isEditable ? { label: tracker.string.SetStatus } : undefined}
selectedSprintStatus={value.status}
onSprintStatusChange={handleComponentStatusChanged}
/>
{/if}
<SprintStatusSelector
{kind}
{size}
{width}
{justify}
{isEditable}
showTooltip={isEditable ? { label: tracker.string.SetStatus } : undefined}
selectedSprintStatus={value}
onSprintStatusChange={onChange}
/>
+3 -1
View File
@@ -136,6 +136,7 @@ import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.svelte'
import IssueStatistics from './components/sprints/IssueStatistics.svelte'
import SprintRefPresenter from './components/sprints/SprintRefPresenter.svelte'
import SprintFilter from './components/sprints/SprintFilter.svelte'
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
@@ -445,7 +446,8 @@ export default async (): Promise<Resources> => ({
TimeSpendReportPopup,
SprintDatePresenter,
SprintLeadPresenter,
NotificationIssuePresenter
NotificationIssuePresenter,
SprintFilter
},
completion: {
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>