mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 13:35:04 +02:00
Tracker: View options - Completed issues period, empty groups display (#1490)
Signed-off-by: Artyom Grigorovich <grigorovichartyom@gmail.com>
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { IssueStatus, Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import Issues from './Issues.svelte'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
|
||||
const completedIssuesPeriod: IssuesDateModificationPeriod | null = null
|
||||
</script>
|
||||
|
||||
<Issues
|
||||
{currentSpace}
|
||||
{completedIssuesPeriod}
|
||||
includedGroups={{ status: [IssueStatus.InProgress, IssueStatus.Todo] }}
|
||||
title={tracker.string.ActiveIssues}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { IssueStatus, Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import Issues from './Issues.svelte'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
|
||||
const completedIssuesPeriod: IssuesDateModificationPeriod | null = null
|
||||
</script>
|
||||
|
||||
<Issues title={tracker.string.BacklogIssues} {currentSpace} includedGroups={{ status: [IssueStatus.Backlog] }} />
|
||||
<Issues
|
||||
title={tracker.string.BacklogIssues}
|
||||
{currentSpace}
|
||||
{completedIssuesPeriod}
|
||||
includedGroups={{ status: [IssueStatus.Backlog] }}
|
||||
/>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="category" class:visible={issuesAmount > 0}>
|
||||
<div class="category">
|
||||
{#if headerComponent}
|
||||
<div class="header categoryHeader flex-between label">
|
||||
<div class="flex-row-center gap-2">
|
||||
@@ -81,13 +81,6 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.category {
|
||||
display: none;
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.categoryHeader {
|
||||
height: 2.5rem;
|
||||
background-color: var(--theme-table-bg-hover);
|
||||
|
||||
@@ -16,13 +16,26 @@
|
||||
import contact from '@anticrm/contact'
|
||||
import type { DocumentQuery, Ref } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Issue, Team, IssuesGrouping, IssuesOrdering } from '@anticrm/tracker'
|
||||
import {
|
||||
Issue,
|
||||
Team,
|
||||
IssuesGrouping,
|
||||
IssuesOrdering,
|
||||
IssuesDateModificationPeriod,
|
||||
IssueStatus
|
||||
} from '@anticrm/tracker'
|
||||
import { Button, Label, ScrollBox, IconOptions, showPopup, eventToHTMLElement } from '@anticrm/ui'
|
||||
import CategoryPresenter from './CategoryPresenter.svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import ViewOptionsPopup from './ViewOptionsPopup.svelte'
|
||||
import { IssuesGroupByKeys, issuesGroupKeyMap, issuesOrderKeyMap } from '../../utils'
|
||||
import {
|
||||
IssuesGroupByKeys,
|
||||
issuesGroupKeyMap,
|
||||
issuesOrderKeyMap,
|
||||
defaultIssueCategories,
|
||||
getIssuesModificationDatePeriodTime
|
||||
} from '../../utils'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
export let title: IntlString = tracker.string.AllIssues
|
||||
@@ -30,6 +43,8 @@
|
||||
export let search: string = ''
|
||||
export let groupingKey: IssuesGrouping = IssuesGrouping.Status
|
||||
export let orderingKey: IssuesOrdering = IssuesOrdering.LastUpdated
|
||||
export let completedIssuesPeriod: IssuesDateModificationPeriod | null = IssuesDateModificationPeriod.All
|
||||
export let shouldShowEmptyGroups: boolean | undefined = false
|
||||
export let includedGroups: Partial<Record<IssuesGroupByKeys, Array<any>>> = {}
|
||||
|
||||
const ENTRIES_LIMIT = 200
|
||||
@@ -41,25 +56,30 @@
|
||||
|
||||
$: totalIssues = getTotalIssues(issuesMap)
|
||||
|
||||
$: resultQuery =
|
||||
search === ''
|
||||
? { space: currentSpace, ...includedIssuesQuery, ...query }
|
||||
: { $search: search, space: currentSpace, ...includedIssuesQuery, ...query }
|
||||
$: baseQuery = {
|
||||
space: currentSpace,
|
||||
...includedIssuesQuery,
|
||||
...filteredIssuesQuery,
|
||||
...query
|
||||
}
|
||||
|
||||
$: resultQuery = search === '' ? baseQuery : { $search: search, ...baseQuery }
|
||||
|
||||
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
||||
currentTeam = res.shift()
|
||||
})
|
||||
|
||||
$: groupByKey = issuesGroupKeyMap[groupingKey]
|
||||
$: categories = getCategories(groupByKey, issues)
|
||||
$: categories = getCategories(groupByKey, issues, !!shouldShowEmptyGroups)
|
||||
$: displayedCategories = (categories as any[]).filter((x: ReturnType<typeof getCategories>) => {
|
||||
return (
|
||||
groupByKey === undefined || includedGroups[groupByKey] === undefined || includedGroups[groupByKey]?.includes(x)
|
||||
)
|
||||
})
|
||||
$: includedIssuesQuery = getIncludedIssues(includedGroups)
|
||||
$: includedIssuesQuery = getIncludedIssuesQuery(includedGroups)
|
||||
$: filteredIssuesQuery = getModifiedOnIssuesFilterQuery(issues, completedIssuesPeriod)
|
||||
|
||||
const getIncludedIssues = (groups: Partial<Record<IssuesGroupByKeys, Array<any>>>) => {
|
||||
const getIncludedIssuesQuery = (groups: Partial<Record<IssuesGroupByKeys, Array<any>>>) => {
|
||||
const resultMap: { [p: string]: { $in: any[] } } = {}
|
||||
|
||||
for (const [key, value] of Object.entries(groups)) {
|
||||
@@ -69,6 +89,24 @@
|
||||
return resultMap
|
||||
}
|
||||
|
||||
const getModifiedOnIssuesFilterQuery = (currentIssues: Issue[], period: IssuesDateModificationPeriod | null) => {
|
||||
const filter: { _id: { $in: Array<Ref<Issue>> } } = { _id: { $in: [] } }
|
||||
|
||||
if (!period || period === IssuesDateModificationPeriod.All) {
|
||||
return {}
|
||||
}
|
||||
|
||||
for (const issue of currentIssues) {
|
||||
if (issue.status === IssueStatus.Done && issue.modifiedOn < getIssuesModificationDatePeriodTime(period)) {
|
||||
continue
|
||||
}
|
||||
|
||||
filter._id.$in.push(issue._id)
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
$: issuesQuery.query<Issue>(
|
||||
tracker.class.Issue,
|
||||
{ ...includedIssuesQuery },
|
||||
@@ -78,18 +116,20 @@
|
||||
{ limit: ENTRIES_LIMIT, lookup: { assignee: contact.class.Employee } }
|
||||
)
|
||||
|
||||
const getCategories = (key: IssuesGroupByKeys | undefined, elements: Issue[]) => {
|
||||
const getCategories = (key: IssuesGroupByKeys | undefined, elements: Issue[], shouldShowAll: boolean) => {
|
||||
if (!key) {
|
||||
return [undefined]
|
||||
return [undefined] // No grouping
|
||||
}
|
||||
|
||||
return Array.from(
|
||||
const existingCategories = Array.from(
|
||||
new Set(
|
||||
elements.map((x) => {
|
||||
return x[key]
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return shouldShowAll ? defaultIssueCategories[key] ?? existingCategories : existingCategories
|
||||
}
|
||||
|
||||
const getTotalIssues = (map: { [status: string]: number }) => {
|
||||
@@ -102,7 +142,16 @@
|
||||
return total
|
||||
}
|
||||
|
||||
const handleOptionsUpdated = (result: { orderBy: IssuesOrdering; groupBy: IssuesGrouping } | undefined) => {
|
||||
const handleOptionsUpdated = (
|
||||
result:
|
||||
| {
|
||||
orderBy: IssuesOrdering
|
||||
groupBy: IssuesGrouping
|
||||
completedIssuesPeriod: IssuesDateModificationPeriod
|
||||
shouldShowEmptyGroups: boolean
|
||||
}
|
||||
| undefined
|
||||
) => {
|
||||
if (result === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -113,6 +162,12 @@
|
||||
|
||||
groupingKey = result.groupBy
|
||||
orderingKey = result.orderBy
|
||||
completedIssuesPeriod = result.completedIssuesPeriod
|
||||
shouldShowEmptyGroups = result.shouldShowEmptyGroups
|
||||
|
||||
if (result.groupBy === IssuesGrouping.Assignee || result.groupBy === IssuesGrouping.NoGrouping) {
|
||||
shouldShowEmptyGroups = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const handleOptionsEditorOpened = (event: MouseEvent) => {
|
||||
@@ -122,7 +177,7 @@
|
||||
|
||||
showPopup(
|
||||
ViewOptionsPopup,
|
||||
{ groupBy: groupingKey, orderBy: orderingKey },
|
||||
{ groupBy: groupingKey, orderBy: orderingKey, completedIssuesPeriod, shouldShowEmptyGroups },
|
||||
eventToHTMLElement(event),
|
||||
undefined,
|
||||
handleOptionsUpdated
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IssuesGrouping, IssuesOrdering } from '@anticrm/tracker'
|
||||
import { Label } from '@anticrm/ui'
|
||||
import { IssuesGrouping, IssuesOrdering, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import { Label, MiniToggle } from '@anticrm/ui'
|
||||
import tracker from '../../plugin'
|
||||
import { issuesGroupByOptions, issuesOrderByOptions } from '../../utils'
|
||||
import { issuesGroupByOptions, issuesOrderByOptions, issuesDateModificationPeriodOptions } from '../../utils'
|
||||
import DropdownNative from '../DropdownNative.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
@@ -24,20 +24,23 @@
|
||||
|
||||
export let groupBy: IssuesGrouping | undefined = undefined
|
||||
export let orderBy: IssuesOrdering | undefined = undefined
|
||||
export let completedIssuesPeriod: IssuesDateModificationPeriod | null = null
|
||||
export let shouldShowEmptyGroups: boolean | undefined = false
|
||||
|
||||
const groupByItems = issuesGroupByOptions
|
||||
const orderByItems = issuesOrderByOptions
|
||||
const dateModificationPeriodItems = issuesDateModificationPeriodOptions
|
||||
|
||||
$: dispatch('update', { groupBy, orderBy })
|
||||
$: dispatch('update', { groupBy, orderBy, completedIssuesPeriod, shouldShowEmptyGroups })
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="sortingContainer">
|
||||
<div class="groupContainer">
|
||||
<div class="viewOption">
|
||||
<div class="label">
|
||||
<Label label={tracker.string.Grouping} />
|
||||
</div>
|
||||
<div class="dropdownContainer">
|
||||
<div class="optionContainer">
|
||||
<DropdownNative items={groupByItems} bind:selected={groupBy} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,11 +48,33 @@
|
||||
<div class="label">
|
||||
<Label label={tracker.string.Ordering} />
|
||||
</div>
|
||||
<div class="dropdownContainer">
|
||||
<div class="optionContainer">
|
||||
<DropdownNative items={orderByItems} bind:selected={orderBy} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="groupContainer">
|
||||
{#if completedIssuesPeriod}
|
||||
<div class="viewOption">
|
||||
<div class="label">
|
||||
<Label label={tracker.string.CompletedIssues} />
|
||||
</div>
|
||||
<div class="optionContainer">
|
||||
<DropdownNative items={dateModificationPeriodItems} bind:selected={completedIssuesPeriod} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if groupBy === IssuesGrouping.Status || groupBy === IssuesGrouping.Priority}
|
||||
<div class="viewOption">
|
||||
<div class="label">
|
||||
<Label label={tracker.string.ShowEmptyGroups} />
|
||||
</div>
|
||||
<div class="optionContainer">
|
||||
<MiniToggle bind:on={shouldShowEmptyGroups} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -60,7 +85,7 @@
|
||||
background-color: var(--board-card-bg-color);
|
||||
}
|
||||
|
||||
.sortingContainer {
|
||||
.groupContainer {
|
||||
padding: 0.5rem 1rem;
|
||||
border-bottom: 1px solid var(--popup-divider);
|
||||
}
|
||||
@@ -77,7 +102,7 @@
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
|
||||
.dropdownContainer {
|
||||
.optionContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user