[UBER-334] Add categories to the assignee popup (#3324)

This commit is contained in:
Sergei Ogorelkov
2023-06-01 23:38:53 +07:00
committed by GitHub
parent eee6e69114
commit 8ce6d005df
25 changed files with 112 additions and 210 deletions
@@ -662,10 +662,9 @@
<div id="assignee-editor">
<AssigneeEditor
focusIndex={5}
value={object}
{object}
kind={'secondary'}
size={'large'}
width={'min-content'}
short
on:change={({ detail }) => {
isAssigneeTouched = true
@@ -15,85 +15,86 @@
<script lang="ts">
import { Employee, EmployeeAccount } from '@hcengineering/contact'
import { AssigneeBox, employeeAccountByIdStore } from '@hcengineering/contact-resources'
import { AttachedData, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Issue, IssueDraft, IssueTemplateData } from '@hcengineering/tracker'
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Issue, Project } from '@hcengineering/tracker'
import { ButtonKind, ButtonSize, IconSize, TooltipAlignment } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { getPreviousAssignees } from '../../utils'
import tracker from '../../plugin'
export let value: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft
type Object = (Doc | {}) & Pick<Issue, 'space' | 'component' | 'assignee'>
export let object: Object
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'large'
export let avatarSize: IconSize = 'card'
export let tooltipAlignment: TooltipAlignment | undefined = undefined
export let width: string = '100%'
export let width: string = 'min-content'
export let focusIndex: number | undefined = undefined
export let short: boolean = false
export let shouldShowName = true
const client = getClient()
const dispatch = createEventDispatcher()
const projectQuery = createQuery()
let project: Project | undefined
let prevAssigned: Ref<Employee>[] = []
let projectLead: Ref<Employee> | undefined = undefined
let projectMembers: Ref<Employee>[] = []
let componentLead: Ref<Employee> | undefined = undefined
let members: Ref<Employee>[] = []
let docQuery: DocumentQuery<Employee> = { active: true }
$: '_class' in value &&
getPreviousAssignees(value).then((res) => {
$: '_class' in object &&
getPreviousAssignees(object._id).then((res) => {
prevAssigned = res
})
function hasSpace (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft): issue is Issue {
return (issue as Issue).space !== undefined
}
async function updateComponentMembers (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft) {
async function updateComponentMembers (project: Project, issue: Object) {
if (issue.component) {
const component = await client.findOne(tracker.class.Component, { _id: issue.component })
projectLead = component?.lead || undefined
componentLead = component?.lead || undefined
} else {
projectLead = undefined
componentLead = undefined
}
projectMembers = []
if (hasSpace(issue)) {
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
if (project !== undefined) {
const accounts = project.members
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
.filter((p) => p !== undefined) as EmployeeAccount[]
members = accounts.map((p) => p.employee)
} else {
members = []
}
if (project !== undefined) {
const accounts = project.members
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
.filter((p) => p !== undefined) as EmployeeAccount[]
members = accounts.map((p) => p.employee)
} else {
members = []
}
docQuery = project?.private ? { _id: { $in: members }, active: true } : { active: true }
}
$: updateComponentMembers(value)
const handleAssigneeChanged = async (newAssignee: Ref<Employee> | undefined) => {
if (newAssignee === undefined || value.assignee === newAssignee) {
if (newAssignee === undefined || object.assignee === newAssignee) {
return
}
dispatch('change', newAssignee)
if ('_class' in value) {
await client.update(value, { assignee: newAssignee })
if ('_class' in object) {
await client.update(object, { assignee: newAssignee })
}
}
$: projectQuery.query(tracker.class.Project, { _id: object.space }, (res) => ([project] = res))
$: project && updateComponentMembers(project, object)
$: docQuery = project?.private ? { _id: { $in: members }, active: true } : { active: true }
</script>
{#if value}
{#if object}
<AssigneeBox
{docQuery}
{focusIndex}
label={tracker.string.Assignee}
placeholder={tracker.string.Assignee}
value={value.assignee}
value={object.assignee}
{prevAssigned}
{projectLead}
{projectMembers}
{componentLead}
{members}
titleDeselect={tracker.string.Unassigned}
{size}
@@ -101,9 +102,15 @@
{avatarSize}
{width}
{short}
{shouldShowName}
showNavigate={false}
justify={'left'}
showTooltip={{ label: tracker.string.AssignTo, direction: tooltipAlignment }}
showTooltip={{
label: tracker.string.AssignTo,
personLabel: tracker.string.AssignedTo,
placeholderLabel: tracker.string.Unassigned,
direction: tooltipAlignment
}}
on:change={({ detail }) => handleAssigneeChanged(detail)}
/>
{/if}
@@ -1,99 +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 { UsersPopup } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Issue, IssueTemplate } from '@hcengineering/tracker'
import { eventToHTMLElement, showPopup, IconSize } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import tracker from '../../plugin'
export let value: Employee | Ref<Employee> | null | undefined
export let object: Issue | IssueTemplate
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
export let isEditable: boolean = true
export let shouldShowLabel: boolean = false
export let defaultName: IntlString | undefined = undefined
export let avatarSize: IconSize = 'x-small'
const client = getClient()
let presenter: AttributeModel | undefined
$: if (value || defaultClass) {
if (value) {
getObjectPresenter(client, typeof value === 'string' ? contact.class.Employee : value._class, { key: '' }).then(
(p) => {
presenter = p
}
)
} else if (defaultClass) {
getObjectPresenter(client, defaultClass, { key: '' }).then((p) => {
presenter = p
})
}
}
const handleAssigneeChanged = async (result: Employee | null | undefined) => {
if (!isEditable || result === undefined) {
return
}
const newAssignee = result === null ? null : result._id
await client.update(object, { assignee: newAssignee })
}
const handleAssigneeEditorOpened = async (event: MouseEvent) => {
if (!isEditable) {
return
}
event?.preventDefault()
event?.stopPropagation()
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
selected: typeof value === 'string' ? value : value?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: tracker.string.AssignTo
},
eventToHTMLElement(event),
handleAssigneeChanged
)
}
</script>
{#if presenter}
<svelte:component
this={presenter.presenter}
{value}
{defaultName}
{avatarSize}
disabled={false}
shouldShowPlaceholder={true}
shouldShowName={shouldShowLabel}
onEmployeeEdit={handleAssigneeEditorOpened}
tooltipLabels={{ personLabel: tracker.string.AssignedTo, placeholderLabel: tracker.string.Unassigned }}
/>
{/if}
@@ -83,7 +83,7 @@
<StatusEditor value={issue} shouldShowLabel kind={'secondary'} />
<PriorityEditor value={issue} shouldShowLabel kind={'secondary'} />
{#if issue.assignee}
<AssigneeEditor value={issue} width={'min-content'} kind={'secondary'} />
<AssigneeEditor object={issue} kind={'secondary'} />
{/if}
</div>
@@ -15,8 +15,6 @@
<script lang="ts">
import { AttachmentsPresenter } from '@hcengineering/attachment-resources'
import { CommentsPresenter } from '@hcengineering/chunter-resources'
import contact from '@hcengineering/contact'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import {
CategoryType,
Class,
@@ -77,7 +75,7 @@
import tracker from '../../plugin'
import ComponentEditor from '../components/ComponentEditor.svelte'
import CreateIssue from '../CreateIssue.svelte'
import AssigneePresenter from './AssigneePresenter.svelte'
import AssigneeEditor from './AssigneeEditor.svelte'
import DueDatePresenter from './DueDatePresenter.svelte'
import SubIssuesSelector from './edit/SubIssuesSelector.svelte'
import IssuePresenter from './IssuePresenter.svelte'
@@ -366,13 +364,7 @@
</div>
<div class="flex-row-center gap-2 reverse flex-no-shrink">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
<AssigneePresenter
value={issue.assignee ? $employeeByIdStore.get(issue.assignee) : null}
defaultClass={contact.class.Employee}
object={issue}
isEditable={true}
avatarSize={'card'}
/>
<AssigneeEditor object={issue} avatarSize={'card'} shouldShowName={false} />
</div>
</div>
<div class="card-content text-md caption-color lines-limit-2">
@@ -161,7 +161,7 @@
<span class="label">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor value={issue} size={'medium'} avatarSize={'card'} />
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" />
<span class="labelTop">
<Label label={tracker.string.Labels} />
@@ -176,10 +176,9 @@
on:change={({ detail }) => (object.priority = detail)}
/>
<AssigneeEditor
value={object}
object={{ ...object, space }}
kind={'secondary'}
size={'large'}
width={'min-content'}
on:change={({ detail }) => (object.assignee = detail)}
/>
<Component
@@ -210,7 +210,7 @@
<div id="sub-issue-assignee-editor">
{#key object.assignee}
<AssigneeEditor
value={object}
{object}
size="small"
kind="no-border"
on:change={({ detail }) => (object.assignee = detail)}
@@ -163,7 +163,7 @@
}}
/>
<AssigneeEditor
value={issue}
object={issue}
on:change={(evt) => {
dispatch('update-issue', { id: issue._id, assignee: evt.detail })
issue.assignee = evt.detail
@@ -17,7 +17,13 @@
import presentation, { createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { StyledTextArea } from '@hcengineering/text-editor'
import { IssuePriority, IssueTemplateChild, Component as ComponentType, Milestone } from '@hcengineering/tracker'
import {
IssuePriority,
IssueTemplateChild,
Component as ComponentType,
Milestone,
Project
} from '@hcengineering/tracker'
import { Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
@@ -25,6 +31,7 @@
import PriorityEditor from '../issues/PriorityEditor.svelte'
import EstimationEditor from './EstimationEditor.svelte'
export let projectId: Ref<Project>
export let milestone: Ref<Milestone> | null = null
export let component: Ref<ComponentType> | null = null
export let childIssue: IssueTemplateChild | undefined = undefined
@@ -142,7 +149,7 @@
/>
{#key newIssue.assignee}
<AssigneeEditor
value={newIssue}
object={{ ...newIssue, space: projectId }}
size="small"
kind="no-border"
width="auto"
@@ -158,7 +158,7 @@
}}
/>
<AssigneeEditor
value={issue}
object={{ ...issue, space: project }}
on:change={(evt) => {
dispatch('update-issue', { id: issue.id, assignee: evt.detail })
issue.assignee = evt.detail
@@ -105,6 +105,7 @@
{#if isCreating}
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
<IssueTemplateChildEditor
projectId={project}
{component}
{milestone}
{isScrollable}
@@ -73,7 +73,7 @@
<span class="label">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor value={issue} size={'medium'} />
<AssigneeEditor object={issue} size={'medium'} width="100%" />
<span class="labelTop">
<Label label={tracker.string.Labels} />
+2 -2
View File
@@ -37,7 +37,7 @@ import LeadPresenter from './components/components/LeadPresenter.svelte'
import ProjectComponents from './components/components/ProjectComponents.svelte'
import CreateIssue from './components/CreateIssue.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import AssigneeEditor from './components/issues/AssigneeEditor.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
import EditIssue from './components/issues/edit/EditIssue.svelte'
import IssueItem from './components/issues/IssueItem.svelte'
@@ -395,7 +395,7 @@ export default async (): Promise<Resources> => ({
ComponentEditor,
StatusPresenter,
StatusEditor,
AssigneePresenter,
AssigneeEditor,
DueDatePresenter,
EditIssue,
NewIssueHeader,
+1 -1
View File
@@ -326,7 +326,7 @@ export default mergeIds(trackerId, tracker, {
StatusPresenter: '' as AnyComponent,
StatusRefPresenter: '' as AnyComponent,
StatusEditor: '' as AnyComponent,
AssigneePresenter: '' as AnyComponent,
AssigneeEditor: '' as AnyComponent,
DueDatePresenter: '' as AnyComponent,
EditIssueTemplate: '' as AnyComponent,
CreateProject: '' as AnyComponent,
+2 -2
View File
@@ -463,13 +463,13 @@ export function subIssueListProvider (subIssues: Issue[], target: Ref<Issue>): v
}
}
export async function getPreviousAssignees (issue: Issue): Promise<Array<Ref<Employee>>> {
export async function getPreviousAssignees (objectId: Ref<Doc>): Promise<Array<Ref<Employee>>> {
return await new Promise((resolve) => {
const query = createQuery(true)
query.query(
core.class.Tx,
{
'tx.objectId': issue._id,
'tx.objectId': objectId,
'tx.operations.assignee': { $exists: true }
},
(res) => {