mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-20 08:37:53 +02:00
UBERF-4248: Task type (#4042)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -33,7 +33,8 @@
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { calcRank } from '@hcengineering/task'
|
||||
import { TaskType, calcRank } from '@hcengineering/task'
|
||||
import { TaskKindSelector } from '@hcengineering/task-resources'
|
||||
import {
|
||||
Component as ComponentType,
|
||||
Issue,
|
||||
@@ -62,9 +63,8 @@
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { activeComponent, activeMilestone, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
||||
import tracker from '../plugin'
|
||||
import ComponentSelector from './components/ComponentSelector.svelte'
|
||||
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import ComponentSelector from './components/ComponentSelector.svelte'
|
||||
import AssigneeEditor from './issues/AssigneeEditor.svelte'
|
||||
import IssueNotification from './issues/IssueNotification.svelte'
|
||||
import ParentIssue from './issues/ParentIssue.svelte'
|
||||
@@ -104,18 +104,24 @@
|
||||
const parentQuery = createQuery()
|
||||
|
||||
let _space = draft?.space ?? space
|
||||
let project: Project | undefined
|
||||
let object = getDefaultObjectFromDraft() ?? getDefaultObject(id)
|
||||
let isAssigneeTouched = false
|
||||
let kind: Ref<TaskType> | undefined
|
||||
|
||||
function objectChange (object: IssueDraft, empty: any) {
|
||||
let templateId: Ref<IssueTemplate> | undefined = draft?.template?.template
|
||||
let appliedTemplateId: Ref<IssueTemplate> | undefined = draft?.template?.template
|
||||
|
||||
let template: IssueTemplate | undefined = undefined
|
||||
const templateQuery = createQuery()
|
||||
|
||||
function objectChange (object: IssueDraft, empty: any): void {
|
||||
if (shouldSaveDraft) {
|
||||
draftController.save(object, empty)
|
||||
}
|
||||
}
|
||||
|
||||
$: objectChange(object, empty)
|
||||
|
||||
$: if (object.parentIssue) {
|
||||
$: if (object.parentIssue !== undefined) {
|
||||
parentQuery.query(
|
||||
tracker.class.Issue,
|
||||
{
|
||||
@@ -131,7 +137,7 @@
|
||||
}
|
||||
|
||||
function getDefaultObjectFromDraft (): IssueDraft | undefined {
|
||||
if (!draft) {
|
||||
if (draft == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -163,7 +169,7 @@
|
||||
parentIssue: parentIssue?._id,
|
||||
subIssues: []
|
||||
}
|
||||
if (originalIssue && !ignoreOriginal) {
|
||||
if (originalIssue !== undefined && !ignoreOriginal) {
|
||||
const res: IssueDraft = {
|
||||
...base,
|
||||
description: originalIssue.description,
|
||||
@@ -176,11 +182,11 @@
|
||||
parentIssue: originalIssue.parents[0]?.parentId,
|
||||
title: `${originalIssue.title} (copy)`
|
||||
}
|
||||
client.findAll(tags.class.TagReference, { attachedTo: originalIssue._id }).then((p) => {
|
||||
void client.findAll(tags.class.TagReference, { attachedTo: originalIssue._id }).then((p) => {
|
||||
object.labels = p
|
||||
})
|
||||
if (originalIssue.relations?.[0]) {
|
||||
client.findOne(tracker.class.Issue, { _id: originalIssue.relations[0]._id as Ref<Issue> }).then((p) => {
|
||||
if (originalIssue.relations?.[0] !== undefined) {
|
||||
void client.findOne(tracker.class.Issue, { _id: originalIssue.relations[0]._id as Ref<Issue> }).then((p) => {
|
||||
relatedTo = p
|
||||
})
|
||||
}
|
||||
@@ -191,13 +197,11 @@
|
||||
}
|
||||
fillDefaults(hierarchy, object, tracker.class.Issue)
|
||||
|
||||
let subIssuesComponent: SubIssues
|
||||
|
||||
let currentProject: Project | undefined
|
||||
|
||||
$: updateIssueStatusId(object, currentProject)
|
||||
$: updateAssigneeId(object, currentProject)
|
||||
$: canSave = getTitle(object.title ?? '').length > 0 && object.status !== undefined
|
||||
$: canSave = getTitle(object.title ?? '').length > 0 && object.status !== undefined && kind !== undefined
|
||||
|
||||
$: empty = {
|
||||
assignee: assignee ?? currentProject?.defaultAssignee,
|
||||
@@ -210,6 +214,8 @@
|
||||
space: _space
|
||||
}
|
||||
|
||||
$: objectChange(object, empty)
|
||||
|
||||
$: if (_space !== undefined && object.space !== _space) {
|
||||
object.space = _space
|
||||
}
|
||||
@@ -221,12 +227,6 @@
|
||||
fillDefaults(hierarchy, object, tracker.class.Issue)
|
||||
}
|
||||
|
||||
let templateId: Ref<IssueTemplate> | undefined = draft?.template?.template
|
||||
let appliedTemplateId: Ref<IssueTemplate> | undefined = draft?.template?.template
|
||||
|
||||
let template: IssueTemplate | undefined = undefined
|
||||
const templateQuery = createQuery()
|
||||
|
||||
$: if (templateId !== undefined) {
|
||||
templateQuery.query(tracker.class.IssueTemplate, { _id: templateId }, (res) => {
|
||||
template = res[0]
|
||||
@@ -284,7 +284,9 @@
|
||||
fillDefaults(hierarchy, object, tracker.class.Issue)
|
||||
}
|
||||
|
||||
$: template && updateTemplate(template)
|
||||
$: if (template !== undefined) {
|
||||
void updateTemplate(template)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const spaceQuery = createQuery()
|
||||
@@ -303,19 +305,19 @@
|
||||
|
||||
const docCreateManager = DocCreateExtensionManager.create(tracker.class.Issue)
|
||||
|
||||
async function updateIssueStatusId (object: IssueDraft, currentProject: Project | undefined) {
|
||||
if (currentProject?.defaultIssueStatus && object.status === undefined) {
|
||||
function updateIssueStatusId (object: IssueDraft, currentProject: Project | undefined): void {
|
||||
if (currentProject?.defaultIssueStatus !== undefined && object.status === undefined) {
|
||||
object.status = currentProject.defaultIssueStatus
|
||||
}
|
||||
}
|
||||
|
||||
function resetDefaultAssigneeId () {
|
||||
if (!isAssigneeTouched && !!object.assignee && object.assignee === currentProject?.defaultAssignee) {
|
||||
function resetDefaultAssigneeId (): void {
|
||||
if (!isAssigneeTouched && !(object.assignee == null) && object.assignee === currentProject?.defaultAssignee) {
|
||||
object = { ...object, assignee: assignee ?? null }
|
||||
}
|
||||
}
|
||||
|
||||
function updateAssigneeId (object: IssueDraft, currentProject: Project | undefined) {
|
||||
function updateAssigneeId (object: IssueDraft, currentProject: Project | undefined): void {
|
||||
if (!isAssigneeTouched && object.assignee == null && currentProject !== undefined) {
|
||||
if (currentProject.defaultAssignee !== undefined) {
|
||||
object.assignee = currentProject.defaultAssignee
|
||||
@@ -324,13 +326,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function clearParentIssue () {
|
||||
function clearParentIssue (): void {
|
||||
object.parentIssue = undefined
|
||||
parentQuery.unsubscribe()
|
||||
parentIssue = undefined
|
||||
}
|
||||
|
||||
function getTitle (value: string) {
|
||||
function getTitle (value: string): string {
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
@@ -338,7 +340,7 @@
|
||||
return true
|
||||
}
|
||||
|
||||
export async function onOutsideClick () {
|
||||
export function onOutsideClick (): void {
|
||||
if (shouldSaveDraft) {
|
||||
draftController.save(object, empty)
|
||||
}
|
||||
@@ -346,7 +348,7 @@
|
||||
|
||||
async function createIssue (): Promise<void> {
|
||||
const _id: Ref<Issue> = generateId()
|
||||
if (!canSave || object.status === undefined || _space === undefined) {
|
||||
if (!canSave || object.status === undefined || _space === undefined || kind === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,18 +378,20 @@
|
||||
comments: 0,
|
||||
subIssues: 0,
|
||||
dueDate: object.dueDate,
|
||||
parents: parentIssue
|
||||
? [
|
||||
{ parentId: parentIssue._id, parentTitle: parentIssue.title, space: parentIssue.space },
|
||||
...parentIssue.parents
|
||||
]
|
||||
: [],
|
||||
parents:
|
||||
parentIssue != null
|
||||
? [
|
||||
{ parentId: parentIssue._id, parentTitle: parentIssue.title, space: parentIssue.space },
|
||||
...parentIssue.parents
|
||||
]
|
||||
: [],
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: object.estimation,
|
||||
reports: 0,
|
||||
relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : [],
|
||||
childInfo: []
|
||||
childInfo: [],
|
||||
kind
|
||||
}
|
||||
|
||||
await docCreateManager.commit(operations, _id, _space, value)
|
||||
@@ -423,14 +427,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
const parents = parentIssue
|
||||
? [
|
||||
{ parentId: _id, parentTitle: value.title, space: parentIssue.space },
|
||||
{ parentId: parentIssue._id, parentTitle: parentIssue.title, space: parentIssue.space },
|
||||
...parentIssue.parents
|
||||
]
|
||||
: [{ parentId: _id, parentTitle: value.title, space: _space }]
|
||||
await subIssuesComponent.save(parents, _id)
|
||||
addNotification(
|
||||
await translate(tracker.string.IssueCreated, {}, $themeStore.language),
|
||||
getTitle(object.title),
|
||||
@@ -438,17 +434,16 @@
|
||||
{
|
||||
issueId: _id,
|
||||
subTitlePostfix: (await translate(tracker.string.CreatedOne, {}, $themeStore.language)).toLowerCase(),
|
||||
issueUrl: currentProject && generateIssueShortLink(getIssueId(currentProject, value as Issue))
|
||||
issueUrl: currentProject != null && generateIssueShortLink(getIssueId(currentProject, value as Issue))
|
||||
}
|
||||
)
|
||||
|
||||
draftController.remove()
|
||||
resetObject()
|
||||
descriptionBox?.removeDraft(false)
|
||||
isAssigneeTouched = false
|
||||
}
|
||||
|
||||
async function setParentIssue () {
|
||||
async function setParentIssue (): Promise<void> {
|
||||
showPopup(
|
||||
SetParentIssueActionPopup,
|
||||
{ value: { ...object, space: _space, attachedTo: parentIssue?._id } },
|
||||
@@ -462,7 +457,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
const handleComponentIdChanged = (componentId: Ref<ComponentType> | null | undefined) => {
|
||||
const handleComponentIdChanged = (componentId: Ref<ComponentType> | null | undefined): void => {
|
||||
if (componentId === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -470,7 +465,7 @@
|
||||
object.component = componentId
|
||||
}
|
||||
|
||||
const handleMilestoneIdChanged = async (milestoneId: Ref<Milestone> | null | undefined) => {
|
||||
const handleMilestoneIdChanged = (milestoneId: Ref<Milestone> | null | undefined): void => {
|
||||
if (milestoneId === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -508,7 +503,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function showConfirmationDialog () {
|
||||
async function showConfirmationDialog (): Promise<void> {
|
||||
draftController.save(object, empty)
|
||||
const isFormEmpty = draft === undefined
|
||||
|
||||
@@ -602,7 +597,7 @@
|
||||
on:close={() => dispatch('close')}
|
||||
onCancel={showConfirmationDialog}
|
||||
hideAttachments={attachments.size === 0}
|
||||
hideSubheader={!parentIssue}
|
||||
hideSubheader={parentIssue == null}
|
||||
noFade={true}
|
||||
on:changeContent
|
||||
>
|
||||
@@ -611,6 +606,9 @@
|
||||
_class={tracker.class.Project}
|
||||
label={tracker.string.Project}
|
||||
bind:space={_space}
|
||||
on:object={(evt) => {
|
||||
project = evt.detail
|
||||
}}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
component={ProjectPresenter}
|
||||
@@ -641,6 +639,7 @@
|
||||
<div class="mr-2">
|
||||
<Label {label} />
|
||||
</div>
|
||||
<TaskKindSelector projectType={project?.type} bind:taskType={kind} baseClass={tracker.class.Issue} />
|
||||
{#if relatedTo}
|
||||
<div class="lower mr-2">
|
||||
<Label label={tracker.string.RelatedTo} />
|
||||
@@ -700,7 +699,7 @@
|
||||
}}
|
||||
on:attachments={(ev) => {
|
||||
if (ev.detail.size > 0) attachments = ev.detail.values
|
||||
else if (ev.detail.size === 0 && ev.detail.values) {
|
||||
else if (ev.detail.size === 0 && ev.detail.values != null) {
|
||||
attachments.clear()
|
||||
attachments = attachments
|
||||
}
|
||||
@@ -708,36 +707,28 @@
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{#if _space}
|
||||
<SubIssues
|
||||
bind:this={subIssuesComponent}
|
||||
projectId={_space}
|
||||
project={currentProject}
|
||||
milestone={object.milestone}
|
||||
component={object.component}
|
||||
bind:subIssues={object.subIssues}
|
||||
/>
|
||||
{/if}
|
||||
<DocCreateExtComponent manager={docCreateManager} kind={'body'} space={currentProject} props={extraProps} />
|
||||
<svelte:fragment slot="pool">
|
||||
<div id="status-editor">
|
||||
<StatusEditor
|
||||
focusIndex={3}
|
||||
value={object}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
defaultIssueStatus={currentProject?.defaultIssueStatus}
|
||||
shouldShowLabel={true}
|
||||
short
|
||||
on:refocus={() => {
|
||||
manager.setFocusPos(3)
|
||||
}}
|
||||
on:change={({ detail }) => {
|
||||
if (object.status !== detail) {
|
||||
object.status = detail
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if kind !== undefined}
|
||||
<StatusEditor
|
||||
focusIndex={3}
|
||||
value={{ ...object, kind }}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
defaultIssueStatus={currentProject?.defaultIssueStatus}
|
||||
shouldShowLabel={true}
|
||||
short
|
||||
on:refocus={() => {
|
||||
manager.setFocusPos(3)
|
||||
}}
|
||||
on:change={({ detail }) => {
|
||||
if (object.status !== detail) {
|
||||
object.status = detail
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div id="priority-editor">
|
||||
<PriorityEditor
|
||||
@@ -821,11 +812,11 @@
|
||||
<div id="parentissue-editor" class="new-line">
|
||||
<Button
|
||||
icon={tracker.icon.Parent}
|
||||
label={object.parentIssue ? tracker.string.RemoveParent : tracker.string.SetParent}
|
||||
label={object.parentIssue != null ? tracker.string.RemoveParent : tracker.string.SetParent}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
notSelected={object.parentIssue === undefined}
|
||||
on:click={object.parentIssue ? clearParentIssue : setParentIssue}
|
||||
on:click={object.parentIssue != null ? clearParentIssue : setParentIssue}
|
||||
/>
|
||||
</div>
|
||||
<DocCreateExtComponent manager={docCreateManager} kind={'pool'} space={currentProject} props={extraProps} />
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
allowDeselect
|
||||
kind={'list'}
|
||||
on:change={(evt) => {
|
||||
client.update(target, { target: evt.detail || null })
|
||||
void client.diffUpdate(target, { target: evt.detail || null })
|
||||
}}
|
||||
/>
|
||||
</FixedColumn>
|
||||
@@ -81,7 +81,7 @@
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
on:click={() => {
|
||||
client.remove(target)
|
||||
void client.remove(target)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -113,7 +113,7 @@
|
||||
allowDeselect
|
||||
kind={'list'}
|
||||
on:change={(evt) => {
|
||||
client.createDoc(tracker.class.RelatedIssueTarget, space, {
|
||||
void client.createDoc(tracker.class.RelatedIssueTarget, space, {
|
||||
target: evt.detail || null,
|
||||
rule: {
|
||||
kind: 'spaceRule',
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2022 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 attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { deleteFile } from '@hcengineering/attachment-resources/src/utils'
|
||||
import core, { AttachedData, Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { calcRank } from '@hcengineering/task'
|
||||
import { Component, Issue, IssueDraft, IssueParentInfo, Milestone, Project } from '@hcengineering/tracker'
|
||||
import { Button, ExpandCollapse, Scroller } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import tracker from '../plugin'
|
||||
import Collapsed from './icons/Collapsed.svelte'
|
||||
import Expanded from './icons/Expanded.svelte'
|
||||
import DraftIssueChildList from './templates/DraftIssueChildList.svelte'
|
||||
|
||||
export let projectId: Ref<Project>
|
||||
export let project: Project | undefined
|
||||
export let milestone: Ref<Milestone> | null = null
|
||||
export let component: Ref<Component> | null = null
|
||||
export let subIssues: IssueDraft[] = []
|
||||
|
||||
let lastProject = project
|
||||
let isCollapsed = false
|
||||
|
||||
async function handleIssueSwap (ev: CustomEvent<{ fromIndex: number, toIndex: number }>) {
|
||||
if (subIssues) {
|
||||
const { fromIndex, toIndex } = ev.detail
|
||||
const [fromIssue] = subIssues.splice(fromIndex, 1)
|
||||
const leftPart = subIssues.slice(0, toIndex)
|
||||
const rightPart = subIssues.slice(toIndex)
|
||||
subIssues = [...leftPart, fromIssue, ...rightPart]
|
||||
}
|
||||
}
|
||||
|
||||
$: onProjectChange(project)
|
||||
|
||||
function onProjectChange (project: Project | undefined) {
|
||||
if (lastProject?._id === project?._id) return
|
||||
lastProject = project
|
||||
if (project === undefined) return
|
||||
subIssues.forEach((p) => {
|
||||
p.status = project.defaultIssueStatus
|
||||
p.space = project._id
|
||||
})
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
// TODO: move to utils
|
||||
export async function save (parents: IssueParentInfo[], _id: Ref<Doc>) {
|
||||
if (project === undefined) return
|
||||
saved = true
|
||||
|
||||
for (const subIssue of subIssues) {
|
||||
const lastOne = await client.findOne<Issue>(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const incResult = await client.updateDoc(
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
project._id,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
const childId = subIssue._id
|
||||
const cvalue: AttachedData<Issue> = {
|
||||
title: subIssue.title.trim(),
|
||||
description: subIssue.description,
|
||||
assignee: subIssue.assignee,
|
||||
component: subIssue.component,
|
||||
milestone: subIssue.milestone,
|
||||
number: (incResult as any).object.sequence,
|
||||
status: subIssue.status ?? project.defaultIssueStatus,
|
||||
priority: subIssue.priority,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
comments: 0,
|
||||
subIssues: 0,
|
||||
dueDate: null,
|
||||
parents,
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: subIssue.estimation,
|
||||
reports: 0,
|
||||
relations: [],
|
||||
childInfo: []
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
tracker.class.Issue,
|
||||
project._id,
|
||||
_id,
|
||||
tracker.class.Issue,
|
||||
'subIssues',
|
||||
cvalue,
|
||||
childId
|
||||
)
|
||||
|
||||
if ((subIssue.labels?.length ?? 0) > 0) {
|
||||
for (const label of subIssue.labels) {
|
||||
await client.addCollection(tags.class.TagReference, project._id, childId, tracker.class.Issue, 'labels', {
|
||||
title: label.title,
|
||||
color: label.color,
|
||||
tag: label.tag
|
||||
})
|
||||
}
|
||||
}
|
||||
saveAttachments(childId)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAttachments (issue: Ref<Issue>) {
|
||||
const draftAttachments = $draftsStore[`${issue}_attachments`]
|
||||
if (draftAttachments) {
|
||||
for (const key in draftAttachments) {
|
||||
await saveAttachment(draftAttachments[key as Ref<Attachment>], issue)
|
||||
}
|
||||
}
|
||||
removeDraft(issue)
|
||||
}
|
||||
|
||||
async function saveAttachment (doc: Attachment, issue: Ref<Issue>): Promise<void> {
|
||||
await client.addCollection(
|
||||
attachment.class.Attachment,
|
||||
projectId,
|
||||
issue,
|
||||
tracker.class.Issue,
|
||||
'attachments',
|
||||
doc,
|
||||
doc._id
|
||||
)
|
||||
}
|
||||
|
||||
export function load (value: IssueDraft[]) {
|
||||
subIssues = value
|
||||
}
|
||||
|
||||
let saved = false
|
||||
|
||||
onDestroy(() => {
|
||||
if (!saved) {
|
||||
subIssues.forEach((st) => {
|
||||
removeDraft(st._id, true)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: move to utils
|
||||
export async function removeDraft (_id: string, removeFiles: boolean = false): Promise<void> {
|
||||
const draftAttachments = $draftsStore[`${_id}_attachments`]
|
||||
DraftController.remove(`${_id}_attachments`)
|
||||
if (removeFiles && draftAttachments) {
|
||||
for (const key in draftAttachments) {
|
||||
const attachment = draftAttachments[key as Ref<Attachment>]
|
||||
await deleteFile(attachment.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- TODO: check if sub issues list is empty in a parent component -->
|
||||
{#if subIssues.length > 0}
|
||||
<div class="flex-between clear-mins">
|
||||
<Button
|
||||
width="min-content"
|
||||
icon={isCollapsed ? Collapsed : Expanded}
|
||||
size="small"
|
||||
kind="ghost"
|
||||
label={tracker.string.SubIssuesList}
|
||||
labelParams={{ subIssues: subIssues.length }}
|
||||
on:click={() => (isCollapsed = !isCollapsed)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
||||
<div class="flex-col flex-no-shrink max-h-30 list clear-mins" class:collapsed={isCollapsed}>
|
||||
<Scroller>
|
||||
<DraftIssueChildList
|
||||
{component}
|
||||
{milestone}
|
||||
bind:issues={subIssues}
|
||||
project={projectId}
|
||||
on:move={handleIssueSwap}
|
||||
on:update-issue
|
||||
/>
|
||||
</Scroller>
|
||||
</div>
|
||||
</ExpandCollapse>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.list {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
|
||||
&.collapsed {
|
||||
padding-top: 1px;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -47,7 +47,7 @@
|
||||
let docWidth: number
|
||||
let docSize = false
|
||||
|
||||
function showCreateDialog () {
|
||||
function showCreateDialog (): void {
|
||||
showPopup(NewComponent, { space, targetElement: null }, 'top')
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { ColorDefinition, IconSize, getPlatformColorDef, themeStore } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import task from '@hcengineering/task'
|
||||
|
||||
export let size: IconSize
|
||||
|
||||
@@ -37,7 +38,7 @@
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{#if category._id === tracker.issueStatusCategory.Backlog}
|
||||
{#if category._id === tracker.issueStatusCategory.Backlog || category._id === task.statusCategory.UnStarted}
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
import { DocNavLink } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import { activeProjects } from '../../utils'
|
||||
import { taskTypeStore } from '@hcengineering/task-resources'
|
||||
import TaskTypeIcon from '@hcengineering/task-resources/src/components/taskTypes/TaskTypeIcon.svelte'
|
||||
|
||||
export let value: WithLookup<Issue>
|
||||
export let disabled: boolean = false
|
||||
@@ -36,17 +38,24 @@
|
||||
let currentProject: Project | undefined = value?.$lookup?.space
|
||||
|
||||
$: if (value !== undefined) {
|
||||
currentProject = $activeProjects.get(value?.space)
|
||||
currentProject = $activeProjects.get(value?.space) as Project
|
||||
}
|
||||
|
||||
$: title = currentProject ? `${currentProject.identifier}-${value?.number}` : `${value?.number}`
|
||||
|
||||
$: presenters =
|
||||
value !== undefined ? getClient().getHierarchy().findMixinMixins(value, view.mixin.ObjectPresenter) : []
|
||||
|
||||
$: taskType = $taskTypeStore.get(value.kind)
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class="flex-row-center flex-between">
|
||||
<div class="flex-row-center">
|
||||
{#if taskType !== undefined}
|
||||
<div class="text-sm mr-1">
|
||||
<TaskTypeIcon value={taskType} />
|
||||
</div>
|
||||
{/if}
|
||||
<DocNavLink
|
||||
object={value}
|
||||
{onClick}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
let currentProject: Project | undefined
|
||||
|
||||
$: currentProject = $activeProjects.get(space)
|
||||
$: currentProject = $activeProjects.get(space) as Project
|
||||
$: issueName = currentProject && issue && `${currentProject.identifier}-${issue.number}`
|
||||
|
||||
const limit: number = 350
|
||||
|
||||
@@ -14,18 +14,29 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Ref, StatusCategory, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ProjectType, getStates } from '@hcengineering/task'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
ProjectType,
|
||||
TaskType,
|
||||
getProjectTypeStates,
|
||||
getStates,
|
||||
getTaskTypeStates,
|
||||
isTaskCategory
|
||||
} from '@hcengineering/task'
|
||||
import { taskTypeStore, typeStore } from '@hcengineering/task-resources'
|
||||
import StatePresenter from '@hcengineering/task-resources/src/components/state/StatePresenter.svelte'
|
||||
import { IssueStatus, Project } from '@hcengineering/tracker'
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
import { statusStore } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import { activeProjects } from '../../utils'
|
||||
import StatusIcon from '../icons/StatusIcon.svelte'
|
||||
|
||||
export let value: WithLookup<IssueStatus> | undefined
|
||||
export let size: IconSize
|
||||
export let space: Ref<Project> | undefined
|
||||
export let space: Ref<Project> | undefined = undefined
|
||||
export let projectType: Ref<ProjectType> | undefined = undefined
|
||||
export let taskType: Ref<TaskType> | undefined = undefined
|
||||
|
||||
const dynamicFillCategories = [tracker.issueStatusCategory.Started]
|
||||
|
||||
@@ -38,22 +49,30 @@
|
||||
count: number | undefined
|
||||
} = { index: undefined, count: undefined }
|
||||
|
||||
const spaceQuery = createQuery()
|
||||
let _space: Project | undefined = undefined
|
||||
$: space
|
||||
? spaceQuery.query(tracker.class.Project, { _id: space }, (res) => {
|
||||
_space = res[0]
|
||||
})
|
||||
: (_space = undefined)
|
||||
$: _space = space !== undefined ? ($activeProjects.get(space) as Project) : undefined
|
||||
|
||||
$: if (value?.category === tracker.issueStatusCategory.Started) {
|
||||
statuses = getStates(_space, $typeStore, $statusStore.byId).filter(
|
||||
(p) => p.category === tracker.issueStatusCategory.Started
|
||||
)
|
||||
if (taskType !== undefined) {
|
||||
statuses = getTaskTypeStates(taskType, $taskTypeStore, $statusStore.byId).filter(
|
||||
(p) => p.category === tracker.issueStatusCategory.Started
|
||||
)
|
||||
} else if (projectType !== undefined) {
|
||||
statuses = getProjectTypeStates(projectType, $typeStore, $statusStore.byId).filter(
|
||||
(p) => p.category === tracker.issueStatusCategory.Started
|
||||
)
|
||||
} else {
|
||||
statuses = getStates(_space, $typeStore, $statusStore.byId).filter(
|
||||
(p) => p.category === tracker.issueStatusCategory.Started
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCategory (_space: Project | undefined, status: WithLookup<IssueStatus>, statuses: IssueStatus[]) {
|
||||
if (status.$lookup?.category) {
|
||||
async function updateCategory (
|
||||
_space: Project | undefined,
|
||||
status: WithLookup<IssueStatus>,
|
||||
statuses: IssueStatus[]
|
||||
): Promise<void> {
|
||||
if (status.$lookup?.category !== undefined) {
|
||||
category = status.$lookup.category
|
||||
}
|
||||
if (category === undefined || category._id !== value?.category) {
|
||||
@@ -83,15 +102,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: type = _space ? $typeStore.get(_space.type) : undefined
|
||||
$: type = _space ? $typeStore.get(_space.type) : projectType !== undefined ? $typeStore.get(projectType) : undefined
|
||||
|
||||
$: viewState = value && getViewState(type, value)
|
||||
|
||||
$: value && updateCategory(_space, value, statuses)
|
||||
$: if (value !== undefined) {
|
||||
void updateCategory(_space, value, statuses)
|
||||
}
|
||||
$: icon = category?.icon
|
||||
$: color = viewState?.color !== undefined ? viewState?.color : category !== undefined ? category.color : -1
|
||||
</script>
|
||||
|
||||
{#if icon !== undefined && color !== undefined && category !== undefined}
|
||||
{#if category !== undefined && isTaskCategory(category._id)}
|
||||
<StatePresenter
|
||||
{space}
|
||||
projectType={_space?.type ?? projectType}
|
||||
{taskType}
|
||||
{value}
|
||||
{size}
|
||||
shouldShowName={false}
|
||||
on:accent-color
|
||||
/>
|
||||
{:else if icon !== undefined && color !== undefined && category !== undefined}
|
||||
<StatusIcon on:accent-color {category} {size} fill={color} {statusIcon} />
|
||||
{/if}
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
|
||||
import { resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import view, { Viewlet } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { IModeSelector } from '@hcengineering/ui'
|
||||
import { TypeSelector, selectedTaskTypeStore, selectedTypeStore, taskTypeStore } from '@hcengineering/task-resources'
|
||||
import tracker from '../../plugin'
|
||||
import IssuesView from './IssuesView.svelte'
|
||||
|
||||
@@ -28,8 +29,12 @@
|
||||
export let baseQuery: DocumentQuery<Issue> = {}
|
||||
export let title: IntlString
|
||||
export let config: [string, IntlString, object][]
|
||||
export let allProjectsTypes: boolean = false
|
||||
|
||||
export let baseClass = tracker.class.Issue
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let query: DocumentQuery<Issue> | undefined = undefined
|
||||
let modeSelectorProps: IModeSelector | undefined = undefined
|
||||
|
||||
@@ -89,8 +94,29 @@
|
||||
onChange: (newMode: string) => dispatch('action', { mode: newMode })
|
||||
}
|
||||
}
|
||||
|
||||
$: allTypes = Array.from($taskTypeStore.values())
|
||||
.filter((it) => it.parent === $selectedTypeStore)
|
||||
.map((it) => it._id)
|
||||
|
||||
$: finalQuery = {
|
||||
...query,
|
||||
...(allProjectsTypes
|
||||
? {}
|
||||
: $selectedTaskTypeStore !== undefined
|
||||
? { kind: $selectedTaskTypeStore }
|
||||
: { kind: { $in: allTypes } })
|
||||
}
|
||||
|
||||
const toVL = (data: any): Viewlet | undefined => data as Viewlet
|
||||
</script>
|
||||
|
||||
{#if query !== undefined && modeSelectorProps !== undefined}
|
||||
<IssuesView {query} space={currentSpace} {title} {modeSelectorProps} />
|
||||
<IssuesView query={finalQuery} space={currentSpace} {title} {modeSelectorProps}>
|
||||
<svelte:fragment slot="type_selector" let:viewlet>
|
||||
{#if !allProjectsTypes}
|
||||
<TypeSelector {baseClass} project={currentSpace} allTypes={toVL(viewlet)?.descriptor === view.viewlet.List} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</IssuesView>
|
||||
{/if}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
let resultQuery: DocumentQuery<Issue> = { ...searchQuery }
|
||||
|
||||
$: if (!label && title) {
|
||||
translate(title, {}, $themeStore.language).then((res) => {
|
||||
void translate(title, {}, $themeStore.language).then((res) => {
|
||||
label = res
|
||||
})
|
||||
}
|
||||
@@ -61,6 +61,11 @@
|
||||
<svelte:fragment slot="label_selector">
|
||||
<slot name="label_selector" />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="type_selector">
|
||||
<slot name="type_selector" {viewlet} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="extra">
|
||||
<ComponentExtensions
|
||||
extension={tracker.extensions.IssueListHeader}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
$: currentSpace = space ?? tracker.project.DefaultProject
|
||||
let currentProject: Project | undefined
|
||||
$: currentProject = $activeProjects.get(currentSpace)
|
||||
$: currentProject = $activeProjects.get(currentSpace) as Project
|
||||
|
||||
let resultQuery: DocumentQuery<any> = { ...query }
|
||||
const client = getClient()
|
||||
@@ -194,6 +194,7 @@
|
||||
$: listProvider.update(tasks)
|
||||
|
||||
let categories: CategoryType[] = []
|
||||
let loadCategories = true
|
||||
|
||||
const queryId = generateId()
|
||||
|
||||
@@ -211,6 +212,7 @@
|
||||
queryId
|
||||
).then((res) => {
|
||||
categories = res
|
||||
loadCategories = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -227,6 +229,7 @@
|
||||
queryId
|
||||
).then((res) => {
|
||||
categories = res
|
||||
loadCategories = false
|
||||
})
|
||||
|
||||
const fullFilled: Record<string, boolean> = {}
|
||||
@@ -305,7 +308,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if categories.length === 0}
|
||||
{#if loadCategories}
|
||||
<Loading />
|
||||
{:else}
|
||||
<ActionContext
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
let currentProject: Project | undefined = undefined
|
||||
|
||||
$: currentProject = $activeProjects.get(value.space)
|
||||
$: currentProject = $activeProjects.get(value.space) as Project
|
||||
|
||||
$: title = currentProject ? `${currentProject.identifier}-${value?.number}` : `${value?.number}`
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
<script lang="ts">
|
||||
import { AttachedData, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getStates } from '@hcengineering/task'
|
||||
import { getTaskTypeStates } from '@hcengineering/task'
|
||||
import { taskTypeStore } from '@hcengineering/task-resources'
|
||||
import { Issue, IssueDraft, IssueStatus, Project } from '@hcengineering/tracker'
|
||||
import {
|
||||
Button,
|
||||
@@ -30,10 +31,8 @@
|
||||
import { statusStore } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { activeProjects } from '../../utils'
|
||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||
import StatusPresenter from './StatusPresenter.svelte'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
type ValueType = Issue | (AttachedData<Issue> & { space: Ref<Project> }) | IssueDraft
|
||||
|
||||
export let value: ValueType
|
||||
@@ -84,11 +83,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
let space: Project | undefined = undefined
|
||||
|
||||
$: space = $activeProjects.get(value.space)
|
||||
|
||||
$: statuses = getStates(space, $typeStore, $statusStore.byId)
|
||||
$: statuses = getTaskTypeStates(value.kind, $taskTypeStore, $statusStore.byId)
|
||||
|
||||
function getSelectedStatus (
|
||||
statuses: WithLookup<IssueStatus>[] | undefined,
|
||||
@@ -97,13 +92,19 @@
|
||||
): WithLookup<IssueStatus> | undefined {
|
||||
if (value.status !== undefined) {
|
||||
const current = statuses?.find((status) => status._id === value.status)
|
||||
if (current) return current
|
||||
if (current != null) {
|
||||
return current
|
||||
}
|
||||
}
|
||||
if (defaultIssueStatus !== undefined) {
|
||||
const res = statuses?.find((status) => status._id === defaultStatus)
|
||||
changeStatus(res?._id, false)
|
||||
void changeStatus(res?._id, false)
|
||||
return res
|
||||
}
|
||||
// We need to choose first one, since it should not be case without status.
|
||||
if (value.status === undefined) {
|
||||
void changeStatus(statuses?.[0]?._id, false)
|
||||
}
|
||||
}
|
||||
|
||||
$: selectedStatus = getSelectedStatus(statuses, value, defaultIssueStatus)
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { IssueStatus, Project } from '@hcengineering/tracker'
|
||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||
import { ProjectType, TaskType } from '@hcengineering/task'
|
||||
|
||||
export let value: IssueStatus | undefined
|
||||
export let space: Ref<Project>
|
||||
export let space: Ref<Project> | undefined = undefined
|
||||
export let projectType: Ref<ProjectType> | undefined = undefined
|
||||
export let taskType: Ref<TaskType> | undefined = undefined
|
||||
export let size: 'small' | 'medium' = 'small'
|
||||
export let kind: 'list-header' | undefined = undefined
|
||||
export let colorInherit: boolean = false
|
||||
@@ -29,8 +32,8 @@
|
||||
|
||||
{#if value}
|
||||
<div class="flex-presenter" style:color={'inherit'}>
|
||||
{#if !inline && shouldShowAvatar}
|
||||
<IssueStatusIcon {value} {size} {space} on:accent-color />
|
||||
{#if !inline}
|
||||
<IssueStatusIcon {value} {size} {space} on:accent-color {projectType} {taskType} />
|
||||
{/if}
|
||||
<span
|
||||
class="overflow-label"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
import { statusStore } from '@hcengineering/view-resources'
|
||||
import StatusPresenter from './StatusPresenter.svelte'
|
||||
import { selectedTaskTypeStore } from '@hcengineering/task-resources'
|
||||
|
||||
export let value: Ref<Status> | undefined
|
||||
export let space: Ref<Project>
|
||||
@@ -29,5 +30,14 @@
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<StatusPresenter {space} value={statusValue} {size} {kind} {colorInherit} {accent} on:accent-color />
|
||||
<StatusPresenter
|
||||
{space}
|
||||
value={statusValue}
|
||||
{size}
|
||||
{kind}
|
||||
{colorInherit}
|
||||
{accent}
|
||||
on:accent-color
|
||||
taskType={$selectedTaskTypeStore}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
ButtonSize,
|
||||
IconSize,
|
||||
SelectPopup,
|
||||
SelectPopupValueType,
|
||||
TooltipAlignment,
|
||||
eventToHTMLElement,
|
||||
showPopup
|
||||
@@ -59,19 +60,6 @@
|
||||
dispatch('change', newStatus)
|
||||
}
|
||||
|
||||
const handleStatusEditorOpened = (event: MouseEvent) => {
|
||||
if (!isEditable) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus },
|
||||
eventToHTMLElement(event),
|
||||
changeStatus
|
||||
)
|
||||
}
|
||||
|
||||
$: statuses = getStatuses($statusStore.byId, $typeStore, type)
|
||||
|
||||
function getStatuses (
|
||||
@@ -100,16 +88,32 @@
|
||||
return current
|
||||
}
|
||||
|
||||
let statusesInfo: SelectPopupValueType[]
|
||||
|
||||
$: selectedStatus = getSelectedStatus(statuses, value)
|
||||
$: selectedStatusLabel = selectedStatus?.name
|
||||
$: statusesInfo = statuses?.map((s) => {
|
||||
return {
|
||||
id: s._id,
|
||||
component: StatusPresenter,
|
||||
props: { value: s, size: 'small' },
|
||||
isSelected: selectedStatus?._id === s._id ?? false
|
||||
$: statusesInfo =
|
||||
statuses?.map((s) => {
|
||||
return {
|
||||
id: s._id,
|
||||
component: StatusPresenter,
|
||||
props: { value: s, size: 'small' },
|
||||
isSelected: selectedStatus?._id === s._id ?? false
|
||||
}
|
||||
}) ?? []
|
||||
const handleStatusEditorOpened = (event: MouseEvent) => {
|
||||
if (!isEditable) {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus },
|
||||
eventToHTMLElement(event),
|
||||
changeStatus
|
||||
)
|
||||
}
|
||||
|
||||
$: smallgap = size === 'inline' || size === 'small'
|
||||
</script>
|
||||
|
||||
|
||||
+2
-2
@@ -22,9 +22,9 @@
|
||||
eventToHTMLElement,
|
||||
getEventPositionElement,
|
||||
ListView,
|
||||
showPopup
|
||||
showPopup,
|
||||
DatePresenter
|
||||
} from '@hcengineering/ui'
|
||||
import { DatePresenter } from '@hcengineering/ui'
|
||||
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
|
||||
import { getIssueId } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import type { Issue, IssueStatus, Project } from '@hcengineering/tracker'
|
||||
import { resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { resolvedLocationStore, IModeSelector } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { IModeSelector } from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
import IssuesView from '../issues/IssuesView.svelte'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { AccountArrayEditor, AssigneeBox } from '@hcengineering/contact-resources'
|
||||
import core, { Account, DocumentUpdate, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import core, { Account, Data, DocumentUpdate, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import task, { ProjectType } from '@hcengineering/task'
|
||||
@@ -42,6 +42,7 @@
|
||||
import tracker from '../../plugin'
|
||||
import StatusSelector from '../issues/StatusSelector.svelte'
|
||||
import ChangeIdentity from './ChangeIdentity.svelte'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
|
||||
export let project: Project | undefined = undefined
|
||||
|
||||
@@ -71,7 +72,7 @@
|
||||
|
||||
$: isNew = !project
|
||||
|
||||
async function handleSave () {
|
||||
async function handleSave (): Promise<void> {
|
||||
if (isNew) {
|
||||
await createProject()
|
||||
} else {
|
||||
@@ -81,7 +82,7 @@
|
||||
|
||||
let identifier: string = project?.identifier ?? 'TSK'
|
||||
|
||||
function getProjectData () {
|
||||
function getProjectData (): Omit<Data<Project>, 'type'> {
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
@@ -98,7 +99,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProject () {
|
||||
async function updateProject (): Promise<void> {
|
||||
if (!project) {
|
||||
return
|
||||
}
|
||||
@@ -154,10 +155,16 @@
|
||||
|
||||
let typeId: Ref<ProjectType> | undefined = project?.type
|
||||
|
||||
async function createProject () {
|
||||
$: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined
|
||||
|
||||
$: if (defaultStatus === undefined && typeType !== undefined) {
|
||||
defaultStatus = typeType.statuses[0]?._id
|
||||
}
|
||||
|
||||
async function createProject (): Promise<void> {
|
||||
const projectId = generateId<Project>()
|
||||
const projectData = getProjectData()
|
||||
if (typeId !== undefined) {
|
||||
if (typeId !== undefined && typeType !== undefined) {
|
||||
const ops = client
|
||||
.apply(projectId)
|
||||
.notMatch(tracker.class.Project, { identifier: projectData.identifier.toUpperCase() })
|
||||
@@ -168,6 +175,9 @@
|
||||
isSaving = false
|
||||
|
||||
if (succeeded) {
|
||||
// Add vacancy mixin
|
||||
await client.createMixin(projectId, tracker.class.Project, core.space.Space, typeType.targetClass, {})
|
||||
|
||||
close(projectId)
|
||||
} else {
|
||||
changeIdentity(changeIdentityRef)
|
||||
@@ -175,17 +185,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function chooseIcon (ev: MouseEvent) {
|
||||
function chooseIcon (ev: MouseEvent): void {
|
||||
const icons = [tracker.icon.Home, tracker.icon.RedCircle]
|
||||
showPopup(IconPicker, { icon, color, icons }, 'top', (result) => {
|
||||
const update = (result: any) => {
|
||||
if (result !== undefined && result !== null) {
|
||||
icon = result.icon
|
||||
color = result.color
|
||||
isColorSelected = true
|
||||
}
|
||||
})
|
||||
}
|
||||
showPopup(IconPicker, { icon, color, icons }, 'top', update, update)
|
||||
}
|
||||
function changeIdentity (element: HTMLElement) {
|
||||
function changeIdentity (element: HTMLElement): void {
|
||||
showPopup(ChangeIdentity, { identifier, projectsIdentifiers }, element, (result) => {
|
||||
if (result != null) {
|
||||
identifier = result.toLocaleUpperCase()
|
||||
@@ -193,7 +204,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
function close (id?: Ref<Project>) {
|
||||
function close (id?: Ref<Project>): void {
|
||||
dispatch('close', id)
|
||||
}
|
||||
|
||||
@@ -203,6 +214,7 @@
|
||||
|
||||
function handleTypeChange (evt: CustomEvent<Ref<ProjectType>>): void {
|
||||
typeId = evt.detail
|
||||
defaultStatus = undefined
|
||||
}
|
||||
|
||||
$: identifier = identifier.toLocaleUpperCase().replaceAll('-', '_').replaceAll(' ', '_').substring(0, 5)
|
||||
@@ -230,7 +242,7 @@
|
||||
<Component
|
||||
is={task.component.ProjectTypeSelector}
|
||||
props={{
|
||||
categories: [tracker.category.ProjectTypeCategory],
|
||||
descriptors: [tracker.descriptors.ProjectType],
|
||||
type: typeId,
|
||||
disabled: !isNew,
|
||||
focusIndex: 4,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { AccountArrayEditor } from '@hcengineering/contact-resources'
|
||||
import contact from '@hcengineering/contact-resources/src/plugin'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
export let value: Project
|
||||
</script>
|
||||
|
||||
<AccountArrayEditor
|
||||
label={contact.string.Members}
|
||||
value={value.members}
|
||||
onChange={(evt) => {
|
||||
void getClient().diffUpdate(value, { members: evt })
|
||||
}}
|
||||
/>
|
||||
@@ -26,13 +26,12 @@
|
||||
Milestone
|
||||
} from '@hcengineering/tracker'
|
||||
import { Button, Component, EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import AssigneeEditor from '../issues/AssigneeEditor.svelte'
|
||||
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
||||
import StatusEditor from '../issues/StatusEditor.svelte'
|
||||
import EstimationEditor from './EstimationEditor.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let parendIssueId: Ref<Issue>
|
||||
export let project: Project
|
||||
|
||||
Reference in New Issue
Block a user