mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-05 10:29:51 +03:00
[UBER-334] Add categories to the assignee popup (#3324)
This commit is contained in:
parent
eee6e69114
commit
8ce6d005df
@ -604,13 +604,9 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
presenter: tracker.component.AssigneePresenter,
|
presenter: tracker.component.AssigneeEditor,
|
||||||
displayProps: { key: 'assigee', fixed: 'right' },
|
displayProps: { key: 'assigee', fixed: 'right' },
|
||||||
props: {
|
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||||
key: 'assignee',
|
|
||||||
defaultClass: contact.class.Employee,
|
|
||||||
shouldShowLabel: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
@ -715,8 +711,8 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
presenter: tracker.component.AssigneePresenter,
|
presenter: tracker.component.AssigneeEditor,
|
||||||
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
|
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -791,8 +787,8 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
presenter: tracker.component.AssigneePresenter,
|
presenter: tracker.component.AssigneeEditor,
|
||||||
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
|
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -86,12 +86,11 @@
|
|||||||
"AvatarProvider": "Avatar provider",
|
"AvatarProvider": "Avatar provider",
|
||||||
"GravatarsManaged": "Gravatars are managed",
|
"GravatarsManaged": "Gravatars are managed",
|
||||||
"Through": "through",
|
"Through": "through",
|
||||||
"CategoryProjectMembers": "Project members",
|
|
||||||
"AddMembersHeader": "Add members to {value}:",
|
"AddMembersHeader": "Add members to {value}:",
|
||||||
"Assigned": "Assigned",
|
"Assigned": "Assigned",
|
||||||
"Unassigned": "Unassigned",
|
"Unassigned": "Unassigned",
|
||||||
"CategoryPreviousAssigned": "Previously assigned",
|
"CategoryPreviousAssigned": "Previously assigned",
|
||||||
"CategoryProjectLead": "Project lead",
|
"CategoryComponentLead": "Component lead",
|
||||||
"CategoryCurrentUser": "Current user",
|
"CategoryCurrentUser": "Current user",
|
||||||
"CategoryOther": "Other",
|
"CategoryOther": "Other",
|
||||||
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}",
|
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}",
|
||||||
|
@ -92,8 +92,7 @@
|
|||||||
"Unassigned": "Не назначен",
|
"Unassigned": "Не назначен",
|
||||||
"CategoryCurrentUser": "Текущий пользователь",
|
"CategoryCurrentUser": "Текущий пользователь",
|
||||||
"CategoryPreviousAssigned": "Ранее назначенные",
|
"CategoryPreviousAssigned": "Ранее назначенные",
|
||||||
"CategoryProjectLead": "Руководитель проекта",
|
"CategoryComponentLead": "Ответственный за компонент",
|
||||||
"CategoryProjectMembers": "Участники проекта",
|
|
||||||
"CategoryOther": "Прочие",
|
"CategoryOther": "Прочие",
|
||||||
"Position": "Должность",
|
"Position": "Должность",
|
||||||
"ConfigLabel": "Контакты",
|
"ConfigLabel": "Контакты",
|
||||||
|
@ -4,21 +4,13 @@ import contact from './plugin'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type AssigneeCategory =
|
export type AssigneeCategory = 'CurrentUser' | 'Assigned' | 'PreviouslyAssigned' | 'ComponentLead' | 'Members' | 'Other'
|
||||||
| 'CurrentUser'
|
|
||||||
| 'Assigned'
|
|
||||||
| 'PreviouslyAssigned'
|
|
||||||
| 'ProjectLead'
|
|
||||||
| 'ProjectMembers'
|
|
||||||
| 'Members'
|
|
||||||
| 'Other'
|
|
||||||
|
|
||||||
const assigneeCategoryTitleMap: Record<AssigneeCategory, IntlString> = Object.freeze({
|
const assigneeCategoryTitleMap: Record<AssigneeCategory, IntlString> = Object.freeze({
|
||||||
CurrentUser: contact.string.CategoryCurrentUser,
|
CurrentUser: contact.string.CategoryCurrentUser,
|
||||||
Assigned: contact.string.Assigned,
|
Assigned: contact.string.Assigned,
|
||||||
PreviouslyAssigned: contact.string.CategoryPreviousAssigned,
|
PreviouslyAssigned: contact.string.CategoryPreviousAssigned,
|
||||||
ProjectLead: contact.string.CategoryProjectLead,
|
ComponentLead: contact.string.CategoryComponentLead,
|
||||||
ProjectMembers: contact.string.CategoryProjectMembers,
|
|
||||||
Members: contact.string.Members,
|
Members: contact.string.Members,
|
||||||
Other: contact.string.CategoryOther
|
Other: contact.string.CategoryOther
|
||||||
})
|
})
|
||||||
@ -30,8 +22,7 @@ export const assigneeCategoryOrder: AssigneeCategory[] = [
|
|||||||
'CurrentUser',
|
'CurrentUser',
|
||||||
'Assigned',
|
'Assigned',
|
||||||
'PreviouslyAssigned',
|
'PreviouslyAssigned',
|
||||||
'ProjectLead',
|
'ComponentLead',
|
||||||
'ProjectMembers',
|
|
||||||
'Members',
|
'Members',
|
||||||
'Other'
|
'Other'
|
||||||
]
|
]
|
||||||
|
@ -35,9 +35,11 @@
|
|||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import presentation, { getClient } from '@hcengineering/presentation'
|
import presentation, { getClient } from '@hcengineering/presentation'
|
||||||
|
import { PersonLabelTooltip, employeeByIdStore } from '..'
|
||||||
import AssigneePopup from './AssigneePopup.svelte'
|
import AssigneePopup from './AssigneePopup.svelte'
|
||||||
import IconPerson from './icons/Person.svelte'
|
import IconPerson from './icons/Person.svelte'
|
||||||
import UserInfo from './UserInfo.svelte'
|
import UserInfo from './UserInfo.svelte'
|
||||||
|
import EmployeePresenter from './EmployeePresenter.svelte'
|
||||||
|
|
||||||
export let _class: Ref<Class<Employee>> = contact.class.Employee
|
export let _class: Ref<Class<Employee>> = contact.class.Employee
|
||||||
export let excluded: Ref<Contact>[] | undefined = undefined
|
export let excluded: Ref<Contact>[] | undefined = undefined
|
||||||
@ -49,8 +51,7 @@
|
|||||||
export let placeholder: IntlString = presentation.string.Search
|
export let placeholder: IntlString = presentation.string.Search
|
||||||
export let value: Ref<Employee> | null | undefined
|
export let value: Ref<Employee> | null | undefined
|
||||||
export let prevAssigned: Ref<Employee>[] | undefined = []
|
export let prevAssigned: Ref<Employee>[] | undefined = []
|
||||||
export let projectLead: Ref<Employee> | undefined = undefined
|
export let componentLead: Ref<Employee> | undefined = undefined
|
||||||
export let projectMembers: Ref<Employee>[] | undefined = []
|
|
||||||
export let members: Ref<Employee>[] | undefined = []
|
export let members: Ref<Employee>[] | undefined = []
|
||||||
export let allowDeselect = true
|
export let allowDeselect = true
|
||||||
export let titleDeselect: IntlString | undefined = undefined
|
export let titleDeselect: IntlString | undefined = undefined
|
||||||
@ -61,8 +62,9 @@
|
|||||||
export let justify: 'left' | 'center' = 'center'
|
export let justify: 'left' | 'center' = 'center'
|
||||||
export let width: string | undefined = undefined
|
export let width: string | undefined = undefined
|
||||||
export let focusIndex = -1
|
export let focusIndex = -1
|
||||||
export let showTooltip: LabelAndProps | undefined = undefined
|
export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined
|
||||||
export let showNavigate = true
|
export let showNavigate = true
|
||||||
|
export let shouldShowName = true
|
||||||
export let id: string | undefined = undefined
|
export let id: string | undefined = undefined
|
||||||
export let short: boolean = false
|
export let short: boolean = false
|
||||||
|
|
||||||
@ -76,7 +78,7 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function updateSelected (value: Ref<Employee> | null | undefined) {
|
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)
|
$: updateSelected(value)
|
||||||
@ -85,6 +87,9 @@
|
|||||||
|
|
||||||
const _click = (ev: MouseEvent): void => {
|
const _click = (ev: MouseEvent): void => {
|
||||||
if (!readonly) {
|
if (!readonly) {
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
AssigneePopup,
|
AssigneePopup,
|
||||||
{
|
{
|
||||||
@ -92,8 +97,7 @@
|
|||||||
options,
|
options,
|
||||||
docQuery,
|
docQuery,
|
||||||
prevAssigned,
|
prevAssigned,
|
||||||
projectLead,
|
componentLead,
|
||||||
projectMembers,
|
|
||||||
members,
|
members,
|
||||||
ignoreUsers: excluded ?? [],
|
ignoreUsers: excluded ?? [],
|
||||||
icon,
|
icon,
|
||||||
@ -131,6 +135,15 @@
|
|||||||
>
|
>
|
||||||
<slot name="content" />
|
<slot name="content" />
|
||||||
</div>
|
</div>
|
||||||
|
{:else if !shouldShowName}
|
||||||
|
<EmployeePresenter
|
||||||
|
value={selected}
|
||||||
|
{avatarSize}
|
||||||
|
tooltipLabels={showTooltip}
|
||||||
|
shouldShowName={false}
|
||||||
|
shouldShowPlaceholder
|
||||||
|
onEmployeeEdit={_click}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
|
<Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
|
||||||
<span
|
<span
|
||||||
|
@ -39,8 +39,7 @@
|
|||||||
export let selected: Ref<Person> | undefined
|
export let selected: Ref<Person> | undefined
|
||||||
export let docQuery: DocumentQuery<Contact> | undefined = undefined
|
export let docQuery: DocumentQuery<Contact> | undefined = undefined
|
||||||
export let prevAssigned: Ref<Employee>[] | undefined = []
|
export let prevAssigned: Ref<Employee>[] | undefined = []
|
||||||
export let projectLead: Ref<Employee> | undefined = undefined
|
export let componentLead: Ref<Employee> | undefined = undefined
|
||||||
export let projectMembers: Ref<Employee>[] | undefined = []
|
|
||||||
export let members: Ref<Employee>[] | undefined = []
|
export let members: Ref<Employee>[] | undefined = []
|
||||||
export let allowDeselect = true
|
export let allowDeselect = true
|
||||||
export let titleDeselect: IntlString | undefined
|
export let titleDeselect: IntlString | undefined
|
||||||
@ -49,7 +48,6 @@
|
|||||||
export let ignoreUsers: Ref<Person>[] = []
|
export let ignoreUsers: Ref<Person>[] = []
|
||||||
export let shadows: boolean = true
|
export let shadows: boolean = true
|
||||||
export let width: 'medium' | 'large' | 'full' = 'medium'
|
export let width: 'medium' | 'large' | 'full' = 'medium'
|
||||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
|
||||||
export let searchField: string = 'name'
|
export let searchField: string = 'name'
|
||||||
export let showCategories: boolean = true
|
export let showCategories: boolean = true
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
@ -83,22 +81,20 @@
|
|||||||
{ ...(options ?? {}), limit: 200, sort: { name: 1 } }
|
{ ...(options ?? {}), limit: 200, sort: { name: 1 } }
|
||||||
)
|
)
|
||||||
|
|
||||||
$: updateCategories(objects, currentEmployee, prevAssigned, projectLead, members, projectMembers)
|
$: updateCategories(objects, currentEmployee, prevAssigned, componentLead, members)
|
||||||
|
|
||||||
function updateCategories (
|
function updateCategories (
|
||||||
objects: Contact[],
|
objects: Contact[],
|
||||||
currentEmployee: Ref<Person>,
|
currentEmployee: Ref<Person>,
|
||||||
prevAssigned: Ref<Person>[] | undefined,
|
prevAssigned: Ref<Person>[] | undefined,
|
||||||
projectLead: Ref<Person> | undefined,
|
componentLead: Ref<Person> | undefined,
|
||||||
members: Ref<Person>[] | undefined,
|
members: Ref<Person>[] | undefined
|
||||||
projectMembers: Ref<Person>[] | undefined
|
|
||||||
) {
|
) {
|
||||||
const persons = new Map<Ref<Person>, AssigneeCategory>(objects.map((t) => [t._id, 'Other']))
|
const persons = new Map<Ref<Person>, AssigneeCategory>(objects.map((t) => [t._id, 'Other']))
|
||||||
if (projectLead) {
|
if (componentLead) {
|
||||||
persons.set(projectLead, 'ProjectLead')
|
persons.set(componentLead, 'ComponentLead')
|
||||||
}
|
}
|
||||||
members?.forEach((p) => persons.set(p, 'Members'))
|
members?.forEach((p) => persons.set(p, 'Members'))
|
||||||
projectMembers?.forEach((p) => persons.set(p, 'ProjectMembers'))
|
|
||||||
prevAssigned?.forEach((p) => persons.set(p, 'PreviouslyAssigned'))
|
prevAssigned?.forEach((p) => persons.set(p, 'PreviouslyAssigned'))
|
||||||
if (selected) {
|
if (selected) {
|
||||||
persons.set(selected, 'Assigned')
|
persons.set(selected, 'Assigned')
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
label: getEmbeddedLabel(getName(value))
|
label: getEmbeddedLabel(getName(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const direction = tooltipLabels?.direction
|
||||||
const component = value ? tooltipLabels.component : undefined
|
const component = value ? tooltipLabels.component : undefined
|
||||||
const label = value
|
const label = value
|
||||||
? tooltipLabels.personLabel
|
? tooltipLabels.personLabel
|
||||||
@ -59,7 +60,8 @@
|
|||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
label,
|
label,
|
||||||
props
|
props,
|
||||||
|
direction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,7 +19,7 @@ import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '
|
|||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import { IntlString, Resources, getResource } from '@hcengineering/platform'
|
import { IntlString, Resources, getResource } from '@hcengineering/platform'
|
||||||
import { MessageBox, ObjectSearchResult, getClient, getFileUrl } from '@hcengineering/presentation'
|
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 AccountArrayEditor from './components/AccountArrayEditor.svelte'
|
||||||
import AccountBox from './components/AccountBox.svelte'
|
import AccountBox from './components/AccountBox.svelte'
|
||||||
import AssigneeBox from './components/AssigneeBox.svelte'
|
import AssigneeBox from './components/AssigneeBox.svelte'
|
||||||
@ -246,6 +246,7 @@ async function openChannelURL (doc: Channel): Promise<void> {
|
|||||||
export interface PersonLabelTooltip {
|
export interface PersonLabelTooltip {
|
||||||
personLabel?: IntlString
|
personLabel?: IntlString
|
||||||
placeholderLabel?: IntlString
|
placeholderLabel?: IntlString
|
||||||
|
direction?: TooltipAlignment
|
||||||
component?: AnySvelteComponent | AnyComponent
|
component?: AnySvelteComponent | AnyComponent
|
||||||
props?: any
|
props?: any
|
||||||
}
|
}
|
||||||
|
@ -69,13 +69,12 @@ export default mergeIds(contactId, contact, {
|
|||||||
Through: '' as IntlString,
|
Through: '' as IntlString,
|
||||||
AvatarProvider: '' as IntlString,
|
AvatarProvider: '' as IntlString,
|
||||||
|
|
||||||
CategoryProjectMembers: '' as IntlString,
|
|
||||||
AddMembersHeader: '' as IntlString,
|
AddMembersHeader: '' as IntlString,
|
||||||
Assigned: '' as IntlString,
|
Assigned: '' as IntlString,
|
||||||
Unassigned: '' as IntlString,
|
Unassigned: '' as IntlString,
|
||||||
CategoryCurrentUser: '' as IntlString,
|
CategoryCurrentUser: '' as IntlString,
|
||||||
CategoryPreviousAssigned: '' as IntlString,
|
CategoryPreviousAssigned: '' as IntlString,
|
||||||
CategoryProjectLead: '' as IntlString,
|
CategoryComponentLead: '' as IntlString,
|
||||||
CategoryOther: '' as IntlString,
|
CategoryOther: '' as IntlString,
|
||||||
DeleteEmployee: '' as IntlString
|
DeleteEmployee: '' as IntlString
|
||||||
},
|
},
|
||||||
|
@ -662,10 +662,9 @@
|
|||||||
<div id="assignee-editor">
|
<div id="assignee-editor">
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
focusIndex={5}
|
focusIndex={5}
|
||||||
value={object}
|
{object}
|
||||||
kind={'secondary'}
|
kind={'secondary'}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
width={'min-content'}
|
|
||||||
short
|
short
|
||||||
on:change={({ detail }) => {
|
on:change={({ detail }) => {
|
||||||
isAssigneeTouched = true
|
isAssigneeTouched = true
|
||||||
|
@ -15,85 +15,86 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { AssigneeBox, employeeAccountByIdStore } from '@hcengineering/contact-resources'
|
import { AssigneeBox, employeeAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { AttachedData, Ref } from '@hcengineering/core'
|
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueDraft, IssueTemplateData } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import { ButtonKind, ButtonSize, IconSize, TooltipAlignment } from '@hcengineering/ui'
|
import { ButtonKind, ButtonSize, IconSize, TooltipAlignment } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
|
||||||
import { getPreviousAssignees } from '../../utils'
|
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 kind: ButtonKind = 'link'
|
||||||
export let size: ButtonSize = 'large'
|
export let size: ButtonSize = 'large'
|
||||||
export let avatarSize: IconSize = 'card'
|
export let avatarSize: IconSize = 'card'
|
||||||
export let tooltipAlignment: TooltipAlignment | undefined = undefined
|
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 focusIndex: number | undefined = undefined
|
||||||
export let short: boolean = false
|
export let short: boolean = false
|
||||||
|
export let shouldShowName = true
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const projectQuery = createQuery()
|
||||||
|
|
||||||
|
let project: Project | undefined
|
||||||
let prevAssigned: Ref<Employee>[] = []
|
let prevAssigned: Ref<Employee>[] = []
|
||||||
let projectLead: Ref<Employee> | undefined = undefined
|
let componentLead: Ref<Employee> | undefined = undefined
|
||||||
let projectMembers: Ref<Employee>[] = []
|
|
||||||
let members: Ref<Employee>[] = []
|
let members: Ref<Employee>[] = []
|
||||||
|
let docQuery: DocumentQuery<Employee> = { active: true }
|
||||||
|
|
||||||
$: '_class' in value &&
|
$: '_class' in object &&
|
||||||
getPreviousAssignees(value).then((res) => {
|
getPreviousAssignees(object._id).then((res) => {
|
||||||
prevAssigned = res
|
prevAssigned = res
|
||||||
})
|
})
|
||||||
|
|
||||||
function hasSpace (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft): issue is Issue {
|
async function updateComponentMembers (project: Project, issue: Object) {
|
||||||
return (issue as Issue).space !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateComponentMembers (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft) {
|
|
||||||
if (issue.component) {
|
if (issue.component) {
|
||||||
const component = await client.findOne(tracker.class.Component, { _id: issue.component })
|
const component = await client.findOne(tracker.class.Component, { _id: issue.component })
|
||||||
projectLead = component?.lead || undefined
|
componentLead = component?.lead || undefined
|
||||||
} else {
|
} else {
|
||||||
projectLead = undefined
|
componentLead = undefined
|
||||||
}
|
}
|
||||||
projectMembers = []
|
if (project !== undefined) {
|
||||||
if (hasSpace(issue)) {
|
const accounts = project.members
|
||||||
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
|
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
|
||||||
if (project !== undefined) {
|
.filter((p) => p !== undefined) as EmployeeAccount[]
|
||||||
const accounts = project.members
|
members = accounts.map((p) => p.employee)
|
||||||
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
|
} else {
|
||||||
.filter((p) => p !== undefined) as EmployeeAccount[]
|
members = []
|
||||||
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) => {
|
const handleAssigneeChanged = async (newAssignee: Ref<Employee> | undefined) => {
|
||||||
if (newAssignee === undefined || value.assignee === newAssignee) {
|
if (newAssignee === undefined || object.assignee === newAssignee) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch('change', newAssignee)
|
dispatch('change', newAssignee)
|
||||||
|
|
||||||
if ('_class' in value) {
|
if ('_class' in object) {
|
||||||
await client.update(value, { assignee: newAssignee })
|
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>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if object}
|
||||||
<AssigneeBox
|
<AssigneeBox
|
||||||
|
{docQuery}
|
||||||
{focusIndex}
|
{focusIndex}
|
||||||
label={tracker.string.Assignee}
|
label={tracker.string.Assignee}
|
||||||
placeholder={tracker.string.Assignee}
|
placeholder={tracker.string.Assignee}
|
||||||
value={value.assignee}
|
value={object.assignee}
|
||||||
{prevAssigned}
|
{prevAssigned}
|
||||||
{projectLead}
|
{componentLead}
|
||||||
{projectMembers}
|
|
||||||
{members}
|
{members}
|
||||||
titleDeselect={tracker.string.Unassigned}
|
titleDeselect={tracker.string.Unassigned}
|
||||||
{size}
|
{size}
|
||||||
@ -101,9 +102,15 @@
|
|||||||
{avatarSize}
|
{avatarSize}
|
||||||
{width}
|
{width}
|
||||||
{short}
|
{short}
|
||||||
|
{shouldShowName}
|
||||||
showNavigate={false}
|
showNavigate={false}
|
||||||
justify={'left'}
|
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)}
|
on:change={({ detail }) => handleAssigneeChanged(detail)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/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'} />
|
<StatusEditor value={issue} shouldShowLabel kind={'secondary'} />
|
||||||
<PriorityEditor value={issue} shouldShowLabel kind={'secondary'} />
|
<PriorityEditor value={issue} shouldShowLabel kind={'secondary'} />
|
||||||
{#if issue.assignee}
|
{#if issue.assignee}
|
||||||
<AssigneeEditor value={issue} width={'min-content'} kind={'secondary'} />
|
<AssigneeEditor object={issue} kind={'secondary'} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachmentsPresenter } from '@hcengineering/attachment-resources'
|
import { AttachmentsPresenter } from '@hcengineering/attachment-resources'
|
||||||
import { CommentsPresenter } from '@hcengineering/chunter-resources'
|
import { CommentsPresenter } from '@hcengineering/chunter-resources'
|
||||||
import contact from '@hcengineering/contact'
|
|
||||||
import { employeeByIdStore } from '@hcengineering/contact-resources'
|
|
||||||
import {
|
import {
|
||||||
CategoryType,
|
CategoryType,
|
||||||
Class,
|
Class,
|
||||||
@ -77,7 +75,7 @@
|
|||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import ComponentEditor from '../components/ComponentEditor.svelte'
|
import ComponentEditor from '../components/ComponentEditor.svelte'
|
||||||
import CreateIssue from '../CreateIssue.svelte'
|
import CreateIssue from '../CreateIssue.svelte'
|
||||||
import AssigneePresenter from './AssigneePresenter.svelte'
|
import AssigneeEditor from './AssigneeEditor.svelte'
|
||||||
import DueDatePresenter from './DueDatePresenter.svelte'
|
import DueDatePresenter from './DueDatePresenter.svelte'
|
||||||
import SubIssuesSelector from './edit/SubIssuesSelector.svelte'
|
import SubIssuesSelector from './edit/SubIssuesSelector.svelte'
|
||||||
import IssuePresenter from './IssuePresenter.svelte'
|
import IssuePresenter from './IssuePresenter.svelte'
|
||||||
@ -366,13 +364,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-row-center gap-2 reverse flex-no-shrink">
|
<div class="flex-row-center gap-2 reverse flex-no-shrink">
|
||||||
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
|
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
|
||||||
<AssigneePresenter
|
<AssigneeEditor object={issue} avatarSize={'card'} shouldShowName={false} />
|
||||||
value={issue.assignee ? $employeeByIdStore.get(issue.assignee) : null}
|
|
||||||
defaultClass={contact.class.Employee}
|
|
||||||
object={issue}
|
|
||||||
isEditable={true}
|
|
||||||
avatarSize={'card'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content text-md caption-color lines-limit-2">
|
<div class="card-content text-md caption-color lines-limit-2">
|
||||||
|
@ -161,7 +161,7 @@
|
|||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Assignee} />
|
<Label label={tracker.string.Assignee} />
|
||||||
</span>
|
</span>
|
||||||
<AssigneeEditor value={issue} size={'medium'} avatarSize={'card'} />
|
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" />
|
||||||
|
|
||||||
<span class="labelTop">
|
<span class="labelTop">
|
||||||
<Label label={tracker.string.Labels} />
|
<Label label={tracker.string.Labels} />
|
||||||
|
@ -176,10 +176,9 @@
|
|||||||
on:change={({ detail }) => (object.priority = detail)}
|
on:change={({ detail }) => (object.priority = detail)}
|
||||||
/>
|
/>
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
value={object}
|
object={{ ...object, space }}
|
||||||
kind={'secondary'}
|
kind={'secondary'}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
width={'min-content'}
|
|
||||||
on:change={({ detail }) => (object.assignee = detail)}
|
on:change={({ detail }) => (object.assignee = detail)}
|
||||||
/>
|
/>
|
||||||
<Component
|
<Component
|
||||||
|
@ -210,7 +210,7 @@
|
|||||||
<div id="sub-issue-assignee-editor">
|
<div id="sub-issue-assignee-editor">
|
||||||
{#key object.assignee}
|
{#key object.assignee}
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
value={object}
|
{object}
|
||||||
size="small"
|
size="small"
|
||||||
kind="no-border"
|
kind="no-border"
|
||||||
on:change={({ detail }) => (object.assignee = detail)}
|
on:change={({ detail }) => (object.assignee = detail)}
|
||||||
|
@ -163,7 +163,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
value={issue}
|
object={issue}
|
||||||
on:change={(evt) => {
|
on:change={(evt) => {
|
||||||
dispatch('update-issue', { id: issue._id, assignee: evt.detail })
|
dispatch('update-issue', { id: issue._id, assignee: evt.detail })
|
||||||
issue.assignee = evt.detail
|
issue.assignee = evt.detail
|
||||||
|
@ -17,7 +17,13 @@
|
|||||||
import presentation, { createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||||
import { StyledTextArea } from '@hcengineering/text-editor'
|
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 { Button, Component, EditBox } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
@ -25,6 +31,7 @@
|
|||||||
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
import PriorityEditor from '../issues/PriorityEditor.svelte'
|
||||||
import EstimationEditor from './EstimationEditor.svelte'
|
import EstimationEditor from './EstimationEditor.svelte'
|
||||||
|
|
||||||
|
export let projectId: Ref<Project>
|
||||||
export let milestone: Ref<Milestone> | null = null
|
export let milestone: Ref<Milestone> | null = null
|
||||||
export let component: Ref<ComponentType> | null = null
|
export let component: Ref<ComponentType> | null = null
|
||||||
export let childIssue: IssueTemplateChild | undefined = undefined
|
export let childIssue: IssueTemplateChild | undefined = undefined
|
||||||
@ -142,7 +149,7 @@
|
|||||||
/>
|
/>
|
||||||
{#key newIssue.assignee}
|
{#key newIssue.assignee}
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
value={newIssue}
|
object={{ ...newIssue, space: projectId }}
|
||||||
size="small"
|
size="small"
|
||||||
kind="no-border"
|
kind="no-border"
|
||||||
width="auto"
|
width="auto"
|
||||||
|
@ -158,7 +158,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AssigneeEditor
|
<AssigneeEditor
|
||||||
value={issue}
|
object={{ ...issue, space: project }}
|
||||||
on:change={(evt) => {
|
on:change={(evt) => {
|
||||||
dispatch('update-issue', { id: issue.id, assignee: evt.detail })
|
dispatch('update-issue', { id: issue.id, assignee: evt.detail })
|
||||||
issue.assignee = evt.detail
|
issue.assignee = evt.detail
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
{#if isCreating}
|
{#if isCreating}
|
||||||
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
|
||||||
<IssueTemplateChildEditor
|
<IssueTemplateChildEditor
|
||||||
|
projectId={project}
|
||||||
{component}
|
{component}
|
||||||
{milestone}
|
{milestone}
|
||||||
{isScrollable}
|
{isScrollable}
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Assignee} />
|
<Label label={tracker.string.Assignee} />
|
||||||
</span>
|
</span>
|
||||||
<AssigneeEditor value={issue} size={'medium'} />
|
<AssigneeEditor object={issue} size={'medium'} width="100%" />
|
||||||
|
|
||||||
<span class="labelTop">
|
<span class="labelTop">
|
||||||
<Label label={tracker.string.Labels} />
|
<Label label={tracker.string.Labels} />
|
||||||
|
@ -37,7 +37,7 @@ import LeadPresenter from './components/components/LeadPresenter.svelte'
|
|||||||
import ProjectComponents from './components/components/ProjectComponents.svelte'
|
import ProjectComponents from './components/components/ProjectComponents.svelte'
|
||||||
import CreateIssue from './components/CreateIssue.svelte'
|
import CreateIssue from './components/CreateIssue.svelte'
|
||||||
import Inbox from './components/inbox/Inbox.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 DueDatePresenter from './components/issues/DueDatePresenter.svelte'
|
||||||
import EditIssue from './components/issues/edit/EditIssue.svelte'
|
import EditIssue from './components/issues/edit/EditIssue.svelte'
|
||||||
import IssueItem from './components/issues/IssueItem.svelte'
|
import IssueItem from './components/issues/IssueItem.svelte'
|
||||||
@ -395,7 +395,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
ComponentEditor,
|
ComponentEditor,
|
||||||
StatusPresenter,
|
StatusPresenter,
|
||||||
StatusEditor,
|
StatusEditor,
|
||||||
AssigneePresenter,
|
AssigneeEditor,
|
||||||
DueDatePresenter,
|
DueDatePresenter,
|
||||||
EditIssue,
|
EditIssue,
|
||||||
NewIssueHeader,
|
NewIssueHeader,
|
||||||
|
@ -326,7 +326,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
StatusPresenter: '' as AnyComponent,
|
StatusPresenter: '' as AnyComponent,
|
||||||
StatusRefPresenter: '' as AnyComponent,
|
StatusRefPresenter: '' as AnyComponent,
|
||||||
StatusEditor: '' as AnyComponent,
|
StatusEditor: '' as AnyComponent,
|
||||||
AssigneePresenter: '' as AnyComponent,
|
AssigneeEditor: '' as AnyComponent,
|
||||||
DueDatePresenter: '' as AnyComponent,
|
DueDatePresenter: '' as AnyComponent,
|
||||||
EditIssueTemplate: '' as AnyComponent,
|
EditIssueTemplate: '' as AnyComponent,
|
||||||
CreateProject: '' as AnyComponent,
|
CreateProject: '' as AnyComponent,
|
||||||
|
@ -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) => {
|
return await new Promise((resolve) => {
|
||||||
const query = createQuery(true)
|
const query = createQuery(true)
|
||||||
query.query(
|
query.query(
|
||||||
core.class.Tx,
|
core.class.Tx,
|
||||||
{
|
{
|
||||||
'tx.objectId': issue._id,
|
'tx.objectId': objectId,
|
||||||
'tx.operations.assignee': { $exists: true }
|
'tx.operations.assignee': { $exists: true }
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user