Add sprint members (#2392)

Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2022-11-22 18:39:37 +07:00 committed by GitHub
parent a2af1802a2
commit dcaa0ea9b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 133 additions and 7 deletions

View File

@ -412,6 +412,9 @@ export class TSprint extends TDoc implements Sprint {
@Prop(TypeRef(contact.class.Employee), tracker.string.ProjectLead) @Prop(TypeRef(contact.class.Employee), tracker.string.ProjectLead)
lead!: Ref<Employee> | null 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

View File

@ -205,6 +205,12 @@
"ActiveSprints": "Active", "ActiveSprints": "Active",
"ClosedSprints": "Done", "ClosedSprints": "Done",
"AddToSprint": "Add to Sprint", "AddToSprint": "Add to Sprint",
"SprintNamePlaceholder": "Sprint name",
"SprintLead": "Lead",
"SprintLeadTitle": "Sprint lead",
"SprintLeadSearchPlaceholder": "Set sprint lead\u2026",
"SprintMembersTitle": "Sprint members",
"SprintMembersSearchPlaceholder": "Change sprint members\u2026",
"NewSprint": "New Sprint", "NewSprint": "New Sprint",
"CreateSprint": "Create", "CreateSprint": "Create",

View File

@ -205,6 +205,12 @@
"ActiveSprints": "Активно", "ActiveSprints": "Активно",
"ClosedSprints": "Завершено", "ClosedSprints": "Завершено",
"AddToSprint": "Добавить в Спринт", "AddToSprint": "Добавить в Спринт",
"SprintNamePlaceholder": "Название спринта",
"SprintLead": "Руководитель",
"SprintLeadTitle": "Руководитель спринта",
"SprintLeadSearchPlaceholder": "Назначьте руководителя спринта\u2026",
"SprintMembersTitle": "Участники спринта",
"SprintMembersSearchPlaceholder": "Измененить участников спринта\u2026",
"NewSprint": "Новый Спринт", "NewSprint": "Новый Спринт",
"CreateSprint": "Создать", "CreateSprint": "Создать",

View File

@ -81,8 +81,9 @@
bind:value={object.lead} bind:value={object.lead}
allowDeselect allowDeselect
titleDeselect={tracker.string.Unassigned} titleDeselect={tracker.string.Unassigned}
showNavigate={false}
/> />
<UserBoxList bind:items={object.members} label={tracker.string.ProjectStatusPlaceholder} /> <UserBoxList bind:items={object.members} label={tracker.string.ProjectMembersSearchPlaceholder} />
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate --> <!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
<DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable /> <DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable />
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable /> <DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />

View File

@ -148,7 +148,7 @@
<svelte:component <svelte:component
this={attributeModel.presenter} this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''} value={getObjectValue(attributeModel.key, docObject) ?? ''}
projectId={docObject._id} parentId={docObject._id}
{...attributeModel.props} {...attributeModel.props}
/> />
</div> </div>

View File

@ -15,11 +15,12 @@
<script lang="ts"> <script lang="ts">
import { Data, Ref } from '@hcengineering/core' import { Data, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import { Card, EmployeeBox, getClient, SpaceSelector } from '@hcengineering/presentation' import { Card, EmployeeBox, getClient, SpaceSelector, UserBoxList } from '@hcengineering/presentation'
import { Sprint, SprintStatus, Team } from '@hcengineering/tracker' import { Project, Sprint, SprintStatus, Team } from '@hcengineering/tracker'
import ui, { DatePresenter, EditBox } from '@hcengineering/ui' import ui, { DatePresenter, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin' import tracker from '../../plugin'
import ProjectSelector from '../ProjectSelector.svelte'
import SprintStatusSelector from './SprintStatusSelector.svelte' import SprintStatusSelector from './SprintStatusSelector.svelte'
export let space: Ref<Team> export let space: Ref<Team>
@ -31,8 +32,10 @@
description: '', description: '',
status: SprintStatus.Planned, status: SprintStatus.Planned,
lead: null, lead: null,
members: [],
comments: 0, comments: 0,
attachments: 0, attachments: 0,
capacity: 0,
startDate: Date.now(), startDate: Date.now(),
targetDate: Date.now() + 14 * 24 * 60 * 60 * 1000 targetDate: Date.now() + 14 * 24 * 60 * 60 * 1000
} }
@ -48,6 +51,14 @@
object.status = newSprintStatus object.status = newSprintStatus
} }
const handleProjectIdChanged = async (projectId: Ref<Project> | null | undefined) => {
if (projectId === undefined) {
return
}
object.project = projectId ?? undefined
}
</script> </script>
<Card <Card
@ -61,7 +72,7 @@
<SpaceSelector _class={tracker.class.Team} label={tracker.string.Team} bind:space /> <SpaceSelector _class={tracker.class.Team} label={tracker.string.Team} bind:space />
</svelte:fragment> </svelte:fragment>
<div class="label"> <div class="label">
<EditBox bind:value={object.label} placeholder={tracker.string.ProjectNamePlaceholder} kind="large-style" focus /> <EditBox bind:value={object.label} placeholder={tracker.string.SprintNamePlaceholder} kind="large-style" focus />
</div> </div>
<div class="description"> <div class="description">
<EditBox <EditBox
@ -72,13 +83,16 @@
</div> </div>
<div slot="pool" class="flex-row-center text-sm gap-1-5"> <div slot="pool" class="flex-row-center text-sm gap-1-5">
<SprintStatusSelector selectedSprintStatus={object.status} onSprintStatusChange={handleProjectStatusChanged} /> <SprintStatusSelector selectedSprintStatus={object.status} onSprintStatusChange={handleProjectStatusChanged} />
<ProjectSelector value={object.project} onChange={handleProjectIdChanged} />
<EmployeeBox <EmployeeBox
label={tracker.string.ProjectLead} label={tracker.string.SprintLead}
placeholder={tracker.string.AssignTo} placeholder={tracker.string.AssignTo}
bind:value={object.lead} bind:value={object.lead}
allowDeselect allowDeselect
titleDeselect={tracker.string.Unassigned} titleDeselect={tracker.string.Unassigned}
showNavigate={false}
/> />
<UserBoxList bind:items={object.members} label={tracker.string.SprintMembersSearchPlaceholder} />
<DatePresenter <DatePresenter
bind:value={object.startDate} bind:value={object.startDate}
editable editable

View File

@ -171,6 +171,7 @@
size: 'x-small' size: 'x-small'
} }
}, },
{ key: '', presenter: tracker.component.SprintMembersPresenter, props: { kind: 'link' } },
{ key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } }, { key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } },
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } }, { key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
{ key: '', presenter: tracker.component.SprintStatusPresenter } { key: '', presenter: tracker.component.SprintStatusPresenter }

View File

@ -178,7 +178,8 @@
<svelte:component <svelte:component
this={attributeModel.presenter} this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''} value={getObjectValue(attributeModel.key, docObject) ?? ''}
projectId={docObject._id} parentId={docObject._id}
sprintId={docObject._id}
{...attributeModel.props} {...attributeModel.props}
/> />
</div> </div>

View File

@ -0,0 +1,84 @@
<!--
// 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 { Ref } from '@hcengineering/core'
import { Sprint } from '@hcengineering/tracker'
import { Button, showPopup, eventToHTMLElement } from '@hcengineering/ui'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import contact, { Employee } from '@hcengineering/contact'
import { getClient, UsersPopup } from '@hcengineering/presentation'
import { translate } from '@hcengineering/platform'
import tracker from '../../plugin'
export let value: Sprint
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = 'min-content'
const client = getClient()
let buttonTitle = ''
$: translate(tracker.string.SprintMembersTitle, {}).then((res) => {
buttonTitle = res
})
const handleSprinttMembersChanged = async (result: Ref<Employee>[] | undefined) => {
if (result === undefined) {
return
}
const memberToPull = value.members.filter((x) => !result.includes(x))[0]
const memberToPush = result.filter((x) => !value.members.includes(x))[0]
if (memberToPull) {
await client.update(value, { $pull: { members: memberToPull } })
}
if (memberToPush) {
await client.update(value, { $push: { members: memberToPush } })
}
}
const handleSprintMembersEditorOpened = async (event: MouseEvent) => {
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
selectedUsers: value.members,
allowDeselect: true,
multiSelect: true,
docQuery: {
active: true
},
placeholder: tracker.string.SprintMembersSearchPlaceholder
},
eventToHTMLElement(event),
undefined,
handleSprinttMembersChanged
)
}
</script>
<Button
{kind}
{size}
{width}
{justify}
title={buttonTitle}
icon={tracker.icon.ProjectMembers}
on:click={handleSprintMembersEditorOpened}
/>

View File

@ -74,6 +74,7 @@ import SprintEditor from './components/sprints/SprintEditor.svelte'
import SprintPresenter from './components/sprints/SprintPresenter.svelte' import SprintPresenter from './components/sprints/SprintPresenter.svelte'
import Sprints from './components/sprints/Sprints.svelte' import Sprints from './components/sprints/Sprints.svelte'
import SprintSelector from './components/sprints/SprintSelector.svelte' import SprintSelector from './components/sprints/SprintSelector.svelte'
import SprintMembersPresenter from './components/sprints/SprintMembersPresenter.svelte'
import SprintStatusPresenter from './components/sprints/SprintStatusPresenter.svelte' import SprintStatusPresenter from './components/sprints/SprintStatusPresenter.svelte'
import SprintTitlePresenter from './components/sprints/SprintTitlePresenter.svelte' import SprintTitlePresenter from './components/sprints/SprintTitlePresenter.svelte'
@ -249,6 +250,7 @@ export default async (): Promise<Resources> => ({
CreateIssueTemplate, CreateIssueTemplate,
Sprints, Sprints,
SprintPresenter, SprintPresenter,
SprintMembersPresenter,
SprintStatusPresenter, SprintStatusPresenter,
SprintTitlePresenter, SprintTitlePresenter,
SprintSelector, SprintSelector,

View File

@ -221,6 +221,12 @@ export default mergeIds(trackerId, tracker, {
PlannedSprints: '' as IntlString, PlannedSprints: '' as IntlString,
ActiveSprints: '' as IntlString, ActiveSprints: '' as IntlString,
ClosedSprints: '' as IntlString, ClosedSprints: '' as IntlString,
SprintNamePlaceholder: '' as IntlString,
SprintLead: '' as IntlString,
SprintLeadTitle: '' as IntlString,
SprintLeadSearchPlaceholder: '' as IntlString,
SprintMembersTitle: '' as IntlString,
SprintMembersSearchPlaceholder: '' as IntlString,
NewSprint: '' as IntlString, NewSprint: '' as IntlString,
CreateSprint: '' as IntlString, CreateSprint: '' as IntlString,
@ -304,6 +310,7 @@ export default mergeIds(trackerId, tracker, {
SprintPresenter: '' as AnyComponent, SprintPresenter: '' as AnyComponent,
SprintStatusPresenter: '' as AnyComponent, SprintStatusPresenter: '' as AnyComponent,
SprintTitlePresenter: '' as AnyComponent, SprintTitlePresenter: '' as AnyComponent,
SprintMembersPresenter: '' as AnyComponent,
ReportedTimeEditor: '' as AnyComponent, ReportedTimeEditor: '' as AnyComponent,
TimeSpendReport: '' as AnyComponent, TimeSpendReport: '' as AnyComponent,
EstimationEditor: '' as AnyComponent, EstimationEditor: '' as AnyComponent,

View File

@ -129,6 +129,7 @@ export interface Sprint extends Doc {
status: SprintStatus status: SprintStatus
lead: Ref<Employee> | null lead: Ref<Employee> | null
members: Ref<Employee>[]
space: Ref<Team> space: Ref<Team>