Allow to do departament d&d (#2194)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-07-02 23:55:15 +07:00 committed by GitHub
parent 808ea852a4
commit a4b3cb4a44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 61 deletions

View File

@ -333,6 +333,29 @@ export function createModel (builder: Builder): void {
},
hr.viewlet.TableMember
)
createAction(builder, {
action: view.actionImpl.ValueSelector,
actionPopup: view.component.ValueSelector,
actionProps: {
attribute: 'department',
_class: hr.class.Department,
query: {},
searchField: 'name',
placeholder: hr.string.Department
},
label: hr.string.Department,
icon: hr.icon.Department,
keyBinding: [],
input: 'none',
category: hr.category.HR,
target: hr.mixin.Staff,
context: {
mode: ['context'],
application: hr.app.HR,
group: 'associate'
}
})
}
export { hrOperation } from './migration'

View File

@ -19,6 +19,7 @@
import presentation from '..'
export let label: IntlString
export let labelProps: IntlString
export let message: IntlString
export let params: Record<string, any> = {}
export let canSubmit = true
@ -27,7 +28,7 @@
</script>
<div class="msgbox-container">
<div class="overflow-label fs-title mb-4"><Label {label} /></div>
<div class="overflow-label fs-title mb-4"><Label {label} params={labelProps ?? {}} /></div>
<div class="message"><Label label={message} {params} /></div>
<div class="footer">
<Button

View File

@ -18,10 +18,11 @@
import { Ref, WithLookup } from '@anticrm/core'
import { Department, Staff } from '@anticrm/hr'
import { Avatar, getClient, UsersPopup } from '@anticrm/presentation'
import { Button, eventToHTMLElement, IconAdd, Label, showPanel, showPopup } from '@anticrm/ui'
import { Button, closeTooltip, eventToHTMLElement, IconAdd, Label, showPanel, showPopup } from '@anticrm/ui'
import view from '@anticrm/view'
import { Menu } from '@anticrm/view-resources'
import hr from '../plugin'
import { addMember } from '../utils'
import CreateDepartment from './CreateDepartment.svelte'
import DepartmentCard from './DepartmentCard.svelte'
import PersonsPresenter from './PersonsPresenter.svelte'
@ -82,7 +83,14 @@
showPanel(view.component.EditDoc, value._id, value._class, 'content')
}
$: values = allEmployees.filter((it) => it.department === value._id)
export let dragPerson: WithLookup<Staff> | undefined
export let dragOver: Department | undefined
$: dragPersonId = dragPerson?._id
$: values = allEmployees.filter((it) => it.department === value._id && it._id !== dragPersonId)
$: dragging = value._id === dragOver?._id && dragPersonId !== undefined
</script>
<div class="flex-center w-full px-4">
@ -91,8 +99,25 @@
class:cursor-pointer={currentDescendants.length}
on:click|stopPropagation={edit}
on:contextmenu|preventDefault={showMenu}
class:dragging
>
<div
class="flex-between pt-4 pb-4 pr-4 pl-2 w-full"
on:dragover|preventDefault|stopPropagation={(evt) => {
dragOver = value
}}
on:dragend|preventDefault|stopPropagation={() => {
dragPerson = undefined
closeTooltip()
}}
on:drop|preventDefault={(itm) => {
closeTooltip()
addMember(client, dragPerson, value).then(() => {
dragPerson = undefined
dragOver = undefined
})
}}
>
<div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full">
<div class="flex-center">
<div class="mr-2">
<Button icon={IconAdd} kind={'link-bordered'} on:click={createChild} />
@ -104,7 +129,7 @@
</div>
<Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
</div>
<PersonsPresenter value={values} />
<PersonsPresenter value={values} bind:dragPerson showDragPerson={dragging} />
</div>
<div class="flex-center mr-2">
<div class="mr-2">
@ -127,17 +152,21 @@
</div>
<div class="ml-8">
{#each currentDescendants as nested}
<DepartmentCard value={nested} {descendants} {allEmployees} />
<DepartmentCard value={nested} {descendants} {allEmployees} bind:dragPerson bind:dragOver />
{/each}
</div>
<style lang="scss">
.container {
background-color: var(--board-card-bg-color);
border: 1px solid transparent;
&:hover {
background-color: var(--board-card-bg-hover);
cursor: pointer;
}
&.dragging {
border: 1px solid var(--theme-bg-focused-color);
}
}
</style>

View File

@ -13,15 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { Ref, WithLookup } from '@anticrm/core'
import { Department, Staff } from '@anticrm/hr'
import { createQuery, getClient, MessageBox, UsersPopup } from '@anticrm/presentation'
import { createQuery, getClient, UsersPopup } from '@anticrm/presentation'
import { Button, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@anticrm/ui'
import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { Table, ViewletSettingButton } from '@anticrm/view-resources'
import hr from '../plugin'
import { addMember } from '../utils'
export let objectId: Ref<Department> | undefined
let value: Department | undefined
@ -50,7 +50,7 @@
ignoreUsers: memberItems.map((it) => it._id)
},
eventToHTMLElement(e),
addMember
(res) => addMember(client, res, value)
)
}
@ -61,50 +61,6 @@
memberItems = result
})
async function addMember (employee: Employee | undefined): Promise<void> {
if (employee === null || employee === undefined || value === undefined) {
return
}
const hierarchy = client.getHierarchy()
if (!hierarchy.hasMixin(employee, hr.mixin.Staff)) {
await client.createMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
} else {
const staff = hierarchy.as(employee, hr.mixin.Staff)
if (staff.department === value._id) return
const current = await client.findOne(hr.class.Department, {
_id: staff.department
})
if (current !== undefined) {
showPopup(
MessageBox,
{
label: hr.string.MoveStaff,
message: hr.string.MoveStaffDescr,
params: {
current: current.name,
department: value.name
}
},
undefined,
async (res?: boolean) => {
if (res === true && value !== undefined) {
await client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
}
)
} else {
await client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
}
}
const preferenceQuery = createQuery()
let preference: ViewletPreference | undefined
let loading = false

View File

@ -18,21 +18,49 @@
import { EmployeePresenter } from '@anticrm/contact-resources'
import { WithLookup } from '@anticrm/core'
import { Staff } from '@anticrm/hr'
import { closeTooltip, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import hr from '../plugin'
import { flip } from 'svelte/animate'
export let value: WithLookup<Staff> | WithLookup<Staff>[]
export let inline: boolean = false
export let dragPerson: WithLookup<Staff> | undefined
export let showDragPerson: boolean = false
let persons: WithLookup<Employee>[] = []
$: persons = Array.isArray(value) ? value : [value]
function ondrag (p: Employee): void {
dragPerson = p as WithLookup<Staff>
}
function showContextMenu (ev: MouseEvent, object: Employee) {
showPopup(
ContextMenu,
{ object },
{ getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY }) }
)
}
</script>
{#if value}
<div class="flex persons">
{#each persons as p}
<div class="ml-2 hover-trans">
{#each persons as p (p._id)}
<div
class="ml-2 hover-trans icon"
draggable={true}
animate:flip={{ duration: 200 }}
on:drag={() => ondrag(p)}
on:dragend|preventDefault|stopPropagation={() => {
dragPerson = undefined
closeTooltip()
}}
on:contextmenu|stopPropagation|preventDefault={(evt) => showContextMenu(evt, p)}
>
<EmployeePresenter
value={p}
avatarSize={'large'}
shouldShowName={false}
{inline}
tooltipLabels={{
@ -42,15 +70,28 @@
/>
</div>
{/each}
{#if showDragPerson && dragPerson !== undefined && persons.find((it) => it._id === dragPerson?._id) === undefined}
<EmployeePresenter
value={dragPerson}
avatarSize={'large'}
shouldShowName={false}
{inline}
tooltipLabels={{
personLabel: hr.string.TeamLeadTooltip,
placeholderLabel: hr.string.AssignLead
}}
/>
{/if}
</div>
{/if}
<style lang="scss">
.persons {
display: grid;
grid-template-columns: repeat(4, min-content);
display: flex;
flex-wrap: wrap;
.icon {
margin: 0.25rem;
margin: 0.15rem;
border-radius: 50%;
}
}
</style>

View File

@ -0,0 +1,54 @@
import { Employee, formatName } from '@anticrm/contact'
import { TxOperations } from '@anticrm/core'
import { Department } from '@anticrm/hr'
import { MessageBox } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import hr from './plugin'
export async function addMember (client: TxOperations, employee?: Employee, value?: Department): Promise<void> {
if (employee === null || employee === undefined || value === undefined) {
return
}
const hierarchy = client.getHierarchy()
if (!hierarchy.hasMixin(employee, hr.mixin.Staff)) {
await client.createMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
} else {
const staff = hierarchy.as(employee, hr.mixin.Staff)
if (staff.department === value._id) return
const current = await client.findOne(hr.class.Department, {
_id: staff.department
})
if (current !== undefined) {
await new Promise((resolve) => {
showPopup(
MessageBox,
{
label: hr.string.MoveStaff,
labelProps: { name: formatName(employee.name) },
message: hr.string.MoveStaffDescr,
params: {
current: current.name,
department: value.name
}
},
undefined,
(res?: boolean) => {
if (res === true && value !== undefined) {
void client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
resolve(null)
}
)
})
} else {
await client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
}
}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { Class, Doc, DocumentQuery, FindOptions, Hierarchy, Ref } from '@anticrm/core'
import { Asset, IntlString } from '@anticrm/platform'
import { getClient, ObjectPopup } from '@anticrm/presentation'
import { getClient, ObjectPopup, updateAttribute } from '@anticrm/presentation'
import { Label, SelectPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import view from '../plugin'
@ -41,7 +41,17 @@
const c = getClient()
const changed = (d: Doc) => (d as any)[attribute] !== newStatus
await Promise.all(docs.filter(changed).map((it) => c.update(it, { [attribute]: newStatus })))
await Promise.all(
docs.filter(changed).map((it) => {
// c.update(it, { [attribute]: newStatus } )
const cl = Hierarchy.mixinOrClass(it)
const attr = c.getHierarchy().getAttribute(cl, attribute)
if (attr === undefined) {
throw new Error('attribute not found')
}
return updateAttribute(c, it, cl, { key: attribute, attr }, newStatus)
})
)
dispatch('close', newStatus)
}