TSK-915: Tracker status (#2802)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2023-03-22 17:08:19 +07:00
committed by GitHub
parent 1ad25782f4
commit 1d94a1c3f8
30 changed files with 266 additions and 489 deletions
+5 -8
View File
@@ -73,8 +73,7 @@ import {
Project,
TimeReportDayType,
TimeSpendReport,
trackerId,
WorkDayLength
trackerId
} from '@hcengineering/tracker'
import { KeyBinding, ViewOptionsModel } from '@hcengineering/view'
import tracker from './plugin'
@@ -92,11 +91,14 @@ export const DOMAIN_TRACKER = 'tracker' as Domain
*/
@Model(tracker.class.IssueStatus, core.class.AttachedDoc, DOMAIN_TRACKER)
export class TIssueStatus extends TAttachedDoc implements IssueStatus {
name!: string
@Index(IndexKind.Indexed)
name!: string
description?: string
color?: number
@Prop(TypeRef(tracker.class.IssueStatusCategory), tracker.string.StatusCategory)
@Index(IndexKind.Indexed)
category!: Ref<IssueStatusCategory>
@Prop(TypeString(), tracker.string.Rank)
@@ -161,10 +163,6 @@ export class TTypeSprintStatus extends TType {}
@Model(tracker.class.Project, core.class.Space, DOMAIN_SPACE)
@UX(tracker.string.Project, tracker.icon.Project, 'Project')
export class TProject extends TSpace implements Project {
@Prop(TypeString(), tracker.string.Title)
@Index(IndexKind.FullText)
reamLogo!: IntlString
@Prop(TypeString(), tracker.string.Identifier)
@Index(IndexKind.FullText)
identifier!: IntlString
@@ -182,7 +180,6 @@ export class TProject extends TSpace implements Project {
@Prop(TypeRef(contact.class.Employee), tracker.string.DefaultAssignee)
defaultAssignee!: Ref<Employee>
declare workDayLength: WorkDayLength
declare defaultTimeReportDay: TimeReportDayType
}
+4 -8
View File
@@ -39,8 +39,7 @@ import {
IssueTemplate,
IssueTemplateChild,
Project,
TimeReportDayType,
WorkDayLength
TimeReportDayType
} from '@hcengineering/tracker'
import { DOMAIN_TRACKER } from '.'
import tracker from './plugin'
@@ -126,8 +125,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
issueStatuses: 0,
defaultIssueStatus: defaultStatusId,
defaultTimeReportDay: TimeReportDayType.PreviousWorkDay,
defaultAssignee: undefined,
workDayLength: WorkDayLength.EIGHT_HOURS
defaultAssignee: undefined
},
tracker.project.DefaultProject
)
@@ -157,14 +155,12 @@ async function fixProjectsIssueStatusesOrder (tx: TxOperations): Promise<void> {
async function upgradeProjectSettings (tx: TxOperations): Promise<void> {
const projects = await tx.findAll(tracker.class.Project, {
defaultTimeReportDay: { $exists: false },
workDayLength: { $exists: false }
defaultTimeReportDay: { $exists: false }
})
await Promise.all(
projects.map((project) =>
tx.update(project, {
defaultTimeReportDay: TimeReportDayType.PreviousWorkDay,
workDayLength: WorkDayLength.EIGHT_HOURS
defaultTimeReportDay: TimeReportDayType.PreviousWorkDay
})
)
)
-1
View File
@@ -280,7 +280,6 @@
"DefaultTimeReportDay": "Select default day for time report",
"DefaultAssignee": "Select default assignee for issues",
"WorkDayLength": "Select length of working day",
"SevenHoursLength": "Seven Hours",
"EightHoursLength": "Eight Hours",
"CreatedOn": "Created on",
-1
View File
@@ -280,7 +280,6 @@
"DefaultTimeReportDay": "Выберите день для временного отчета по умолчанию",
"DefaultAssignee": "Выберите исполнителя по умолчанию",
"WorkDayLength": "Выберите длину рабочего дня",
"SevenHoursLength": "Семь Часов",
"EightHoursLength": "Восемь Часов",
"CreatedOn": "Создана",
@@ -24,8 +24,7 @@
fillDefaults,
generateId,
Ref,
SortingOrder,
WithLookup
SortingOrder
} from '@hcengineering/core'
import { getResource, translate } from '@hcengineering/platform'
import {
@@ -41,15 +40,15 @@
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import {
calcRank,
Component as ComponentType,
DraftIssueChild,
Issue,
IssueDraft,
IssuePriority,
IssueStatus,
IssueTemplate,
Component as ComponentType,
Sprint,
Project
Project,
Sprint
} from '@hcengineering/tracker'
import {
ActionIcon,
@@ -62,8 +61,7 @@
IconMoreH,
Label,
Menu,
showPopup,
Spinner
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ObjectBox } from '@hcengineering/view-resources'
@@ -71,13 +69,13 @@
import { createEventDispatcher } from 'svelte'
import { activeComponent, activeSprint, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
import tracker from '../plugin'
import ComponentSelector from './ComponentSelector.svelte'
import AssigneeEditor from './issues/AssigneeEditor.svelte'
import IssueNotification from './issues/IssueNotification.svelte'
import ParentIssue from './issues/ParentIssue.svelte'
import PriorityEditor from './issues/PriorityEditor.svelte'
import StatusEditor from './issues/StatusEditor.svelte'
import EstimationEditor from './issues/timereport/EstimationEditor.svelte'
import ComponentSelector from './ComponentSelector.svelte'
import SetDueDateActionPopup from './SetDueDateActionPopup.svelte'
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
import SprintSelector from './sprints/SprintSelector.svelte'
@@ -101,7 +99,6 @@
let subIssuesComponent: SubIssues
let issueStatuses: WithLookup<IssueStatus>[] | undefined
let labels: TagReference[] = draft?.labels || []
let objectId: Ref<Issue> = draft?.issueId || generateId()
let saveTimer: number | undefined
@@ -137,6 +134,15 @@
createOn: Date.now()
}
$: _space = draft?.project || space
$: !originalIssue && !draft && updateIssueStatusId(currentProject, status)
$: !originalIssue && !draft && updateAssigneeId(currentProject)
$: canSave = getTitle(object.title ?? '').length > 0
$: if (object.space !== _space) {
object.space = _space
}
let object = originalIssue
? {
...originalIssue,
@@ -145,15 +151,16 @@
attachments: 0,
reportedTime: 0,
reports: 0,
childInfo: []
childInfo: [],
space: _space
}
: toIssue(defaultIssue, draft)
: { ...toIssue(defaultIssue, draft), space: _space }
fillDefaults(hierarchy, object, tracker.class.Issue)
function resetObject (): void {
templateId = undefined
template = undefined
object = { ...defaultIssue }
object = { ...defaultIssue, space: _space }
subIssues = []
labels = []
if (!originalIssue && !draft) {
@@ -228,7 +235,6 @@
$: updateTemplate(template)
const dispatch = createEventDispatcher()
const statusesQuery = createQuery()
const spaceQuery = createQuery()
let descriptionBox: AttachmentStyledBox
@@ -238,22 +244,6 @@
attr: client.getHierarchy().getAttribute(tracker.class.Issue, 'labels')
}
$: _space = draft?.project || space
$: !originalIssue && !draft && updateIssueStatusId(currentProject, status)
$: !originalIssue && !draft && updateAssigneeId(currentProject)
$: canSave = getTitle(object.title ?? '').length > 0
$: statusesQuery.query(
tracker.class.IssueStatus,
{ attachedTo: _space },
(statuses) => {
issueStatuses = statuses
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
$: spaceQuery.query(tracker.class.Project, { _id: _space }, (res) => {
currentProject = res.shift()
})
@@ -706,72 +696,64 @@
}}
/>
{/key}
{#if issueStatuses}
<SubIssues
bind:this={subIssuesComponent}
projectId={_space}
parent={objectId}
statuses={issueStatuses ?? []}
project={currentProject}
sprint={object.sprint}
component={object.component}
/>
{/if}
<SubIssues
bind:this={subIssuesComponent}
projectId={_space}
parent={objectId}
project={currentProject}
sprint={object.sprint}
component={object.component}
/>
<svelte:fragment slot="pool">
{#if issueStatuses}
<div id="status-editor">
<StatusEditor
value={object}
statuses={issueStatuses}
kind="no-border"
size="small"
shouldShowLabel={true}
on:change={({ detail }) => (object.status = detail)}
/>
</div>
<PriorityEditor
<div id="status-editor">
<StatusEditor
value={object}
shouldShowLabel
isEditable
kind="no-border"
size="small"
justify="center"
on:change={({ detail }) => (object.priority = detail)}
shouldShowLabel={true}
on:change={({ detail }) => (object.status = detail)}
/>
<AssigneeEditor
value={object}
size="small"
kind="no-border"
width={'min-content'}
on:change={({ detail }) => (object.assignee = detail)}
/>
<Component
is={tags.component.TagsDropdownEditor}
props={{
items: labels,
key,
targetClass: tracker.class.Issue,
countLabel: tracker.string.NumberLabels
}}
on:open={(evt) => {
addTagRef(evt.detail)
}}
on:delete={(evt) => {
labels = labels.filter((it) => it._id !== evt.detail)
}}
/>
<EstimationEditor kind={'no-border'} size={'small'} value={object} {currentProject} />
<ComponentSelector value={object.component} onChange={handleComponentIdChanged} isEditable={true} />
<SprintSelector
value={object.sprint}
onChange={handleSprintIdChanged}
useComponent={(!originalIssue && object.component) || undefined}
/>
{#if object.dueDate !== null}
<DatePresenter bind:value={object.dueDate} editable />
{/if}
{:else}
<Spinner size="small" />
</div>
<PriorityEditor
value={object}
shouldShowLabel
isEditable
kind="no-border"
size="small"
justify="center"
on:change={({ detail }) => (object.priority = detail)}
/>
<AssigneeEditor
value={object}
size="small"
kind="no-border"
width={'min-content'}
on:change={({ detail }) => (object.assignee = detail)}
/>
<Component
is={tags.component.TagsDropdownEditor}
props={{
items: labels,
key,
targetClass: tracker.class.Issue,
countLabel: tracker.string.NumberLabels
}}
on:open={(evt) => {
addTagRef(evt.detail)
}}
on:delete={(evt) => {
labels = labels.filter((it) => it._id !== evt.detail)
}}
/>
<EstimationEditor kind={'no-border'} size={'small'} value={object} {currentProject} />
<ComponentSelector value={object.component} onChange={handleComponentIdChanged} isEditable={true} />
<SprintSelector
value={object.sprint}
onChange={handleSprintIdChanged}
useComponent={(!originalIssue && object.component) || undefined}
/>
{#if object.dueDate !== null}
<DatePresenter bind:value={object.dueDate} editable />
{/if}
<ActionIcon icon={IconMoreH} size={'medium'} action={showMoreActions} />
</svelte:fragment>
@@ -15,19 +15,10 @@
<script lang="ts">
import attachment, { Attachment } from '@hcengineering/attachment'
import { deleteFile } from '@hcengineering/attachment-resources/src/utils'
import core, { AttachedData, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import core, { AttachedData, Ref, SortingOrder } from '@hcengineering/core'
import { draftStore, getClient, updateDraftStore } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import {
calcRank,
DraftIssueChild,
Issue,
IssueParentInfo,
IssueStatus,
Component,
Sprint,
Project
} from '@hcengineering/tracker'
import { calcRank, Component, DraftIssueChild, Issue, IssueParentInfo, Project, Sprint } from '@hcengineering/tracker'
import { Button, closeTooltip, ExpandCollapse, IconAdd, Scroller } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import tracker from '../plugin'
@@ -42,7 +33,6 @@
export let sprint: Ref<Sprint> | null = null
export let component: Ref<Component> | null = null
export let subIssues: DraftIssueChild[] = []
export let statuses: WithLookup<IssueStatus>[]
let isCollapsed = false
let isCreating = false
@@ -208,7 +198,6 @@
<div class="flex-col flex-no-shrink max-h-30 list clear-mins" class:collapsed={isCollapsed}>
<Scroller>
<DraftIssueChildList
{statuses}
{component}
{sprint}
bind:issues={subIssues}
@@ -224,7 +213,6 @@
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
<DraftIssueChildEditor
{project}
{statuses}
{component}
{sprint}
on:close={() => {
@@ -23,9 +23,9 @@
import { Label, resizeObserver, Scroller } from '@hcengineering/ui'
import tracker from '../../plugin'
import AssigneeEditor from './AssigneeEditor.svelte'
import IssueStatusActivity from './IssueStatusActivity.svelte'
import PriorityEditor from './PriorityEditor.svelte'
import StatusEditor from './StatusEditor.svelte'
import IssueStatusActivity from './IssueStatusActivity.svelte'
export let object: Issue
let issue: Issue | undefined
@@ -88,7 +88,7 @@
{#if issue}
<div class="fs-title text-xl ml-2">{issueName} {issue.title}</div>
<div class="flex mt-2">
<StatusEditor value={issue} {statuses} shouldShowLabel kind={'transparent'} />
<StatusEditor value={issue} shouldShowLabel kind={'transparent'} />
<PriorityEditor value={issue} shouldShowLabel />
{#if issue.assignee}
<AssigneeEditor value={issue} width={'min-content'} />
@@ -1,19 +1,10 @@
<script lang="ts">
import core, {
IdMap,
Ref,
Timestamp,
toIdMap,
Tx,
TxCollectionCUD,
TxCreateDoc,
TxUpdateDoc,
WithLookup
} from '@hcengineering/core'
import core, { Ref, Timestamp, Tx, TxCollectionCUD, TxCreateDoc, TxUpdateDoc, WithLookup } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus } from '@hcengineering/tracker'
import { Label, ticker } from '@hcengineering/ui'
import tracker from '../../plugin'
import { statusByIdStore } from '../../utils'
import Duration from './Duration.svelte'
import StatusPresenter from './StatusPresenter.svelte'
@@ -28,23 +19,6 @@
duration: number
}
const stQuery = createQuery()
let statuses: IdMap<IssueStatus> = new Map()
stQuery.query(
tracker.class.IssueStatus,
{},
(res) => {
statuses = toIdMap(res)
},
{
lookup: {
category: tracker.class.IssueStatusCategory
}
}
)
$: query.query(
core.class.Tx,
{ 'tx.objectId': issue._id },
@@ -109,7 +83,7 @@
displaySt = result
}
$: updateStatus(txes, statuses, $ticker)
$: updateStatus(txes, $statusByIdStore, $ticker)
</script>
<div class="flex-row mt-4 mb-4">
@@ -13,19 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { WithLookup, SortingOrder } from '@hcengineering/core'
import { WithLookup } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { IssueStatus, IssueStatusCategory } from '@hcengineering/tracker'
import { getPlatformColor, IconSize } from '@hcengineering/ui'
import tracker from '../../plugin'
import { statusStore } from '../../utils'
import StatusIcon from '../icons/StatusIcon.svelte'
export let value: WithLookup<IssueStatus>
export let size: IconSize
export let fill: string | undefined = undefined
export let issueStatuses: IssueStatus[] | undefined = undefined
const dynamicFillCategories = [tracker.issueStatusCategory.Started]
const client = getClient()
@@ -40,19 +39,14 @@
const categoriesQuery = createQuery()
$: if (value.category === tracker.issueStatusCategory.Started) {
if (issueStatuses === undefined) {
categoriesQuery.query(
tracker.class.IssueStatus,
{ category: tracker.issueStatusCategory.Started },
(res) => (statuses = res),
{ sort: { rank: SortingOrder.Ascending } }
const _s = [
...$statusStore.filter(
(it) => it.attachedTo === value.attachedTo && it.category === tracker.issueStatusCategory.Started
)
} else {
const _s = [...issueStatuses.filter((it) => it.category === tracker.issueStatusCategory.Started)]
_s.sort((a, b) => a.rank.localeCompare(b.rank))
statuses = _s
categoriesQuery.unsubscribe()
}
]
_s.sort((a, b) => a.rank.localeCompare(b.rank))
statuses = _s
categoriesQuery.unsubscribe()
}
async function updateCategory (status: WithLookup<IssueStatus>, statuses: IssueStatus[]) {
@@ -13,18 +13,24 @@
// limitations under the License.
-->
<script lang="ts">
import { AttachedData, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { AttachedData, Ref, WithLookup } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { DraftIssueChild, Issue, IssueStatus, Project } from '@hcengineering/tracker'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { Button, eventToHTMLElement, SelectPopup, showPopup, TooltipAlignment } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { statusStore } from '../../utils'
import IssueStatusIcon from './IssueStatusIcon.svelte'
import StatusPresenter from './StatusPresenter.svelte'
export let value: Issue | AttachedData<Issue> | DraftIssueChild
export let statuses: WithLookup<IssueStatus>[] | undefined = undefined
export let value:
| Issue
| (AttachedData<Issue> & { space: Ref<Project> })
| (DraftIssueChild & { space: Ref<Project> })
let statuses: WithLookup<IssueStatus>[] | undefined = undefined
export let isEditable: boolean = true
export let shouldShowLabel: boolean = false
export let tooltipAlignment: TooltipAlignment | undefined = undefined
@@ -34,11 +40,7 @@
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = undefined
// Extra properties
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]> | undefined = undefined
const client = getClient()
const statusesQuery = createQuery()
const dispatch = createEventDispatcher()
const changeStatus = async (newStatus: Ref<IssueStatus> | undefined) => {
@@ -66,6 +68,8 @@
)
}
$: statuses = $statusStore.filter((it) => it.attachedTo === value?.space)
$: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0]
$: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined
$: statusesInfo = statuses?.map((s) => {
@@ -76,25 +80,6 @@
isSelected: selectedStatus?._id === s._id ?? false
}
})
$: if (!statuses) {
statuses = '_id' in value ? issueStatuses?.get(value.space) : undefined
if (statuses === undefined) {
const query = '_id' in value ? { attachedTo: value.space } : {}
statusesQuery.query(
tracker.class.IssueStatus,
query,
(result) => {
statuses = result
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
} else {
statusesQuery.unsubscribe()
}
}
$: smallgap = size === 'inline' || size === 'small'
</script>
@@ -103,11 +88,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-row-center flex-no-shrink" class:cursor-pointer={isEditable} on:click={handleStatusEditorOpened}>
<div class="flex-center flex-no-shrink square-4">
{#if selectedStatus}<IssueStatusIcon
value={selectedStatus}
issueStatuses={statuses}
size={kind === 'list' ? 'inline' : 'medium'}
/>{/if}
{#if selectedStatus}<IssueStatusIcon value={selectedStatus} size={kind === 'list' ? 'inline' : 'medium'} />{/if}
</div>
{#if selectedStatusLabel}
<span
@@ -13,20 +13,21 @@
// limitations under the License.
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { IssueStatus } from '@hcengineering/tracker'
import { statusByIdStore } from '../../utils'
import IssueStatusIcon from './IssueStatusIcon.svelte'
export let value: WithLookup<IssueStatus> | undefined
export let size: 'small' | 'medium' = 'medium'
export let value: IssueStatus | undefined
export let size: 'small' | 'medium' = 'small'
</script>
{#if value}
{@const icon = $statusByIdStore.get(value._id)?.$lookup?.category?.icon}
<div class="flex-presenter">
{#if value.$lookup?.category?.icon}
{#if icon}
<IssueStatusIcon {value} {size} />
{/if}
<span class="overflow-label" class:ml-2={value.$lookup?.category?.icon !== undefined}>
<span class="overflow-label" class:ml-2={icon !== undefined}>
{value.name}
</span>
</div>
@@ -13,20 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, WithLookup } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import tracker, { IssueStatus } from '@hcengineering/tracker'
import { Ref } from '@hcengineering/core'
import { IssueStatus } from '@hcengineering/tracker'
import { statusByIdStore } from '../../utils'
import StatusPresenter from './StatusPresenter.svelte'
export let value: Ref<IssueStatus> | undefined
export let size: 'small' | 'medium' = 'medium'
let status: WithLookup<IssueStatus> | undefined
const query = createQuery()
$: query.query(tracker.class.IssueStatus, { _id: value }, (res) => ([status] = res), {
lookup: { category: tracker.class.IssueStatusCategory }
})
</script>
<StatusPresenter value={status} {size} />
{#if value}
<StatusPresenter value={$statusByIdStore.get(value)} {size} />
{/if}
@@ -15,11 +15,11 @@
<script lang="ts">
import contact, { EmployeeAccount } from '@hcengineering/contact'
import { employeeByIdStore, EmployeePresenter } from '@hcengineering/contact-resources'
import core, { ClassifierKind, Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
import core, { ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
import { AttributeBarEditor, createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import type { Issue, IssueStatus } from '@hcengineering/tracker'
import type { Issue } from '@hcengineering/tracker'
import { Component, Label } from '@hcengineering/ui'
import { getFiltredKeys, isCollectionAttr, ObjectBox } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
@@ -32,7 +32,6 @@
import StatusEditor from '../StatusEditor.svelte'
export let issue: Issue
export let issueStatuses: WithLookup<IssueStatus>[]
export let showAllMixins: boolean = false
const query = createQuery()
@@ -116,7 +115,7 @@
<Label label={tracker.string.Status} />
</span>
<StatusEditor value={issue} statuses={issueStatuses} shouldShowLabel />
<StatusEditor value={issue} shouldShowLabel />
{#if issue.blockedBy?.length}
<span class="labelTop">
@@ -13,29 +13,28 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Account, AttachedData, Doc, generateId, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import core, { Account, AttachedData, Doc, generateId, Ref, SortingOrder } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import presentation, { getClient, KeyedAttribute } from '@hcengineering/presentation'
import { IssueStatus, IssuePriority, Issue, Project, calcRank } from '@hcengineering/tracker'
import { addNotification, Button, Component, EditBox } from '@hcengineering/ui'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { calcRank, Issue, IssuePriority, IssueStatus, Project } from '@hcengineering/tracker'
import { addNotification, Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import AssigneeEditor from '../AssigneeEditor.svelte'
import StatusEditor from '../StatusEditor.svelte'
import PriorityEditor from '../PriorityEditor.svelte'
import EstimationEditor from '../timereport/EstimationEditor.svelte'
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import IssueNotification from '../IssueNotification.svelte'
import { translate } from '@hcengineering/platform'
import PriorityEditor from '../PriorityEditor.svelte'
import StatusEditor from '../StatusEditor.svelte'
import EstimationEditor from '../timereport/EstimationEditor.svelte'
export let parentIssue: Issue
export let issueStatuses: WithLookup<IssueStatus>[]
export let currentProject: Project
const dispatch = createEventDispatcher()
const client = getClient()
let newIssue: AttachedData<Issue> = getIssueDefaults()
let newIssue = { ...getIssueDefaults(), space: currentProject._id }
let thisRef: HTMLDivElement
let focusIssueTitle: () => void
let labels: TagReference[] = []
@@ -71,7 +70,7 @@
}
function resetToDefaults () {
newIssue = getIssueDefaults()
newIssue = { ...getIssueDefaults(), space: currentProject._id }
labels = []
focusIssueTitle?.()
objectId = generateId()
@@ -173,7 +172,6 @@
<div id="status-editor" class="mr-1">
<StatusEditor
value={newIssue}
statuses={issueStatuses}
kind="transparent"
size="medium"
justify="center"
@@ -14,13 +14,13 @@
-->
<script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { Class, Data, Doc, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { Class, Data, Doc, Ref, WithLookup } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import setting, { settingId } from '@hcengineering/setting'
import type { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import type { Issue, Project } from '@hcengineering/tracker'
import {
Button,
EditBox,
@@ -48,13 +48,11 @@
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const queryClient = createQuery()
const statusesQuery = createQuery()
const dispatch = createEventDispatcher()
const client = getClient()
let issue: WithLookup<Issue> | undefined
let currentProject: Project | undefined
let issueStatuses: WithLookup<IssueStatus>[] | undefined
let title = ''
let description = ''
let innerWidth: number
@@ -93,20 +91,8 @@
{ lookup: { attachedTo: tracker.class.Issue, space: tracker.class.Project } }
)
$: currentProject &&
statusesQuery.query(
tracker.class.IssueStatus,
{ attachedTo: currentProject._id },
(statuses) => (issueStatuses = statuses),
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
$: issueId = currentProject && issue && getIssueId(currentProject, issue)
$: canSave = title.trim().length > 0
$: isDescriptionEmpty = !new DOMParser().parseFromString(description, 'text/html').documentElement.innerText?.trim()
$: parentIssue = issue?.$lookup?.attachedTo
let saved = false
@@ -190,7 +176,7 @@
{#if parentIssue}
<div class="mb-6">
{#if currentProject && issueStatuses}
{#if currentProject}
<SubIssueSelector {issue} />
{:else}
<Spinner />
@@ -221,12 +207,8 @@
<div class="mt-6">
{#key issue._id && currentProject !== undefined}
{#if currentProject !== undefined && issueStatuses !== undefined}
<SubIssues
{issue}
issueStatuses={new Map([[currentProject._id, issueStatuses]])}
projects={new Map([[currentProject?._id, currentProject]])}
/>
{#if currentProject !== undefined}
<SubIssues {issue} projects={new Map([[currentProject?._id, currentProject]])} />
{/if}
{/key}
</div>
@@ -270,8 +252,8 @@
</svelte:fragment>
<svelte:fragment slot="custom-attributes">
{#if issue && currentProject && issueStatuses}
<ControlPanel {issue} {issueStatuses} {showAllMixins} />
{#if issue && currentProject}
<ControlPanel {issue} {showAllMixins} />
{/if}
<div class="divider" />
@@ -13,8 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import { Doc, DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { Issue, Project } from '@hcengineering/tracker'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import {
ActionContext,
@@ -34,7 +34,6 @@
// Extra properties
export let projects: Map<Ref<Project>, Project> | undefined
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]>
let list: List
@@ -66,7 +65,7 @@
documents={issues}
{query}
flatHeaders={true}
props={{ projects, issueStatuses }}
props={{ projects }}
{disableHeader}
selectedObjectIds={$selectionStore ?? []}
on:row-focus={(event) => {
@@ -13,18 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, SortingOrder, toIdMap, WithLookup } from '@hcengineering/core'
import { Ref, toIdMap } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project, trackerId } from '@hcengineering/tracker'
import { Issue, Project, trackerId } from '@hcengineering/tracker'
import {
Button,
Chevron,
closeTooltip,
ExpandCollapse,
getCurrentLocation,
IconScaleFull,
IconAdd,
IconArrowRight,
IconScaleFull,
Label,
navigate
} from '@hcengineering/ui'
@@ -33,8 +33,8 @@
createFilter,
filterStore,
getViewOptions,
viewOptionStore,
ViewletSettingButton
ViewletSettingButton,
viewOptionStore
} from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import CreateSubIssue from './CreateSubIssue.svelte'
@@ -42,7 +42,6 @@
export let issue: Issue
export let projects: Map<Ref<Project>, Project>
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]>
let subIssueEditorRef: HTMLDivElement
let isCollapsed = false
@@ -58,7 +57,6 @@
})
let _projects = projects
let _issueStatuses = issueStatuses
const projectsQuery = createQuery()
@@ -70,28 +68,6 @@
projectsQuery.unsubscribe()
}
const statusesQuery = createQuery()
$: if (issueStatuses === undefined) {
statusesQuery.query(
tracker.class.IssueStatus,
{},
(statuses) => {
const st = new Map<Ref<Project>, WithLookup<IssueStatus>[]>()
for (const s of statuses) {
const id = s.attachedTo as Ref<Project>
st.set(id, [...(st.get(id) ?? []), s])
}
_issueStatuses = st
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
} else {
statusesQuery.unsubscribe()
}
$: viewOptions = viewlet !== undefined ? getViewOptions(viewlet, $viewOptionStore) : undefined
</script>
@@ -158,39 +134,25 @@
</div>
</div>
<div class="mt-1">
{#if issueStatuses}
{#if hasSubIssues && viewOptions && viewlet}
{#if !isCollapsed}
<ExpandCollapse isExpanded={!isCollapsed}>
<div class="list" class:collapsed={isCollapsed}>
<SubIssueList
projects={_projects}
{viewlet}
{viewOptions}
issueStatuses={_issueStatuses}
query={{ attachedTo: issue._id }}
/>
</div>
</ExpandCollapse>
{#if hasSubIssues && viewOptions && viewlet}
{#if !isCollapsed}
<ExpandCollapse isExpanded={!isCollapsed}>
<div class="list" class:collapsed={isCollapsed}>
<SubIssueList projects={_projects} {viewlet} {viewOptions} query={{ attachedTo: issue._id }} />
</div>
</ExpandCollapse>
{/if}
{/if}
<ExpandCollapse isExpanded={!isCollapsed}>
{#if isCreating}
{@const project = projects.get(issue.space)}
{#if project !== undefined}
<div class="pt-4" bind:this={subIssueEditorRef}>
<CreateSubIssue parentIssue={issue} currentProject={project} on:close={() => (isCreating = false)} />
</div>
{/if}
{/if}
<ExpandCollapse isExpanded={!isCollapsed}>
{#if isCreating}
{@const project = projects.get(issue.space)}
{@const statuses = issueStatuses.get(issue.space)}
{#if project !== undefined && statuses !== undefined}
<div class="pt-4" bind:this={subIssueEditorRef}>
<CreateSubIssue
parentIssue={issue}
issueStatuses={statuses}
currentProject={project}
on:close={() => (isCreating = false)}
/>
</div>
{/if}
{/if}
</ExpandCollapse>
{/if}
</ExpandCollapse>
</div>
<style lang="scss">
@@ -29,7 +29,7 @@
} from '@hcengineering/ui'
import { getIssueId } from '../../../issues'
import tracker from '../../../plugin'
import { subIssueListProvider } from '../../../utils'
import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils'
export let value: WithLookup<Issue>
export let currentProject: Project | undefined = undefined
@@ -44,7 +44,7 @@
$: project = currentProject
let subIssues: Issue[] = []
let countComplate: number = 0
let countComplete: number = 0
const projectQuery = createQuery()
$: if (currentProject === undefined) {
@@ -59,7 +59,6 @@
projectQuery.unsubscribe()
}
const query = createQuery()
const statusesQuery = createQuery()
$: update(value)
@@ -75,25 +74,18 @@
} else {
query.unsubscribe()
}
if (value.subIssues > 0 || value.$lookup?.subIssues !== undefined) {
statusesQuery.query(tracker.class.IssueStatus, {}, (res) => (statuses = res), {
lookup: { category: tracker.class.IssueStatusCategory }
})
} else {
statusesQuery.unsubscribe()
}
}
let statuses: WithLookup<IssueStatus>[] = []
$: if (statuses && subIssues) {
const doneStatuses = statuses.filter((s) => s.category === tracker.issueStatusCategory.Completed).map((p) => p._id)
countComplate = subIssues.filter((si) => doneStatuses.includes(si.status)).length
$: if (subIssues) {
const doneStatuses = $statusStore
.filter((s) => s.category === tracker.issueStatusCategory.Completed)
.map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
}
$: hasSubIssues = (subIssues?.length ?? 0) > 0
function getIssueStatusIcon (issue: Issue, statuses: WithLookup<IssueStatus>[] | undefined) {
const status = statuses?.find((s) => issue.status === s._id)
function getIssueStatusIcon (issue: Issue, statuses: Map<Ref<WithLookup<IssueStatus>>, WithLookup<IssueStatus>>) {
const status = statuses.get(issue.status)
const category = status?.$lookup?.category
const color = status?.color ?? category?.color
@@ -119,7 +111,12 @@
value: subIssues.map((iss) => {
const text = project ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
return { id: iss._id, text, isSelected: iss._id === value._id, ...getIssueStatusIcon(iss, statuses) }
return {
id: iss._id,
text,
isSelected: iss._id === value._id,
...getIssueStatusIcon(iss, $statusByIdStore)
}
}),
width: 'large'
},
@@ -156,9 +153,9 @@
{#if subIssues}
<div class="flex-row-center content-color text-sm pointer-events-none">
<div class="mr-1">
<ProgressCircle bind:value={countComplate} bind:max={subIssues.length} size={'inline'} primary />
<ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'inline'} primary />
</div>
{countComplate}/{subIssues.length}
{countComplete}/{subIssues.length}
</div>
{/if}
</svelte:fragment>
@@ -29,7 +29,7 @@
} from '@hcengineering/ui'
import { getIssueId } from '../../../issues'
import tracker from '../../../plugin'
import { subIssueListProvider } from '../../../utils'
import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils'
export let object: WithLookup<Doc & { related: number }> | undefined
export let value: WithLookup<Doc & { related: number }> | undefined
@@ -43,10 +43,9 @@
let btn: HTMLElement
let subIssues: Issue[] = []
let countComplate: number = 0
let countComplete: number = 0
const query = createQuery()
const statusesQuery = createQuery()
$: _object = object ?? value
@@ -67,21 +66,18 @@
}
)
}
statusesQuery.query(tracker.class.IssueStatus, {}, (res) => (statuses = res), {
lookup: { category: tracker.class.IssueStatusCategory }
})
}
let statuses: WithLookup<IssueStatus>[] = []
$: if (statuses && subIssues) {
const doneStatuses = statuses.filter((s) => s.category === tracker.issueStatusCategory.Completed).map((p) => p._id)
countComplate = subIssues.filter((si) => doneStatuses.includes(si.status)).length
$: if (subIssues) {
const doneStatuses = $statusStore
.filter((s) => s.category === tracker.issueStatusCategory.Completed)
.map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
}
$: hasSubIssues = (subIssues?.length ?? 0) > 0
function getIssueStatusIcon (issue: Issue, statuses: WithLookup<IssueStatus>[] | undefined) {
const status = statuses?.find((s) => issue.status === s._id)
function getIssueStatusIcon (issue: Issue, statuses: Map<Ref<WithLookup<IssueStatus>>, WithLookup<IssueStatus>>) {
const status = statuses?.get(issue.status)
const category = status?.$lookup?.category
const color = status?.color ?? category?.color
@@ -105,7 +101,7 @@
value: subIssues.map((iss) => {
const text = currentProject ? `${getIssueId(currentProject, iss)} ${iss.title}` : iss.title
return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, statuses) }
return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, $statusByIdStore) }
}),
width: 'large'
},
@@ -142,9 +138,9 @@
{#if subIssues}
<div class="flex-row-center content-color text-sm pointer-events-none">
<div class="mr-1">
<ProgressCircle bind:value={countComplate} bind:max={subIssues.length} size={'inline'} primary />
<ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'inline'} primary />
</div>
{countComplate}/{subIssues.length}
{countComplete}/{subIssues.length}
</div>
{/if}
</svelte:fragment>
@@ -13,16 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Doc, DocumentQuery, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import contact from '@hcengineering/contact'
import { Doc, DocumentQuery, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Issue, Project } from '@hcengineering/tracker'
import { Label, Spinner } from '@hcengineering/ui'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import SubIssueList from '../edit/SubIssueList.svelte'
import AddIssueDuo from '../../icons/AddIssueDuo.svelte'
import contact from '@hcengineering/contact'
import SubIssueList from '../edit/SubIssueList.svelte'
export let object: Doc
export let viewlet: Viewlet
@@ -52,31 +52,11 @@
$: projectsQuery.query(tracker.class.Project, {}, async (result) => {
projects = new Map(result.map((it) => [it._id, it]))
})
let issueStatuses = new Map<Ref<Project>, WithLookup<IssueStatus>[]>()
const statusesQuery = createQuery()
$: statusesQuery.query(
tracker.class.IssueStatus,
{},
(statuses) => {
const st = new Map<Ref<Project>, WithLookup<IssueStatus>[]>()
for (const s of statuses) {
const id = s.attachedTo as Ref<Project>
st.set(id, [...(st.get(id) ?? []), s])
}
issueStatuses = st
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
</script>
{#if subIssues !== undefined && viewlet !== undefined}
{#if issueStatuses.size > 0 && projects && subIssues.length > 0}
<SubIssueList bind:viewOptions {viewlet} issues={subIssues} {projects} {issueStatuses} {disableHeader} />
{#if projects && subIssues.length > 0}
<SubIssueList bind:viewOptions {viewlet} issues={subIssues} {projects} {disableHeader} />
{:else}
<div class="antiSection-empty solid flex-col mt-3">
<div class="flex-center content-accent-color">
@@ -14,9 +14,8 @@
// limitations under the License.
-->
<script lang="ts">
import { SortingOrder, WithLookup } from '@hcengineering/core'
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Issue, Project } from '@hcengineering/tracker'
import { Button, EditStyle, eventToHTMLElement, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { EditBoxPopup } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
@@ -41,7 +40,6 @@
const query = createQuery()
let currentProject: Project | undefined
let issueStatuses: WithLookup<IssueStatus>[] | undefined
$: query.query(
object._class,
@@ -60,19 +58,6 @@
}
)
$: defaultTimeReportDay = currentProject?.defaultTimeReportDay
const statusesQuery = createQuery()
$: currentProject &&
statusesQuery.query(
tracker.class.IssueStatus,
{ attachedTo: currentProject._id },
(statuses) => (issueStatuses = statuses),
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -124,12 +109,8 @@
<IssuePresenter value={object} disableClick />
</svelte:fragment>
{#if currentProject && issueStatuses}
<SubIssuesEstimations
issue={object}
issueStatuses={new Map([[currentProject._id, issueStatuses]])}
projects={new Map([[currentProject?._id, currentProject]])}
/>
{#if currentProject}
<SubIssuesEstimations issue={object} projects={new Map([[currentProject?._id, currentProject]])} />
{/if}
{#if currentProject}
@@ -13,16 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Issue, Project } from '@hcengineering/tracker'
import { Expandable, Spinner } from '@hcengineering/ui'
import tracker from '../../../plugin'
import EstimationSubIssueList from './EstimationSubIssueList.svelte'
export let issue: Issue
export let projects: Map<Ref<Project>, Project>
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]>
const subIssuesQuery = createQuery()
@@ -35,7 +34,7 @@
$: total = (subIssues ?? []).reduce((a, b) => a + b.estimation, 0)
</script>
{#if subIssues && issueStatuses}
{#if subIssues}
{#if hasSubIssues}
<Expandable label={tracker.string.ChildEstimation} contentColor bordered>
<svelte:fragment slot="title">: <span class="caption-color">{total}</span></svelte:fragment>
@@ -13,11 +13,11 @@
// limitations under the License.
-->
<script lang="ts">
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus } from '@hcengineering/tracker'
import { Ref } from '@hcengineering/core'
import { Issue } from '@hcengineering/tracker'
import { floorFractionDigits, Label } from '@hcengineering/ui'
import tracker from '../../plugin'
import { statusByIdStore } from '../../utils'
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
import TimePresenter from '../issues/timereport/TimePresenter.svelte'
export let docs: Issue[] | undefined = undefined
@@ -28,23 +28,13 @@
$: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
$: rootNoBacklogIssues = noParents?.filter(
(it) => issueStatuses.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
(it) => $statusByIdStore.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
)
const statuses = createQuery()
let issueStatuses: IdMap<IssueStatus> = new Map()
$: if (noParents !== undefined) {
statuses.query(tracker.class.IssueStatus, {}, (res) => {
issueStatuses = toIdMap(res)
})
} else {
statuses.unsubscribe()
}
$: totalEstimation = floorFractionDigits(
(rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
.map((it) => {
const cat = issueStatuses.get(it.status)?.category
const cat = $statusByIdStore.get(it.status)?.category
let retEst = it.estimation
if (it.childInfo?.length > 0) {
@@ -14,17 +14,16 @@
-->
<script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { generateId, Ref, WithLookup } from '@hcengineering/core'
import { generateId, Ref } from '@hcengineering/core'
import presentation, { createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import {
Component as ComponentType,
DraftIssueChild,
IssuePriority,
IssueStatus,
IssueTemplateChild,
Component as ComponentType,
Sprint,
Project
Project,
Sprint
} from '@hcengineering/tracker'
import { Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@@ -39,12 +38,12 @@
export let component: Ref<ComponentType> | null = null
export let childIssue: DraftIssueChild | undefined = undefined
export let showBorder = false
export let statuses: WithLookup<IssueStatus>[]
const dispatch = createEventDispatcher()
const client = getClient()
let newIssue: DraftIssueChild = childIssue !== undefined ? { ...childIssue } : getIssueDefaults()
let newIssue =
childIssue !== undefined ? { ...childIssue, space: project._id } : { ...getIssueDefaults(), space: project._id }
let thisRef: HTMLDivElement
let focusIssueTitle: () => void
let labels: TagElement[] = []
@@ -75,7 +74,7 @@
}
function resetToDefaults () {
newIssue = getIssueDefaults()
newIssue = { ...getIssueDefaults(), space: project._id }
focusIssueTitle?.()
}
@@ -152,7 +151,6 @@
<div class="buttons-group xsmall-gap">
<StatusEditor
value={newIssue}
{statuses}
kind="no-border"
size="small"
shouldShowLabel={true}
@@ -13,16 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, WithLookup } from '@hcengineering/core'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import tracker, {
DraftIssueChild,
IssueStatus,
IssueTemplateChild,
Component,
Sprint,
Project
} from '@hcengineering/tracker'
import tracker, { Component, DraftIssueChild, IssueTemplateChild, Project, Sprint } from '@hcengineering/tracker'
import { eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { ActionContext, FixedColumn } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
@@ -38,7 +31,6 @@
export let project: Ref<Project>
export let sprint: Ref<Sprint> | null = null
export let component: Ref<Component> | null = null
export let statuses: WithLookup<IssueStatus>[]
const dispatch = createEventDispatcher()
let draggingIndex: number | null = null
@@ -52,7 +44,6 @@
project: currentProject,
sprint,
component,
statuses,
childIssue: target
},
eventToHTMLElement(evt),
@@ -135,8 +126,7 @@
</div>
<div class="flex-row-center ml-6 clear-mins gap-2">
<StatusEditor
value={issue}
{statuses}
value={{ ...issue, space: project }}
kind="list"
size="small"
shouldShowLabel={true}
@@ -13,9 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { IssueStatus } from '@hcengineering/tracker'
import { Icon, Label, IconEdit, IconClose, tooltip } from '@hcengineering/ui'
import { Icon, IconClose, IconEdit, Label, tooltip } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import Circles from '../icons/Circles.svelte'
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
@@ -23,7 +23,6 @@
export let value: IssueStatus
export let isDefault = false
export let isSingle = true
export let issueStatuses: IssueStatus[] | undefined = undefined
const dispatch = createEventDispatcher()
@@ -38,7 +37,7 @@
<Circles />
</div>
<div class="flex-no-shrink ml-2">
<IssueStatusIcon {value} size="small" {issueStatuses} />
<IssueStatusIcon {value} size="small" />
</div>
<span class="content-accent-color ml-2">{value.name}</span>
{#if value.description}
@@ -31,6 +31,7 @@
import { createEventDispatcher } from 'svelte'
import { flip } from 'svelte/animate'
import tracker from '../../plugin'
import { statusStore } from '../../utils'
import StatusEditor from './StatusEditor.svelte'
import StatusPresenter from './StatusPresenter.svelte'
@@ -40,11 +41,9 @@
const client = getClient()
const dispatch = createEventDispatcher()
const projectQuery = createQuery()
const statusesQuery = createQuery()
let project: Project | undefined
let statusCategories: IssueStatusCategory[] | undefined
let workflowStatuses: IssueStatus[] | undefined
let editingStatus: IssueStatus | Partial<AttachedData<IssueStatus>> | null = null
let draggingStatus: IssueStatus | null = null
@@ -67,10 +66,10 @@
}
async function addStatus () {
if (editingStatus?.name && editingStatus?.category && workflowStatuses) {
const categoryStatuses = workflowStatuses.filter((s) => s.category === editingStatus!.category)
if (editingStatus?.name && editingStatus?.category && $statusStore) {
const categoryStatuses = $statusStore.filter((s) => s.category === editingStatus!.category)
const prevStatus = categoryStatuses[categoryStatuses.length - 1]
const nextStatus = workflowStatuses[workflowStatuses.findIndex(({ _id }) => _id === prevStatus._id) + 1]
const nextStatus = $statusStore[$statusStore.findIndex(({ _id }) => _id === prevStatus._id) + 1]
isSaving = true
await client.addCollection(
@@ -94,15 +93,9 @@
}
async function editStatus () {
if (
workflowStatuses &&
statusCategories &&
editingStatus?.name &&
editingStatus?.category &&
'_id' in editingStatus
) {
if ($statusStore && statusCategories && editingStatus?.name && editingStatus?.category && '_id' in editingStatus) {
const statusId = '_id' in editingStatus ? editingStatus._id : undefined
const status = statusId && workflowStatuses.find(({ _id }) => _id === statusId)
const status = statusId && $statusStore.find(({ _id }) => _id === statusId)
if (!status) {
return
@@ -164,14 +157,12 @@
},
undefined,
async (result) => {
if (result && project && workflowStatuses) {
if (result && project && $statusStore) {
isSaving = true
await client.removeDoc(status._class, status.space, status._id)
if (project.defaultIssueStatus === status._id) {
const newDefaultStatus = workflowStatuses.find(
(s) => s._id !== status._id && s.category === status.category
)
const newDefaultStatus = $statusStore.find((s) => s._id !== status._id && s.category === status.category)
if (newDefaultStatus?._id) {
await updateProjectDefaultStatus(newDefaultStatus._id)
}
@@ -205,12 +196,12 @@
}
async function handleDrop (toItem: IssueStatus) {
if (workflowStatuses && draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) {
if ($statusStore && draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) {
const fromIndex = getStatusIndex(draggingStatus)
const toIndex = getStatusIndex(toItem)
const [prev, next] = [
workflowStatuses[fromIndex < toIndex ? toIndex : toIndex - 1],
workflowStatuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
$statusStore[fromIndex < toIndex ? toIndex : toIndex - 1],
$statusStore[fromIndex < toIndex ? toIndex + 1 : toIndex]
]
isSaving = true
@@ -222,7 +213,7 @@
}
function getStatusIndex (status: IssueStatus) {
return workflowStatuses?.findIndex(({ _id }) => _id === status._id) ?? -1
return $statusStore?.findIndex(({ _id }) => _id === status._id) ?? -1
}
function resetDrag () {
@@ -231,9 +222,6 @@
}
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
$: statusesQuery.query(tracker.class.IssueStatus, { attachedTo: projectId }, (res) => (workflowStatuses = res), {
sort: { rank: SortingOrder.Ascending }
})
$: updateStatusCategories()
</script>
@@ -254,13 +242,14 @@
</div>
</svelte:fragment>
{#if project === undefined || statusCategories === undefined || workflowStatuses === undefined}
{#if project === undefined || statusCategories === undefined || $statusStore === undefined}
<Loading />
{:else}
<Scroller>
<div class="popupPanel-body__main-content py-10 clear-mins">
{#each statusCategories as category}
{@const statuses = workflowStatuses?.filter((s) => s.category === category._id) ?? []}
{@const statuses =
$statusStore?.filter((s) => s.attachedTo === projectId && s.category === category._id) ?? []}
{@const isSingle = statuses.length === 1}
<div class="flex-between category-name">
<Label label={category.label} />
@@ -312,7 +301,6 @@
editingStatus = { ...detail, color: detail.color ?? category.color }
}}
on:delete={deleteStatus}
issueStatuses={workflowStatuses}
/>
{/if}
</div>
-1
View File
@@ -301,7 +301,6 @@ export default mergeIds(trackerId, tracker, {
DefaultTimeReportDay: '' as IntlString,
DefaultAssignee: '' as IntlString,
WorkDayLength: '' as IntlString,
SevenHoursLength: '' as IntlString,
EightHoursLength: '' as IntlString,
HourLabel: '' as IntlString,
+23
View File
@@ -19,6 +19,7 @@ import core, {
Class,
Doc,
DocumentQuery,
IdMap,
Ref,
SortingOrder,
Space,
@@ -55,6 +56,7 @@ import {
MILLISECONDS_IN_WEEK
} from '@hcengineering/ui'
import { CategoryQuery, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
import { writable } from 'svelte/store'
import tracker from './plugin'
import { defaultPriorities, defaultComponentStatuses, defaultSprintStatuses, issuePriorities } from './types'
@@ -655,3 +657,24 @@ export async function removeProject (project: Project): Promise<void> {
const client = getClient()
await client.removeDoc(tracker.class.Project, core.space.Space, project._id)
}
// Issue status live query
export const statusByIdStore = writable<IdMap<WithLookup<IssueStatus>>>(new Map())
export const statusStore = writable<Array<WithLookup<IssueStatus>>>([])
const query = createQuery(true)
query.query(
tracker.class.IssueStatus,
{},
(res) => {
statusStore.set(res)
statusByIdStore.set(toIdMap(res))
},
{
lookup: {
category: tracker.class.IssueStatusCategory
},
sort: {
rank: SortingOrder.Ascending
}
}
)
-9
View File
@@ -54,7 +54,6 @@ export interface Project extends Space {
defaultIssueStatus: Ref<IssueStatus>
defaultAssignee?: Ref<Employee>
icon?: Asset
workDayLength?: WorkDayLength
defaultTimeReportDay: TimeReportDayType
}
@@ -66,14 +65,6 @@ export enum TimeReportDayType {
PreviousWorkDay = 'PreviousWorkDay'
}
/**
* @public
*/
export enum WorkDayLength {
SEVEN_HOURS = 7,
EIGHT_HOURS = 8
}
/**
* @public
*/