mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
Allow to do departament d&d (#2194)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
808ea852a4
commit
a4b3cb4a44
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
54
plugins/hr-resources/src/utils.ts
Normal file
54
plugins/hr-resources/src/utils.ts
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user