From 8ce6d005dfa2238f30f6ea900775407a59dbcbce Mon Sep 17 00:00:00 2001
From: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
Date: Thu, 1 Jun 2023 20:38:53 +0400
Subject: [PATCH] [UBER-334] Add categories to the assignee popup (#3324)

---
 models/tracker/src/index.ts                   | 16 ++-
 plugins/contact-assets/lang/en.json           |  3 +-
 plugins/contact-assets/lang/ru.json           |  3 +-
 plugins/contact-resources/src/assignee.ts     | 15 +--
 .../src/components/AssigneeBox.svelte         | 25 +++--
 .../src/components/AssigneePopup.svelte       | 16 ++-
 .../src/components/PersonPresenter.svelte     |  4 +-
 plugins/contact-resources/src/index.ts        |  3 +-
 plugins/contact-resources/src/plugin.ts       |  3 +-
 .../src/components/CreateIssue.svelte         |  3 +-
 .../components/issues/AssigneeEditor.svelte   | 83 +++++++++-------
 .../issues/AssigneePresenter.svelte           | 99 -------------------
 .../src/components/issues/IssuePreview.svelte |  2 +-
 .../src/components/issues/KanbanView.svelte   | 12 +--
 .../issues/edit/ControlPanel.svelte           |  2 +-
 .../templates/CreateIssueTemplate.svelte      |  3 +-
 .../templates/DraftIssueChildEditor.svelte    |  2 +-
 .../templates/DraftIssueChildList.svelte      |  2 +-
 .../templates/IssueTemplateChildEditor.svelte | 11 ++-
 .../templates/IssueTemplateChildList.svelte   |  2 +-
 .../templates/IssueTemplateChilds.svelte      |  1 +
 .../templates/TemplateControlPanel.svelte     |  2 +-
 plugins/tracker-resources/src/index.ts        |  4 +-
 plugins/tracker-resources/src/plugin.ts       |  2 +-
 plugins/tracker-resources/src/utils.ts        |  4 +-
 25 files changed, 112 insertions(+), 210 deletions(-)
 delete mode 100644 plugins/tracker-resources/src/components/issues/AssigneePresenter.svelte

diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts
index 49b0b1348c..85dd3a99f5 100644
--- a/models/tracker/src/index.ts
+++ b/models/tracker/src/index.ts
@@ -604,13 +604,9 @@ export function createModel (builder: Builder): void {
         },
         {
           key: 'assignee',
-          presenter: tracker.component.AssigneePresenter,
+          presenter: tracker.component.AssigneeEditor,
           displayProps: { key: 'assigee', fixed: 'right' },
-          props: {
-            key: 'assignee',
-            defaultClass: contact.class.Employee,
-            shouldShowLabel: false
-          }
+          props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
         }
       ],
       options: {
@@ -715,8 +711,8 @@ export function createModel (builder: Builder): void {
         },
         {
           key: 'assignee',
-          presenter: tracker.component.AssigneePresenter,
-          props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
+          presenter: tracker.component.AssigneeEditor,
+          props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
         }
       ]
     },
@@ -791,8 +787,8 @@ export function createModel (builder: Builder): void {
         },
         {
           key: 'assignee',
-          presenter: tracker.component.AssigneePresenter,
-          props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
+          presenter: tracker.component.AssigneeEditor,
+          props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
         }
       ]
     },
diff --git a/plugins/contact-assets/lang/en.json b/plugins/contact-assets/lang/en.json
index 9827588bb8..baa9de059f 100644
--- a/plugins/contact-assets/lang/en.json
+++ b/plugins/contact-assets/lang/en.json
@@ -86,12 +86,11 @@
     "AvatarProvider": "Avatar provider",
     "GravatarsManaged": "Gravatars are managed",
     "Through": "through",
-    "CategoryProjectMembers": "Project members",
     "AddMembersHeader": "Add members to {value}:",
     "Assigned": "Assigned",
     "Unassigned": "Unassigned",
     "CategoryPreviousAssigned": "Previously assigned",
-    "CategoryProjectLead": "Project lead",
+    "CategoryComponentLead": "Component lead",
     "CategoryCurrentUser": "Current user",    
     "CategoryOther": "Other",
     "NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}",
diff --git a/plugins/contact-assets/lang/ru.json b/plugins/contact-assets/lang/ru.json
index afe3530eb1..81cedcf284 100644
--- a/plugins/contact-assets/lang/ru.json
+++ b/plugins/contact-assets/lang/ru.json
@@ -92,8 +92,7 @@
     "Unassigned": "Не назначен",
     "CategoryCurrentUser": "Текущий пользователь",
     "CategoryPreviousAssigned": "Ранее назначенные",
-    "CategoryProjectLead": "Руководитель проекта",
-    "CategoryProjectMembers": "Участники проекта",
+    "CategoryComponentLead": "Ответственный за компонент",
     "CategoryOther": "Прочие",
     "Position": "Должность",
     "ConfigLabel": "Контакты",
diff --git a/plugins/contact-resources/src/assignee.ts b/plugins/contact-resources/src/assignee.ts
index d34eb704cd..7eefb560db 100644
--- a/plugins/contact-resources/src/assignee.ts
+++ b/plugins/contact-resources/src/assignee.ts
@@ -4,21 +4,13 @@ import contact from './plugin'
 /**
  * @public
  */
-export type AssigneeCategory =
-  | 'CurrentUser'
-  | 'Assigned'
-  | 'PreviouslyAssigned'
-  | 'ProjectLead'
-  | 'ProjectMembers'
-  | 'Members'
-  | 'Other'
+export type AssigneeCategory = 'CurrentUser' | 'Assigned' | 'PreviouslyAssigned' | 'ComponentLead' | 'Members' | 'Other'
 
 const assigneeCategoryTitleMap: Record<AssigneeCategory, IntlString> = Object.freeze({
   CurrentUser: contact.string.CategoryCurrentUser,
   Assigned: contact.string.Assigned,
   PreviouslyAssigned: contact.string.CategoryPreviousAssigned,
-  ProjectLead: contact.string.CategoryProjectLead,
-  ProjectMembers: contact.string.CategoryProjectMembers,
+  ComponentLead: contact.string.CategoryComponentLead,
   Members: contact.string.Members,
   Other: contact.string.CategoryOther
 })
@@ -30,8 +22,7 @@ export const assigneeCategoryOrder: AssigneeCategory[] = [
   'CurrentUser',
   'Assigned',
   'PreviouslyAssigned',
-  'ProjectLead',
-  'ProjectMembers',
+  'ComponentLead',
   'Members',
   'Other'
 ]
diff --git a/plugins/contact-resources/src/components/AssigneeBox.svelte b/plugins/contact-resources/src/components/AssigneeBox.svelte
index 5e1e353f0d..aebbc59987 100644
--- a/plugins/contact-resources/src/components/AssigneeBox.svelte
+++ b/plugins/contact-resources/src/components/AssigneeBox.svelte
@@ -35,9 +35,11 @@
   import view from '@hcengineering/view'
   import { createEventDispatcher } from 'svelte'
   import presentation, { getClient } from '@hcengineering/presentation'
+  import { PersonLabelTooltip, employeeByIdStore } from '..'
   import AssigneePopup from './AssigneePopup.svelte'
   import IconPerson from './icons/Person.svelte'
   import UserInfo from './UserInfo.svelte'
+  import EmployeePresenter from './EmployeePresenter.svelte'
 
   export let _class: Ref<Class<Employee>> = contact.class.Employee
   export let excluded: Ref<Contact>[] | undefined = undefined
@@ -49,8 +51,7 @@
   export let placeholder: IntlString = presentation.string.Search
   export let value: Ref<Employee> | null | undefined
   export let prevAssigned: Ref<Employee>[] | undefined = []
-  export let projectLead: Ref<Employee> | undefined = undefined
-  export let projectMembers: Ref<Employee>[] | undefined = []
+  export let componentLead: Ref<Employee> | undefined = undefined
   export let members: Ref<Employee>[] | undefined = []
   export let allowDeselect = true
   export let titleDeselect: IntlString | undefined = undefined
@@ -61,8 +62,9 @@
   export let justify: 'left' | 'center' = 'center'
   export let width: string | undefined = undefined
   export let focusIndex = -1
-  export let showTooltip: LabelAndProps | undefined = undefined
+  export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined
   export let showNavigate = true
+  export let shouldShowName = true
   export let id: string | undefined = undefined
   export let short: boolean = false
 
@@ -76,7 +78,7 @@
   const client = getClient()
 
   async function updateSelected (value: Ref<Employee> | null | undefined) {
-    selected = value ? await client.findOne(_class, { _id: value }) : undefined
+    selected = value ? $employeeByIdStore.get(value) ?? (await client.findOne(_class, { _id: value })) : undefined
   }
 
   $: updateSelected(value)
@@ -85,6 +87,9 @@
 
   const _click = (ev: MouseEvent): void => {
     if (!readonly) {
+      ev.preventDefault()
+      ev.stopPropagation()
+
       showPopup(
         AssigneePopup,
         {
@@ -92,8 +97,7 @@
           options,
           docQuery,
           prevAssigned,
-          projectLead,
-          projectMembers,
+          componentLead,
           members,
           ignoreUsers: excluded ?? [],
           icon,
@@ -131,6 +135,15 @@
     >
       <slot name="content" />
     </div>
+  {:else if !shouldShowName}
+    <EmployeePresenter
+      value={selected}
+      {avatarSize}
+      tooltipLabels={showTooltip}
+      shouldShowName={false}
+      shouldShowPlaceholder
+      onEmployeeEdit={_click}
+    />
   {:else}
     <Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
       <span
diff --git a/plugins/contact-resources/src/components/AssigneePopup.svelte b/plugins/contact-resources/src/components/AssigneePopup.svelte
index 5ec4c78967..7ece26934e 100644
--- a/plugins/contact-resources/src/components/AssigneePopup.svelte
+++ b/plugins/contact-resources/src/components/AssigneePopup.svelte
@@ -39,8 +39,7 @@
   export let selected: Ref<Person> | undefined
   export let docQuery: DocumentQuery<Contact> | undefined = undefined
   export let prevAssigned: Ref<Employee>[] | undefined = []
-  export let projectLead: Ref<Employee> | undefined = undefined
-  export let projectMembers: Ref<Employee>[] | undefined = []
+  export let componentLead: Ref<Employee> | undefined = undefined
   export let members: Ref<Employee>[] | undefined = []
   export let allowDeselect = true
   export let titleDeselect: IntlString | undefined
@@ -49,7 +48,6 @@
   export let ignoreUsers: Ref<Person>[] = []
   export let shadows: boolean = true
   export let width: 'medium' | 'large' | 'full' = 'medium'
-  export let size: 'small' | 'medium' | 'large' = 'small'
   export let searchField: string = 'name'
   export let showCategories: boolean = true
   export let icon: Asset | AnySvelteComponent | undefined = undefined
@@ -83,22 +81,20 @@
     { ...(options ?? {}), limit: 200, sort: { name: 1 } }
   )
 
-  $: updateCategories(objects, currentEmployee, prevAssigned, projectLead, members, projectMembers)
+  $: updateCategories(objects, currentEmployee, prevAssigned, componentLead, members)
 
   function updateCategories (
     objects: Contact[],
     currentEmployee: Ref<Person>,
     prevAssigned: Ref<Person>[] | undefined,
-    projectLead: Ref<Person> | undefined,
-    members: Ref<Person>[] | undefined,
-    projectMembers: Ref<Person>[] | undefined
+    componentLead: Ref<Person> | undefined,
+    members: Ref<Person>[] | undefined
   ) {
     const persons = new Map<Ref<Person>, AssigneeCategory>(objects.map((t) => [t._id, 'Other']))
-    if (projectLead) {
-      persons.set(projectLead, 'ProjectLead')
+    if (componentLead) {
+      persons.set(componentLead, 'ComponentLead')
     }
     members?.forEach((p) => persons.set(p, 'Members'))
-    projectMembers?.forEach((p) => persons.set(p, 'ProjectMembers'))
     prevAssigned?.forEach((p) => persons.set(p, 'PreviouslyAssigned'))
     if (selected) {
       persons.set(selected, 'Assigned')
diff --git a/plugins/contact-resources/src/components/PersonPresenter.svelte b/plugins/contact-resources/src/components/PersonPresenter.svelte
index e915690855..4b7b1cdf27 100644
--- a/plugins/contact-resources/src/components/PersonPresenter.svelte
+++ b/plugins/contact-resources/src/components/PersonPresenter.svelte
@@ -47,6 +47,7 @@
             label: getEmbeddedLabel(getName(value))
           }
     }
+    const direction = tooltipLabels?.direction
     const component = value ? tooltipLabels.component : undefined
     const label = value
       ? tooltipLabels.personLabel
@@ -59,7 +60,8 @@
     return {
       component,
       label,
-      props
+      props,
+      direction
     }
   }
 </script>
diff --git a/plugins/contact-resources/src/index.ts b/plugins/contact-resources/src/index.ts
index d9e1acbe7f..578a1263b8 100644
--- a/plugins/contact-resources/src/index.ts
+++ b/plugins/contact-resources/src/index.ts
@@ -19,7 +19,7 @@ import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '
 import login from '@hcengineering/login'
 import { IntlString, Resources, getResource } from '@hcengineering/platform'
 import { MessageBox, ObjectSearchResult, getClient, getFileUrl } from '@hcengineering/presentation'
-import { AnyComponent, AnySvelteComponent, showPopup } from '@hcengineering/ui'
+import { AnyComponent, AnySvelteComponent, TooltipAlignment, showPopup } from '@hcengineering/ui'
 import AccountArrayEditor from './components/AccountArrayEditor.svelte'
 import AccountBox from './components/AccountBox.svelte'
 import AssigneeBox from './components/AssigneeBox.svelte'
@@ -246,6 +246,7 @@ async function openChannelURL (doc: Channel): Promise<void> {
 export interface PersonLabelTooltip {
   personLabel?: IntlString
   placeholderLabel?: IntlString
+  direction?: TooltipAlignment
   component?: AnySvelteComponent | AnyComponent
   props?: any
 }
diff --git a/plugins/contact-resources/src/plugin.ts b/plugins/contact-resources/src/plugin.ts
index 8bb1360472..48f1485bc7 100644
--- a/plugins/contact-resources/src/plugin.ts
+++ b/plugins/contact-resources/src/plugin.ts
@@ -69,13 +69,12 @@ export default mergeIds(contactId, contact, {
     Through: '' as IntlString,
     AvatarProvider: '' as IntlString,
 
-    CategoryProjectMembers: '' as IntlString,
     AddMembersHeader: '' as IntlString,
     Assigned: '' as IntlString,
     Unassigned: '' as IntlString,
     CategoryCurrentUser: '' as IntlString,
     CategoryPreviousAssigned: '' as IntlString,
-    CategoryProjectLead: '' as IntlString,
+    CategoryComponentLead: '' as IntlString,
     CategoryOther: '' as IntlString,
     DeleteEmployee: '' as IntlString
   },
diff --git a/plugins/tracker-resources/src/components/CreateIssue.svelte b/plugins/tracker-resources/src/components/CreateIssue.svelte
index 356e988c14..2abb53e87d 100644
--- a/plugins/tracker-resources/src/components/CreateIssue.svelte
+++ b/plugins/tracker-resources/src/components/CreateIssue.svelte
@@ -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
diff --git a/plugins/tracker-resources/src/components/issues/AssigneeEditor.svelte b/plugins/tracker-resources/src/components/issues/AssigneeEditor.svelte
index c91d9fb9a7..ceb200ece2 100644
--- a/plugins/tracker-resources/src/components/issues/AssigneeEditor.svelte
+++ b/plugins/tracker-resources/src/components/issues/AssigneeEditor.svelte
@@ -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}
diff --git a/plugins/tracker-resources/src/components/issues/AssigneePresenter.svelte b/plugins/tracker-resources/src/components/issues/AssigneePresenter.svelte
deleted file mode 100644
index 46c3a987e7..0000000000
--- a/plugins/tracker-resources/src/components/issues/AssigneePresenter.svelte
+++ /dev/null
@@ -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}
diff --git a/plugins/tracker-resources/src/components/issues/IssuePreview.svelte b/plugins/tracker-resources/src/components/issues/IssuePreview.svelte
index 1cc9aa7964..ea0d188066 100644
--- a/plugins/tracker-resources/src/components/issues/IssuePreview.svelte
+++ b/plugins/tracker-resources/src/components/issues/IssuePreview.svelte
@@ -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>
 
diff --git a/plugins/tracker-resources/src/components/issues/KanbanView.svelte b/plugins/tracker-resources/src/components/issues/KanbanView.svelte
index 60585b7991..d08df24df7 100644
--- a/plugins/tracker-resources/src/components/issues/KanbanView.svelte
+++ b/plugins/tracker-resources/src/components/issues/KanbanView.svelte
@@ -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">
diff --git a/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte b/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte
index fb0fef5d0e..ddb47b0aae 100644
--- a/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte
+++ b/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte
@@ -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} />
diff --git a/plugins/tracker-resources/src/components/templates/CreateIssueTemplate.svelte b/plugins/tracker-resources/src/components/templates/CreateIssueTemplate.svelte
index e3dcce42c8..e70663799f 100644
--- a/plugins/tracker-resources/src/components/templates/CreateIssueTemplate.svelte
+++ b/plugins/tracker-resources/src/components/templates/CreateIssueTemplate.svelte
@@ -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
diff --git a/plugins/tracker-resources/src/components/templates/DraftIssueChildEditor.svelte b/plugins/tracker-resources/src/components/templates/DraftIssueChildEditor.svelte
index a9d432ed96..0e5e518118 100644
--- a/plugins/tracker-resources/src/components/templates/DraftIssueChildEditor.svelte
+++ b/plugins/tracker-resources/src/components/templates/DraftIssueChildEditor.svelte
@@ -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)}
diff --git a/plugins/tracker-resources/src/components/templates/DraftIssueChildList.svelte b/plugins/tracker-resources/src/components/templates/DraftIssueChildList.svelte
index 093411913e..1f5c281454 100644
--- a/plugins/tracker-resources/src/components/templates/DraftIssueChildList.svelte
+++ b/plugins/tracker-resources/src/components/templates/DraftIssueChildList.svelte
@@ -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
diff --git a/plugins/tracker-resources/src/components/templates/IssueTemplateChildEditor.svelte b/plugins/tracker-resources/src/components/templates/IssueTemplateChildEditor.svelte
index a205aa7910..68b1c5f4d8 100644
--- a/plugins/tracker-resources/src/components/templates/IssueTemplateChildEditor.svelte
+++ b/plugins/tracker-resources/src/components/templates/IssueTemplateChildEditor.svelte
@@ -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"
diff --git a/plugins/tracker-resources/src/components/templates/IssueTemplateChildList.svelte b/plugins/tracker-resources/src/components/templates/IssueTemplateChildList.svelte
index 5189a9da43..4f48625051 100644
--- a/plugins/tracker-resources/src/components/templates/IssueTemplateChildList.svelte
+++ b/plugins/tracker-resources/src/components/templates/IssueTemplateChildList.svelte
@@ -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
diff --git a/plugins/tracker-resources/src/components/templates/IssueTemplateChilds.svelte b/plugins/tracker-resources/src/components/templates/IssueTemplateChilds.svelte
index 677a9e2380..48591a98ab 100644
--- a/plugins/tracker-resources/src/components/templates/IssueTemplateChilds.svelte
+++ b/plugins/tracker-resources/src/components/templates/IssueTemplateChilds.svelte
@@ -105,6 +105,7 @@
 {#if isCreating}
   <ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
     <IssueTemplateChildEditor
+      projectId={project}
       {component}
       {milestone}
       {isScrollable}
diff --git a/plugins/tracker-resources/src/components/templates/TemplateControlPanel.svelte b/plugins/tracker-resources/src/components/templates/TemplateControlPanel.svelte
index f98619688d..05611f4c68 100644
--- a/plugins/tracker-resources/src/components/templates/TemplateControlPanel.svelte
+++ b/plugins/tracker-resources/src/components/templates/TemplateControlPanel.svelte
@@ -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} />
diff --git a/plugins/tracker-resources/src/index.ts b/plugins/tracker-resources/src/index.ts
index 27583e70db..ca237f0821 100644
--- a/plugins/tracker-resources/src/index.ts
+++ b/plugins/tracker-resources/src/index.ts
@@ -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,
diff --git a/plugins/tracker-resources/src/plugin.ts b/plugins/tracker-resources/src/plugin.ts
index 263f59c511..a5b5604940 100644
--- a/plugins/tracker-resources/src/plugin.ts
+++ b/plugins/tracker-resources/src/plugin.ts
@@ -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,
diff --git a/plugins/tracker-resources/src/utils.ts b/plugins/tracker-resources/src/utils.ts
index 6904de0b95..b8d7a83c92 100644
--- a/plugins/tracker-resources/src/utils.ts
+++ b/plugins/tracker-resources/src/utils.ts
@@ -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) => {