mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
[UBER-150] Milestones update (#3212)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
This commit is contained in:
parent
88d62bcff5
commit
ffc3999bdf
@ -390,12 +390,6 @@ export class TMilestone extends TDoc implements Milestone {
|
|||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
status!: MilestoneStatus
|
status!: MilestoneStatus
|
||||||
|
|
||||||
@Prop(TypeRef(contact.class.Employee), tracker.string.ComponentLead)
|
|
||||||
lead!: Ref<Employee> | null
|
|
||||||
|
|
||||||
@Prop(ArrOf(TypeRef(contact.class.Employee)), tracker.string.Members)
|
|
||||||
members!: Ref<Employee>[]
|
|
||||||
|
|
||||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||||
comments!: number
|
comments!: number
|
||||||
|
|
||||||
@ -406,9 +400,6 @@ export class TMilestone extends TDoc implements Milestone {
|
|||||||
targetDate!: Timestamp
|
targetDate!: Timestamp
|
||||||
|
|
||||||
declare space: Ref<Project>
|
declare space: Ref<Project>
|
||||||
|
|
||||||
@Prop(TypeNumber(), tracker.string.Capacity)
|
|
||||||
capacity!: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -955,6 +946,10 @@ export function createModel (builder: Builder): void {
|
|||||||
presenter: tracker.component.MilestoneRefPresenter
|
presenter: tracker.component.MilestoneRefPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectEditor, {
|
||||||
|
editor: tracker.component.EditMilestone
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Issue, core.class.Class, setting.mixin.Editable, {
|
builder.mixin(tracker.class.Issue, core.class.Class, setting.mixin.Editable, {
|
||||||
value: true
|
value: true
|
||||||
})
|
})
|
||||||
@ -1336,9 +1331,6 @@ export function createModel (builder: Builder): void {
|
|||||||
filters: []
|
filters: []
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ClassFilters, {
|
|
||||||
filters: []
|
|
||||||
})
|
|
||||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ClassFilters, {
|
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ClassFilters, {
|
||||||
filters: ['status'],
|
filters: ['status'],
|
||||||
strict: true
|
strict: true
|
||||||
@ -1766,11 +1758,10 @@ export function createModel (builder: Builder): void {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const milestoneOptions: ViewOptionsModel = {
|
const milestoneOptions: ViewOptionsModel = {
|
||||||
groupBy: ['lead'],
|
groupBy: ['status'],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
['modifiedOn', SortingOrder.Descending],
|
['modifiedOn', SortingOrder.Descending],
|
||||||
['targetDate', SortingOrder.Descending],
|
['targetDate', SortingOrder.Descending]
|
||||||
['capacity', SortingOrder.Ascending]
|
|
||||||
],
|
],
|
||||||
other: []
|
other: []
|
||||||
}
|
}
|
||||||
@ -1789,26 +1780,7 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
{ key: '', presenter: tracker.component.MilestonePresenter, props: { shouldUseMargin: true } },
|
{ key: '', presenter: tracker.component.MilestonePresenter, props: { shouldUseMargin: true } },
|
||||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||||
{
|
{ key: '', presenter: tracker.component.MilestoneDatePresenter, props: { field: 'targetDate' } }
|
||||||
key: '',
|
|
||||||
presenter: contact.component.MembersPresenter,
|
|
||||||
props: {
|
|
||||||
kind: 'link',
|
|
||||||
intlTitle: tracker.string.MilestoneMembersTitle,
|
|
||||||
intlSearchPh: tracker.string.MilestoneMembersSearchPlaceholder
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ key: '', presenter: tracker.component.MilestoneDatePresenter, props: { field: 'targetDate' } },
|
|
||||||
{
|
|
||||||
key: 'lead',
|
|
||||||
presenter: tracker.component.MilestoneLeadPresenter,
|
|
||||||
props: {
|
|
||||||
_class: tracker.class.Milestone,
|
|
||||||
defaultClass: contact.class.Employee,
|
|
||||||
shouldShowLabel: false,
|
|
||||||
size: 'x-small'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
tracker.viewlet.MilestoneList
|
tracker.viewlet.MilestoneList
|
||||||
@ -1857,32 +1829,6 @@ export function createModel (builder: Builder): void {
|
|||||||
['comments', 'status', 'priority', 'assignee', 'subIssues', 'blockedBy', 'milestone', 'dueDate']
|
['comments', 'status', 'priority', 'assignee', 'subIssues', 'blockedBy', 'milestone', 'dueDate']
|
||||||
)
|
)
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: view.actionImpl.ValueSelector,
|
|
||||||
actionPopup: view.component.ValueSelector,
|
|
||||||
actionProps: {
|
|
||||||
attribute: 'lead',
|
|
||||||
_class: contact.class.Employee,
|
|
||||||
query: {},
|
|
||||||
placeholder: tracker.string.MilestoneLead
|
|
||||||
},
|
|
||||||
label: tracker.string.MilestoneLead,
|
|
||||||
icon: contact.icon.Person,
|
|
||||||
keyBinding: [],
|
|
||||||
input: 'none',
|
|
||||||
category: tracker.category.Tracker,
|
|
||||||
target: tracker.class.Milestone,
|
|
||||||
context: {
|
|
||||||
mode: ['context'],
|
|
||||||
application: tracker.app.Tracker,
|
|
||||||
group: 'edit'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tracker.action.SetMilestoneLead
|
|
||||||
)
|
|
||||||
|
|
||||||
const componentListViewOptions: ViewOptionsModel = {
|
const componentListViewOptions: ViewOptionsModel = {
|
||||||
groupBy: ['lead'],
|
groupBy: ['lead'],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
|
@ -86,7 +86,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
action: {
|
action: {
|
||||||
NewRelatedIssue: '' as Ref<Action<Doc, Record<string, any>>>,
|
NewRelatedIssue: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||||
DeleteMilestone: '' as Ref<Action<Doc, Record<string, any>>>,
|
DeleteMilestone: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||||
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>,
|
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>
|
||||||
SetMilestoneLead: '' as Ref<Action<Doc, Record<string, any>>>
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -207,11 +207,6 @@
|
|||||||
"ClosedMilestones": "Done",
|
"ClosedMilestones": "Done",
|
||||||
"AddToMilestone": "Add to Milestone",
|
"AddToMilestone": "Add to Milestone",
|
||||||
"MilestoneNamePlaceholder": "Milestone name",
|
"MilestoneNamePlaceholder": "Milestone name",
|
||||||
"MilestoneLead": "Lead",
|
|
||||||
"MilestoneLeadTitle": "Milestone lead",
|
|
||||||
"MilestoneLeadSearchPlaceholder": "Set milestone lead\u2026",
|
|
||||||
"MilestoneMembersTitle": "Milestone members",
|
|
||||||
"MilestoneMembersSearchPlaceholder": "Change milestone members\u2026",
|
|
||||||
|
|
||||||
"NewMilestone": "New Milestone",
|
"NewMilestone": "New Milestone",
|
||||||
"CreateMilestone": "Create",
|
"CreateMilestone": "Create",
|
||||||
@ -252,7 +247,6 @@
|
|||||||
"TimeSpendHours": "{value}h",
|
"TimeSpendHours": "{value}h",
|
||||||
"ChildEstimation": "Subissues Estimation",
|
"ChildEstimation": "Subissues Estimation",
|
||||||
"ChildReportedTime": "Subissues Time",
|
"ChildReportedTime": "Subissues Time",
|
||||||
"Capacity": "Capacity",
|
|
||||||
"CapacityValue": "of {value}d",
|
"CapacityValue": "of {value}d",
|
||||||
"NewRelatedIssue": "New related issue",
|
"NewRelatedIssue": "New related issue",
|
||||||
"RelatedIssuesNotFound": "Related issues not found",
|
"RelatedIssuesNotFound": "Related issues not found",
|
||||||
|
@ -207,11 +207,6 @@
|
|||||||
"ClosedMilestones": "Завершено",
|
"ClosedMilestones": "Завершено",
|
||||||
"AddToMilestone": "Добавить в Майлстоун",
|
"AddToMilestone": "Добавить в Майлстоун",
|
||||||
"MilestoneNamePlaceholder": "Название майлстоуна",
|
"MilestoneNamePlaceholder": "Название майлстоуна",
|
||||||
"MilestoneLead": "Руководитель",
|
|
||||||
"MilestoneLeadTitle": "Руководитель майлстоуна",
|
|
||||||
"MilestoneLeadSearchPlaceholder": "Назначьте руководителя майлстоуна\u2026",
|
|
||||||
"MilestoneMembersTitle": "Участники майлстоуна",
|
|
||||||
"MilestoneMembersSearchPlaceholder": "Измененить участников майлстоуна\u2026",
|
|
||||||
|
|
||||||
"NewMilestone": "Новый Майлстоун",
|
"NewMilestone": "Новый Майлстоун",
|
||||||
"CreateMilestone": "Создать",
|
"CreateMilestone": "Создать",
|
||||||
@ -252,7 +247,6 @@
|
|||||||
"TimeSpendHours": "{value}h",
|
"TimeSpendHours": "{value}h",
|
||||||
"ChildEstimation": "Оценка подзадач",
|
"ChildEstimation": "Оценка подзадач",
|
||||||
"ChildReportedTime": "Время водзадач",
|
"ChildReportedTime": "Время водзадач",
|
||||||
"Capacity": "Вместимость",
|
|
||||||
"CapacityValue": "из {value}d",
|
"CapacityValue": "из {value}d",
|
||||||
"NewRelatedIssue": "Завести связанную задачу",
|
"NewRelatedIssue": "Завести связанную задачу",
|
||||||
"RelatedIssuesNotFound": "Связанные задачи не найдены",
|
"RelatedIssuesNotFound": "Связанные задачи не найдены",
|
||||||
|
@ -1,89 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
import { Milestone } from '@hcengineering/tracker'
|
||||||
import { Button, DatePresenter, EditBox, Icon, IconMoreH, Label, showPopup } from '@hcengineering/ui'
|
import { EditBox } from '@hcengineering/ui'
|
||||||
import { ContextMenu, DocAttributeBar } from '@hcengineering/view-resources'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { onDestroy } from 'svelte'
|
|
||||||
import { activeMilestone } from '../../issues'
|
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import Expanded from '../icons/Expanded.svelte'
|
|
||||||
import IssuesView from '../issues/IssuesView.svelte'
|
|
||||||
import MilestonePopup from './MilestonePopup.svelte'
|
|
||||||
|
|
||||||
export let milestone: Milestone
|
export let object: Milestone
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
async function change (field: string, value: any) {
|
let oldLabel = ''
|
||||||
await client.update(milestone, { [field]: value })
|
let rawLabel = ''
|
||||||
}
|
|
||||||
let container: HTMLElement
|
|
||||||
|
|
||||||
function selectMilestone (): void {
|
async function change<K extends keyof Milestone> (field: K, value: Milestone[K]) {
|
||||||
showPopup(MilestonePopup, { _class: tracker.class.Milestone }, container, (value) => {
|
await client.update(object, { [field]: value })
|
||||||
if (value != null) {
|
|
||||||
milestone = value
|
|
||||||
dispatch('milestone', milestone._id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu (ev?: Event): void {
|
$: if (oldLabel !== object.label) {
|
||||||
if (milestone) {
|
oldLabel = object.label
|
||||||
showPopup(ContextMenu, { object: milestone }, (ev as MouseEvent).target as HTMLElement)
|
rawLabel = object.label
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: $activeMilestone = milestone?._id
|
onMount(() => dispatch('open', { ignoreKeys: ['label'] }))
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
$activeMilestone = undefined
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IssuesView
|
<EditBox
|
||||||
query={{ milestone: milestone._id, space: milestone.space }}
|
bind:value={rawLabel}
|
||||||
space={milestone.space}
|
placeholder={tracker.string.MilestoneNamePlaceholder}
|
||||||
label={milestone.label}
|
kind="large-style"
|
||||||
>
|
focusable
|
||||||
<svelte:fragment slot="label_selector">
|
on:blur={async () => {
|
||||||
<div bind:this={container}>
|
const trimmedLabel = rawLabel.trim()
|
||||||
<Button size={'small'} kind={'link'} on:click={selectMilestone}>
|
|
||||||
<svelte:fragment slot="content">
|
if (trimmedLabel.length === 0) {
|
||||||
<div class="ac-header__icon"><Icon icon={tracker.icon.Milestone} size={'small'} /></div>
|
rawLabel = oldLabel
|
||||||
<span class="ac-header__title mr-1">{milestone.label}</span>
|
} else if (trimmedLabel !== object.label) {
|
||||||
<Icon icon={Expanded} size={'small'} />
|
await change('label', trimmedLabel)
|
||||||
</svelte:fragment>
|
}
|
||||||
</Button>
|
}}
|
||||||
</div>
|
/>
|
||||||
</svelte:fragment>
|
|
||||||
<svelte:fragment slot="afterHeader">
|
|
||||||
<div class="ac-header search-start full divide">
|
|
||||||
<DatePresenter value={milestone.targetDate} kind={'transparent'} size={'medium'} />
|
|
||||||
<div class="flex-row-center ml-2">
|
|
||||||
{#if milestone?.capacity}
|
|
||||||
<Label label={tracker.string.CapacityValue} params={{ value: milestone?.capacity }} />
|
|
||||||
{/if}
|
|
||||||
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} on:click={showMenu} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</svelte:fragment>
|
|
||||||
<svelte:fragment slot="aside">
|
|
||||||
<div class="popupPanel-body__aside-content">
|
|
||||||
<EditBox kind={'large-style'} bind:value={milestone.label} on:change={() => change('label', milestone.label)} />
|
|
||||||
<div class="mt-2">
|
|
||||||
<StyledTextBox
|
|
||||||
alwaysEdit={true}
|
|
||||||
showButtons={false}
|
|
||||||
placeholder={tracker.string.Description}
|
|
||||||
content={milestone.description ?? ''}
|
|
||||||
on:value={(evt) => change('description', evt.detail)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DocAttributeBar object={milestone} mixins={[]} ignoreKeys={['icon', 'label', 'description']} />
|
|
||||||
</svelte:fragment>
|
|
||||||
</IssuesView>
|
|
||||||
|
@ -138,7 +138,7 @@
|
|||||||
<SearchEdit bind:value={search} on:change={() => {}} />
|
<SearchEdit bind:value={search} on:change={() => {}} />
|
||||||
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
<FilterButton _class={tracker.class.Issue} {space} />
|
<FilterButton _class={tracker.class.Milestone} {space} />
|
||||||
</div>
|
</div>
|
||||||
<div class="ac-header-full medium-gap">
|
<div class="ac-header-full medium-gap">
|
||||||
{#if viewlet}
|
{#if viewlet}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@hcengineering/contact'
|
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
import { Milestone } from '@hcengineering/tracker'
|
||||||
import { Component } from '@hcengineering/ui'
|
import { Component } from '@hcengineering/ui'
|
||||||
import { BuildModelKey, Viewlet, ViewOptions } from '@hcengineering/view'
|
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import NewMilestone from './NewMilestone.svelte'
|
import NewMilestone from './NewMilestone.svelte'
|
||||||
|
|
||||||
@ -16,19 +15,6 @@
|
|||||||
|
|
||||||
const createItemDialog = NewMilestone
|
const createItemDialog = NewMilestone
|
||||||
const createItemLabel = tracker.string.CreateMilestone
|
const createItemLabel = tracker.string.CreateMilestone
|
||||||
|
|
||||||
const retrieveMembers = (s: Milestone) => s.members
|
|
||||||
|
|
||||||
function updateConfig (config: (string | BuildModelKey)[]): (string | BuildModelKey)[] {
|
|
||||||
return config.map((it) => {
|
|
||||||
if (typeof it === 'string') {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
return it.presenter === contact.component.MembersPresenter
|
|
||||||
? { ...it, props: { ...it.props, retrieveMembers } }
|
|
||||||
: it
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if viewlet?.$lookup?.descriptor?.component}
|
{#if viewlet?.$lookup?.descriptor?.component}
|
||||||
@ -36,7 +22,7 @@
|
|||||||
is={viewlet.$lookup.descriptor.component}
|
is={viewlet.$lookup.descriptor.component}
|
||||||
props={{
|
props={{
|
||||||
_class: tracker.class.Milestone,
|
_class: tracker.class.Milestone,
|
||||||
config: updateConfig(viewlet.config),
|
config: viewlet.config,
|
||||||
options: viewlet.options,
|
options: viewlet.options,
|
||||||
createItemDialog,
|
createItemDialog,
|
||||||
createItemLabel,
|
createItemLabel,
|
||||||
|
@ -1,106 +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 contact, { Employee } from '@hcengineering/contact'
|
|
||||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
|
||||||
import { IntlString } from '@hcengineering/platform'
|
|
||||||
import { getClient } from '@hcengineering/presentation'
|
|
||||||
import { UsersPopup } from '@hcengineering/contact-resources'
|
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
|
||||||
import { eventToHTMLElement, IconSize, showPopup } from '@hcengineering/ui'
|
|
||||||
import { AttributeModel } from '@hcengineering/view'
|
|
||||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
|
||||||
import tracker from '../../plugin'
|
|
||||||
import LeadPopup from '../components/LeadPopup.svelte'
|
|
||||||
|
|
||||||
export let value: Employee | null
|
|
||||||
export let size: IconSize = 'x-small'
|
|
||||||
export let object: Milestone
|
|
||||||
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
|
|
||||||
export let isEditable: boolean = true
|
|
||||||
export let shouldShowLabel: boolean = false
|
|
||||||
export let defaultName: IntlString | undefined = undefined
|
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
let presenter: AttributeModel | undefined
|
|
||||||
|
|
||||||
$: if (value || defaultClass) {
|
|
||||||
if (value) {
|
|
||||||
getObjectPresenter(client, value._class, { key: '' }).then((p) => {
|
|
||||||
presenter = p
|
|
||||||
})
|
|
||||||
} else if (defaultClass) {
|
|
||||||
getObjectPresenter(client, defaultClass, { key: '' }).then((p) => {
|
|
||||||
presenter = p
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLeadChanged = async (result: Employee | null | undefined) => {
|
|
||||||
if (!isEditable || result === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const newLead = result === null ? null : result._id
|
|
||||||
|
|
||||||
await client.update(object, { lead: newLead })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLeadEditorOpened = async (event: MouseEvent) => {
|
|
||||||
if (!isEditable) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
showPopup(
|
|
||||||
UsersPopup,
|
|
||||||
{
|
|
||||||
_class: contact.class.Employee,
|
|
||||||
selected: value?._id,
|
|
||||||
docQuery: {
|
|
||||||
active: true
|
|
||||||
},
|
|
||||||
allowDeselect: true,
|
|
||||||
placeholder: tracker.string.ComponentLeadSearchPlaceholder
|
|
||||||
},
|
|
||||||
eventToHTMLElement(event),
|
|
||||||
handleLeadChanged
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if value && presenter}
|
|
||||||
<svelte:component
|
|
||||||
this={presenter.presenter}
|
|
||||||
{value}
|
|
||||||
{defaultName}
|
|
||||||
avatarSize={size}
|
|
||||||
disabled={false}
|
|
||||||
shouldShowPlaceholder={true}
|
|
||||||
shouldShowName={shouldShowLabel}
|
|
||||||
onEmployeeEdit={handleLeadEditorOpened}
|
|
||||||
tooltipLabels={{ component: LeadPopup, props: { lead: value } }}
|
|
||||||
/>
|
|
||||||
{:else if presenter}
|
|
||||||
<svelte:component
|
|
||||||
this={presenter.presenter}
|
|
||||||
{value}
|
|
||||||
{defaultName}
|
|
||||||
avatarSize={size}
|
|
||||||
disabled={false}
|
|
||||||
shouldShowPlaceholder={true}
|
|
||||||
shouldShowName={shouldShowLabel}
|
|
||||||
onEmployeeEdit={handleLeadEditorOpened}
|
|
||||||
tooltipLabels={{ personLabel: tracker.string.AssignedTo, placeholderLabel: tracker.string.AssignTo }}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
@ -15,42 +15,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { WithLookup } from '@hcengineering/core'
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
import { Milestone } from '@hcengineering/tracker'
|
||||||
import { Icon, getCurrentResolvedLocation, navigate, tooltip } from '@hcengineering/ui'
|
import { Icon } from '@hcengineering/ui'
|
||||||
|
import { DocNavLink } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Milestone>
|
export let value: WithLookup<Milestone>
|
||||||
export let shouldShowAvatar: boolean = true
|
export let shouldShowAvatar = true
|
||||||
export let onClick: (() => void) | undefined = undefined
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let inline: boolean = false
|
export let inline = false
|
||||||
|
export let onClick: (() => void) | undefined = undefined
|
||||||
function navigateToMilestone () {
|
|
||||||
if (disabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (onClick) {
|
|
||||||
onClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
const loc = getCurrentResolvedLocation()
|
|
||||||
loc.path[4] = 'milestones'
|
|
||||||
loc.path[5] = value._id
|
|
||||||
loc.path.length = 6
|
|
||||||
loc.fragment = undefined
|
|
||||||
navigate(loc)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
<DocNavLink object={value} {disabled} {inline} {onClick}>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<div class="flex-presenter" class:inline-presenter={inline}>
|
||||||
<div class="flex-presenter" class:inline-presenter={inline} on:click={navigateToMilestone}>
|
|
||||||
{#if !inline && shouldShowAvatar}
|
{#if !inline && shouldShowAvatar}
|
||||||
<div class="icon" use:tooltip={{ label: tracker.string.Milestone }}>
|
<div class="icon">
|
||||||
<Icon icon={tracker.icon.Milestone} size={'small'} />
|
<Icon icon={tracker.icon.Milestone} size="small" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span title={value.label} class="overflow-label label">
|
<span title={value.label} class="overflow-label label">
|
||||||
{value.label}
|
{value.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</DocNavLink>
|
||||||
|
@ -15,58 +15,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { Project } from '@hcengineering/tracker'
|
||||||
import { Milestone, Project } from '@hcengineering/tracker'
|
|
||||||
import {
|
|
||||||
closePopup,
|
|
||||||
closeTooltip,
|
|
||||||
getCurrentResolvedLocation,
|
|
||||||
navigate,
|
|
||||||
resolvedLocationStore
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import { onDestroy } from 'svelte'
|
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { MilestoneViewMode } from '../../utils'
|
import { MilestoneViewMode } from '../../utils'
|
||||||
import EditMilestone from './EditMilestone.svelte'
|
|
||||||
import MilestoneBrowser from './MilestoneBrowser.svelte'
|
import MilestoneBrowser from './MilestoneBrowser.svelte'
|
||||||
|
|
||||||
export let currentSpace: Ref<Project>
|
export let currentSpace: Ref<Project>
|
||||||
export let label: IntlString = tracker.string.Milestones
|
export let label: IntlString = tracker.string.Milestones
|
||||||
export let search: string = ''
|
export let search: string = ''
|
||||||
export let mode: MilestoneViewMode = 'all'
|
export let mode: MilestoneViewMode = 'all'
|
||||||
|
|
||||||
let milestoneId: Ref<Milestone> | undefined
|
|
||||||
let milestone: Milestone | undefined
|
|
||||||
|
|
||||||
onDestroy(
|
|
||||||
resolvedLocationStore.subscribe(async (loc) => {
|
|
||||||
closeTooltip()
|
|
||||||
closePopup()
|
|
||||||
|
|
||||||
milestoneId = loc.path[5] as Ref<Milestone>
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const milestoneQuery = createQuery()
|
|
||||||
$: if (milestoneId !== undefined) {
|
|
||||||
milestoneQuery.query(tracker.class.Milestone, { _id: milestoneId }, (result) => {
|
|
||||||
milestone = result.shift()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
milestoneQuery.unsubscribe()
|
|
||||||
milestone = undefined
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if milestone}
|
<MilestoneBrowser {label} query={{ space: currentSpace }} {search} {mode} />
|
||||||
<EditMilestone
|
|
||||||
{milestone}
|
|
||||||
on:milestone={(evt) => {
|
|
||||||
const loc = getCurrentResolvedLocation()
|
|
||||||
loc.path[5] = evt.detail
|
|
||||||
navigate(loc)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<MilestoneBrowser {label} query={{ space: currentSpace }} {search} {mode} />
|
|
||||||
{/if}
|
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
import { Data, Ref } from '@hcengineering/core'
|
import { Data, Ref } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
|
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||||
import { EmployeeBox, UserBoxList } from '@hcengineering/contact-resources'
|
|
||||||
import { Milestone, MilestoneStatus, Project } from '@hcengineering/tracker'
|
import { Milestone, MilestoneStatus, Project } from '@hcengineering/tracker'
|
||||||
import ui, { DatePresenter, EditBox } from '@hcengineering/ui'
|
import ui, { DatePresenter, EditBox } from '@hcengineering/ui'
|
||||||
|
import { StyledTextArea } from '@hcengineering/text-editor'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import MilestoneStatusSelector from './MilestoneStatusSelector.svelte'
|
import MilestoneStatusSelector from './MilestoneStatusSelector.svelte'
|
||||||
import { StyledTextArea } from '@hcengineering/text-editor'
|
|
||||||
|
|
||||||
export let space: Ref<Project>
|
export let space: Ref<Project>
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -32,11 +31,8 @@
|
|||||||
label: '' as IntlString,
|
label: '' as IntlString,
|
||||||
description: '',
|
description: '',
|
||||||
status: MilestoneStatus.Planned,
|
status: MilestoneStatus.Planned,
|
||||||
lead: null,
|
|
||||||
members: [],
|
|
||||||
comments: 0,
|
comments: 0,
|
||||||
attachments: 0,
|
attachments: 0,
|
||||||
capacity: 0,
|
|
||||||
targetDate: Date.now() + 14 * 24 * 60 * 60 * 1000
|
targetDate: Date.now() + 14 * 24 * 60 * 60 * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,22 +79,6 @@
|
|||||||
kind={'secondary'}
|
kind={'secondary'}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
/>
|
/>
|
||||||
<EmployeeBox
|
|
||||||
label={tracker.string.MilestoneLead}
|
|
||||||
placeholder={tracker.string.AssignTo}
|
|
||||||
kind={'secondary'}
|
|
||||||
size={'large'}
|
|
||||||
bind:value={object.lead}
|
|
||||||
allowDeselect
|
|
||||||
titleDeselect={tracker.string.Unassigned}
|
|
||||||
showNavigate={false}
|
|
||||||
/>
|
|
||||||
<UserBoxList
|
|
||||||
bind:items={object.members}
|
|
||||||
label={tracker.string.MilestoneMembersSearchPlaceholder}
|
|
||||||
kind={'secondary'}
|
|
||||||
size={'large'}
|
|
||||||
/>
|
|
||||||
<DatePresenter
|
<DatePresenter
|
||||||
bind:value={object.targetDate}
|
bind:value={object.targetDate}
|
||||||
editable
|
editable
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { ScrumRecord, Milestone, Project } from '@hcengineering/tracker'
|
import { ScrumRecord, Project, Scrum } from '@hcengineering/tracker'
|
||||||
import { Button, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
import { Button, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||||
import { ActionContext, List } from '@hcengineering/view-resources'
|
import { ActionContext, List } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
@ -31,7 +31,7 @@
|
|||||||
showPopup(NewScrum, { space: currentSpace, targetElement: null }, null)
|
showPopup(NewScrum, { space: currentSpace, targetElement: null }, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const retrieveMembers = (s: Milestone) => s.members
|
const retrieveMembers = (s: Scrum) => s.members
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionContext
|
<ActionContext
|
||||||
|
@ -69,7 +69,7 @@ import RelationsPopup from './components/RelationsPopup.svelte'
|
|||||||
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
||||||
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||||
import MilestoneDatePresenter from './components/milestones/MilestoneDatePresenter.svelte'
|
import MilestoneDatePresenter from './components/milestones/MilestoneDatePresenter.svelte'
|
||||||
import MilestoneLeadPresenter from './components/milestones/MilestoneLeadPresenter.svelte'
|
import EditMilestone from './components/milestones/EditMilestone.svelte'
|
||||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
import Views from './components/views/Views.svelte'
|
||||||
import Statuses from './components/workflow/Statuses.svelte'
|
import Statuses from './components/workflow/Statuses.svelte'
|
||||||
@ -423,6 +423,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
CreateIssueTemplate,
|
CreateIssueTemplate,
|
||||||
Milestones,
|
Milestones,
|
||||||
MilestonePresenter,
|
MilestonePresenter,
|
||||||
|
EditMilestone,
|
||||||
Scrums,
|
Scrums,
|
||||||
ScrumRecordPanel,
|
ScrumRecordPanel,
|
||||||
MilestoneStatusPresenter,
|
MilestoneStatusPresenter,
|
||||||
@ -450,7 +451,6 @@ export default async (): Promise<Resources> => ({
|
|||||||
DeleteComponentPresenter,
|
DeleteComponentPresenter,
|
||||||
TimeSpendReportPopup,
|
TimeSpendReportPopup,
|
||||||
MilestoneDatePresenter,
|
MilestoneDatePresenter,
|
||||||
MilestoneLeadPresenter,
|
|
||||||
NotificationIssuePresenter,
|
NotificationIssuePresenter,
|
||||||
MilestoneFilter,
|
MilestoneFilter,
|
||||||
PriorityFilterValuePresenter,
|
PriorityFilterValuePresenter,
|
||||||
|
@ -229,11 +229,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
ActiveMilestones: '' as IntlString,
|
ActiveMilestones: '' as IntlString,
|
||||||
ClosedMilestones: '' as IntlString,
|
ClosedMilestones: '' as IntlString,
|
||||||
MilestoneNamePlaceholder: '' as IntlString,
|
MilestoneNamePlaceholder: '' as IntlString,
|
||||||
MilestoneLead: '' as IntlString,
|
|
||||||
MilestoneLeadTitle: '' as IntlString,
|
|
||||||
MilestoneLeadSearchPlaceholder: '' as IntlString,
|
|
||||||
MilestoneMembersTitle: '' as IntlString,
|
|
||||||
MilestoneMembersSearchPlaceholder: '' as IntlString,
|
|
||||||
|
|
||||||
NewMilestone: '' as IntlString,
|
NewMilestone: '' as IntlString,
|
||||||
CreateMilestone: '' as IntlString,
|
CreateMilestone: '' as IntlString,
|
||||||
@ -277,7 +272,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
|
|
||||||
ChildEstimation: '' as IntlString,
|
ChildEstimation: '' as IntlString,
|
||||||
ChildReportedTime: '' as IntlString,
|
ChildReportedTime: '' as IntlString,
|
||||||
Capacity: '' as IntlString,
|
|
||||||
CapacityValue: '' as IntlString,
|
CapacityValue: '' as IntlString,
|
||||||
AddedReference: '' as IntlString,
|
AddedReference: '' as IntlString,
|
||||||
AddedAsBlocked: '' as IntlString,
|
AddedAsBlocked: '' as IntlString,
|
||||||
@ -360,7 +354,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
MilestoneStatusPresenter: '' as AnyComponent,
|
MilestoneStatusPresenter: '' as AnyComponent,
|
||||||
MilestoneTitlePresenter: '' as AnyComponent,
|
MilestoneTitlePresenter: '' as AnyComponent,
|
||||||
MilestoneDatePresenter: '' as AnyComponent,
|
MilestoneDatePresenter: '' as AnyComponent,
|
||||||
MilestoneLeadPresenter: '' as AnyComponent,
|
EditMilestone: '' as AnyComponent,
|
||||||
ReportedTimeEditor: '' as AnyComponent,
|
ReportedTimeEditor: '' as AnyComponent,
|
||||||
TimeSpendReport: '' as AnyComponent,
|
TimeSpendReport: '' as AnyComponent,
|
||||||
EstimationEditor: '' as AnyComponent,
|
EstimationEditor: '' as AnyComponent,
|
||||||
|
@ -124,18 +124,12 @@ export interface Milestone extends Doc {
|
|||||||
|
|
||||||
status: MilestoneStatus
|
status: MilestoneStatus
|
||||||
|
|
||||||
lead: Ref<Employee> | null
|
|
||||||
members: Ref<Employee>[]
|
|
||||||
|
|
||||||
space: Ref<Project>
|
space: Ref<Project>
|
||||||
|
|
||||||
comments: number
|
comments: number
|
||||||
attachments?: number
|
attachments?: number
|
||||||
|
|
||||||
targetDate: Timestamp
|
targetDate: Timestamp
|
||||||
|
|
||||||
// Capacity in man days.
|
|
||||||
capacity: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user