mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-26 02:10:07 +00:00
Hr ui fix (#2112)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
09130c5b69
commit
1091ced274
@ -34,6 +34,8 @@
|
|||||||
"@anticrm/model-view": "~0.6.0",
|
"@anticrm/model-view": "~0.6.0",
|
||||||
"@anticrm/model-workbench": "~0.6.1",
|
"@anticrm/model-workbench": "~0.6.1",
|
||||||
"@anticrm/model-contact": "~0.6.1",
|
"@anticrm/model-contact": "~0.6.1",
|
||||||
|
"@anticrm/model-chunter": "~0.6.0",
|
||||||
|
"@anticrm/model-attachment": "~0.6.0",
|
||||||
"@anticrm/hr": "~0.6.0",
|
"@anticrm/hr": "~0.6.0",
|
||||||
"@anticrm/hr-resources": "~0.6.0",
|
"@anticrm/hr-resources": "~0.6.0",
|
||||||
"@anticrm/view": "~0.6.0"
|
"@anticrm/view": "~0.6.0"
|
||||||
|
@ -14,14 +14,16 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Employee } from '@anticrm/contact'
|
import { Employee } from '@anticrm/contact'
|
||||||
import contact, { TEmployee } from '@anticrm/model-contact'
|
import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
|
||||||
import { IndexKind, Ref } from '@anticrm/core'
|
import { Arr, IndexKind, Ref } from '@anticrm/core'
|
||||||
import type { Department, Staff } from '@anticrm/hr'
|
import type { Department, DepartmentMember, Staff } from '@anticrm/hr'
|
||||||
import { Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
import { Builder, Index, Mixin, Model, Prop, TypeRef, Collection, TypeString, UX, ArrOf } from '@anticrm/model'
|
||||||
import core, { TSpace } from '@anticrm/model-core'
|
import core, { TSpace } from '@anticrm/model-core'
|
||||||
import workbench from '@anticrm/model-workbench'
|
import workbench from '@anticrm/model-workbench'
|
||||||
import hr from './plugin'
|
import hr from './plugin'
|
||||||
import view, { createAction } from '@anticrm/model-view'
|
import view, { createAction } from '@anticrm/model-view'
|
||||||
|
import attachment from '@anticrm/model-attachment'
|
||||||
|
import chunter from '@anticrm/model-chunter'
|
||||||
|
|
||||||
@Model(hr.class.Department, core.class.Space)
|
@Model(hr.class.Department, core.class.Space)
|
||||||
@UX(hr.string.Department, hr.icon.Department)
|
@UX(hr.string.Department, hr.icon.Department)
|
||||||
@ -33,12 +35,28 @@ export class TDepartment extends TSpace implements Department {
|
|||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
name!: string
|
name!: string
|
||||||
|
|
||||||
|
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
|
||||||
|
channels?: number
|
||||||
|
|
||||||
|
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, undefined, attachment.string.Files)
|
||||||
|
attachments?: number
|
||||||
|
|
||||||
|
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||||
|
comments?: number
|
||||||
|
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
|
|
||||||
@Prop(TypeRef(contact.class.Employee), hr.string.TeamLead)
|
@Prop(TypeRef(contact.class.Employee), hr.string.TeamLead)
|
||||||
teamLead!: Ref<Employee> | null
|
teamLead!: Ref<Employee> | null
|
||||||
|
|
||||||
|
@Prop(ArrOf(TypeRef(hr.class.DepartmentMember)), contact.string.Members)
|
||||||
|
declare members: Arr<Ref<DepartmentMember>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(hr.class.DepartmentMember, contact.class.EmployeeAccount)
|
||||||
|
@UX(contact.string.Employee, hr.icon.HR)
|
||||||
|
export class TDepartmentMember extends TEmployeeAccount implements DepartmentMember {}
|
||||||
|
|
||||||
@Mixin(hr.mixin.Staff, contact.class.Employee)
|
@Mixin(hr.mixin.Staff, contact.class.Employee)
|
||||||
@UX(contact.string.Employee, hr.icon.HR)
|
@UX(contact.string.Employee, hr.icon.HR)
|
||||||
export class TStaff extends TEmployee implements Staff {
|
export class TStaff extends TEmployee implements Staff {
|
||||||
@ -47,7 +65,7 @@ export class TStaff extends TEmployee implements Staff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TDepartment, TStaff)
|
builder.createModel(TDepartment, TDepartmentMember, TStaff)
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
workbench.class.Application,
|
workbench.class.Application,
|
||||||
@ -76,6 +94,14 @@ export function createModel (builder: Builder): void {
|
|||||||
inlineEditor: hr.component.DepartmentEditor
|
inlineEditor: hr.component.DepartmentEditor
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(hr.class.Department, core.class.Class, view.mixin.ObjectEditor, {
|
||||||
|
editor: hr.component.EditDepartment
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(hr.class.DepartmentMember, core.class.Class, view.mixin.ArrayEditor, {
|
||||||
|
editor: hr.component.DepartmentStaff
|
||||||
|
})
|
||||||
|
|
||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
|
@ -118,7 +118,8 @@ export class TCollectionEditor extends TClass implements CollectionEditor {
|
|||||||
|
|
||||||
@Mixin(view.mixin.ArrayEditor, core.class.Class)
|
@Mixin(view.mixin.ArrayEditor, core.class.Class)
|
||||||
export class TArrayEditor extends TClass implements ArrayEditor {
|
export class TArrayEditor extends TClass implements ArrayEditor {
|
||||||
inlineEditor!: AnyComponent
|
inlineEditor?: AnyComponent
|
||||||
|
editor?: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mixin(view.mixin.AttributePresenter, core.class.Class)
|
@Mixin(view.mixin.AttributePresenter, core.class.Class)
|
||||||
|
@ -55,6 +55,9 @@
|
|||||||
|
|
||||||
const typeClass = hierarchy.getClass(presenterClass.attrClass)
|
const typeClass = hierarchy.getClass(presenterClass.attrClass)
|
||||||
const editorMixin = hierarchy.as(typeClass, mixinRef)
|
const editorMixin = hierarchy.as(typeClass, mixinRef)
|
||||||
|
if (category === 'array' && editorMixin.inlineEditor === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
editor = getResource(editorMixin.inlineEditor).catch((cause) => {
|
editor = getResource(editorMixin.inlineEditor).catch((cause) => {
|
||||||
console.error(`failed to find editor for ${_class} ${attribute} ${presenterClass.attrClass} cause: ${cause}`)
|
console.error(`failed to find editor for ${_class} ${attribute} ${presenterClass.attrClass} cause: ${cause}`)
|
||||||
})
|
})
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"@anticrm/core": "~0.6.16",
|
"@anticrm/core": "~0.6.16",
|
||||||
"@anticrm/panel": "~0.6.0",
|
"@anticrm/panel": "~0.6.0",
|
||||||
"@anticrm/contact": "~0.6.5",
|
"@anticrm/contact": "~0.6.5",
|
||||||
|
"@anticrm/view": "~0.6.0",
|
||||||
"@anticrm/view-resources": "~0.6.0",
|
"@anticrm/view-resources": "~0.6.0",
|
||||||
"@anticrm/contact-resources": "~0.6.0",
|
"@anticrm/contact-resources": "~0.6.0",
|
||||||
"@anticrm/setting": "~0.6.1",
|
"@anticrm/setting": "~0.6.1",
|
||||||
|
@ -19,25 +19,19 @@
|
|||||||
import CreateDepartment from './CreateDepartment.svelte'
|
import CreateDepartment from './CreateDepartment.svelte'
|
||||||
import DepartmentCard from './DepartmentCard.svelte'
|
import DepartmentCard from './DepartmentCard.svelte'
|
||||||
import hr from '../plugin'
|
import hr from '../plugin'
|
||||||
import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, ActionIcon } from '@anticrm/ui'
|
import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, ActionIcon, showPanel } from '@anticrm/ui'
|
||||||
import contact, { Employee } from '@anticrm/contact'
|
import contact, { Employee } from '@anticrm/contact'
|
||||||
import { EmployeePresenter } from '@anticrm/contact-resources'
|
import { EmployeePresenter } from '@anticrm/contact-resources'
|
||||||
import DepartmentStaff from './DepartmentStaff.svelte'
|
|
||||||
import { Menu } from '@anticrm/view-resources'
|
import { Menu } from '@anticrm/view-resources'
|
||||||
|
import view from '@anticrm/view'
|
||||||
|
|
||||||
export let value: WithLookup<Department>
|
export let value: WithLookup<Department>
|
||||||
export let descendants: Map<Ref<Department>, WithLookup<Department>[]>
|
export let descendants: Map<Ref<Department>, WithLookup<Department>[]>
|
||||||
|
|
||||||
$: currentDescendants = descendants.get(value._id) ?? []
|
$: currentDescendants = descendants.get(value._id) ?? []
|
||||||
let expand = false
|
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
function toggle () {
|
|
||||||
if (currentDescendants.length === 0) return
|
|
||||||
expand = !expand
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changeLead (result: Employee | null | undefined): Promise<void> {
|
async function changeLead (result: Employee | null | undefined): Promise<void> {
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
return
|
return
|
||||||
@ -50,6 +44,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openLeadEditor (event: MouseEvent) {
|
function openLeadEditor (event: MouseEvent) {
|
||||||
|
event?.preventDefault()
|
||||||
|
event?.stopPropagation()
|
||||||
showPopup(
|
showPopup(
|
||||||
UsersPopup,
|
UsersPopup,
|
||||||
{
|
{
|
||||||
@ -64,13 +60,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createChild (e: MouseEvent) {
|
function createChild (e: MouseEvent) {
|
||||||
showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e), (res) => {
|
showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e))
|
||||||
if (res && !expand) expand = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function editMembers (e: MouseEvent) {
|
|
||||||
showPopup(DepartmentStaff, { _id: value._id }, 'float')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu (e: MouseEvent) {
|
function showMenu (e: MouseEvent) {
|
||||||
@ -82,29 +72,30 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function edit (e: MouseEvent): void {
|
||||||
|
showPanel(view.component.EditDoc, value._id, value._class, 'content')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-center w-full px-4">
|
<div class="flex-center w-full px-4">
|
||||||
<div
|
<div
|
||||||
class="w-full mt-2 mb-2 container flex"
|
class="w-full mt-2 mb-2 container flex"
|
||||||
class:cursor-pointer={currentDescendants.length}
|
class:cursor-pointer={currentDescendants.length}
|
||||||
on:click|stopPropagation={toggle}
|
on:click|stopPropagation={edit}
|
||||||
on:contextmenu|preventDefault={showMenu}
|
on:contextmenu|preventDefault={showMenu}
|
||||||
>
|
>
|
||||||
{#if currentDescendants.length}
|
|
||||||
<div class="verticalDivider" />
|
|
||||||
<div class="verticalDivider" />
|
|
||||||
{/if}
|
|
||||||
<div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full">
|
<div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full">
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
|
<div class="mr-2">
|
||||||
|
<Button icon={IconAdd} on:click={createChild} />
|
||||||
|
</div>
|
||||||
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} />
|
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} />
|
||||||
<div class="flex-row ml-2">
|
<div class="flex-row ml-2">
|
||||||
<div class="fs-title">
|
<div class="fs-title">
|
||||||
{value.name}
|
{value.name}
|
||||||
</div>
|
</div>
|
||||||
<div class="cursor-pointer" on:click|stopPropagation={editMembers}>
|
<Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
|
||||||
<Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-center mr-2">
|
<div class="flex-center mr-2">
|
||||||
@ -122,19 +113,16 @@
|
|||||||
onEmployeeEdit={openLeadEditor}
|
onEmployeeEdit={openLeadEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button icon={IconAdd} on:click={createChild} />
|
|
||||||
<ActionIcon icon={IconMoreV} size={'medium'} action={showMenu} />
|
<ActionIcon icon={IconMoreV} size={'medium'} action={showMenu} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if expand && currentDescendants.length}
|
<div class="ml-8">
|
||||||
<div class="ml-8">
|
{#each currentDescendants as nested}
|
||||||
{#each descendants.get(value._id) ?? [] as nested}
|
<DepartmentCard value={nested} {descendants} />
|
||||||
<DepartmentCard value={nested} {descendants} />
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
import hr from '../plugin'
|
import hr from '../plugin'
|
||||||
|
|
||||||
export let value: Ref<Department> | undefined
|
export let value: Ref<Department> | undefined
|
||||||
export let label: IntlString = hr.string.Department
|
export let label: IntlString = hr.string.ParentDepartmentLabel
|
||||||
export let onChange: (value: any) => void
|
export let onChange: (value: any) => void
|
||||||
export let kind: ButtonKind = 'no-border'
|
export let kind: ButtonKind = 'no-border'
|
||||||
export let size: ButtonSize = 'small'
|
export let size: ButtonSize = 'small'
|
||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<SpaceSelector
|
<SpaceSelector
|
||||||
_class={hr.class.Department}
|
_class={hr.class.Department}
|
||||||
label={hr.string.ParentDepartmentLabel}
|
{label}
|
||||||
{size}
|
{size}
|
||||||
{kind}
|
{kind}
|
||||||
{justify}
|
{justify}
|
||||||
|
@ -13,33 +13,31 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
import { Employee } from '@anticrm/contact'
|
||||||
|
import { EmployeePresenter } from '@anticrm/contact-resources'
|
||||||
|
import contact from '@anticrm/contact-resources/src/plugin'
|
||||||
import { Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
import { Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||||
import { Department, Staff } from '@anticrm/hr'
|
import { Department, DepartmentMember, Staff } from '@anticrm/hr'
|
||||||
import { Avatar, createQuery, MessageBox, getClient, UsersPopup } from '@anticrm/presentation'
|
import { createQuery, getClient, MessageBox, UsersPopup } from '@anticrm/presentation'
|
||||||
import { Scroller, Panel, Button, showPopup, eventToHTMLElement } from '@anticrm/ui'
|
import { CircleButton, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import StaffPresenter from './StaffPresenter.svelte'
|
|
||||||
import hr from '../plugin'
|
import hr from '../plugin'
|
||||||
|
|
||||||
export let _id: Ref<Department> | undefined
|
export let objectId: Ref<Department> | undefined
|
||||||
let value: Department | undefined
|
let value: Department | undefined
|
||||||
let employees: WithLookup<Staff>[] = []
|
let employees: WithLookup<Staff>[] = []
|
||||||
let accounts: EmployeeAccount[] = []
|
let accounts: DepartmentMember[] = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
const departmentQuery = createQuery()
|
const departmentQuery = createQuery()
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
const accountsQuery = createQuery()
|
const accountsQuery = createQuery()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
$: _id &&
|
$: objectId &&
|
||||||
value === undefined &&
|
value === undefined &&
|
||||||
departmentQuery.query(
|
departmentQuery.query(
|
||||||
hr.class.Department,
|
hr.class.Department,
|
||||||
{
|
{
|
||||||
_id
|
_id: objectId
|
||||||
},
|
},
|
||||||
(res) => ([value] = res)
|
(res) => ([value] = res)
|
||||||
)
|
)
|
||||||
@ -48,7 +46,7 @@
|
|||||||
accountsQuery.query(
|
accountsQuery.query(
|
||||||
contact.class.EmployeeAccount,
|
contact.class.EmployeeAccount,
|
||||||
{
|
{
|
||||||
_id: { $in: value.members as Ref<EmployeeAccount>[] }
|
_id: { $in: value.members }
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
accounts = res
|
accounts = res
|
||||||
@ -79,7 +77,7 @@
|
|||||||
UsersPopup,
|
UsersPopup,
|
||||||
{
|
{
|
||||||
_class: contact.class.Employee,
|
_class: contact.class.Employee,
|
||||||
ignoreUsers: employees.filter((p) => p.department === _id).map((p) => p._id)
|
ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id)
|
||||||
},
|
},
|
||||||
eventToHTMLElement(e),
|
eventToHTMLElement(e),
|
||||||
addMember
|
addMember
|
||||||
@ -87,7 +85,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addMember (employee: Employee | undefined): Promise<void> {
|
async function addMember (employee: Employee | undefined): Promise<void> {
|
||||||
if (employee === undefined || value === undefined) {
|
if (employee === null || employee === undefined || value === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,32 +129,64 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel
|
<div class="container">
|
||||||
isHeader={true}
|
<div class="flex flex-between">
|
||||||
isAside={false}
|
<div class="title"><Label label={contact.string.Members} /></div>
|
||||||
isFullSize
|
<CircleButton id={hr.string.AddEmployee} icon={IconAdd} size={'small'} selected on:click={add} />
|
||||||
on:fullsize
|
</div>
|
||||||
on:close={() => {
|
{#if employees.length > 0}
|
||||||
dispatch('close')
|
<Scroller>
|
||||||
}}
|
<table class="antiTable">
|
||||||
>
|
<thead class="scroller-thead">
|
||||||
<svelte:fragment slot="title">
|
<tr class="scroller-thead__tr">
|
||||||
<div class="antiTitle icon-wrapper">
|
<th><Label label={contact.string.Member} /></th>
|
||||||
{#if value}
|
<th><Label label={hr.string.Department} /></th>
|
||||||
<div class="wrapped-icon"><Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} /></div>
|
</tr>
|
||||||
<div class="title-wrapper">
|
</thead>
|
||||||
<span class="wrapped-title">{value.name}</span>
|
<tbody>
|
||||||
</div>
|
{#each employees as value}
|
||||||
{/if}
|
<tr class="antiTable-body__row">
|
||||||
|
<td><EmployeePresenter {value} /></td>
|
||||||
|
<td>
|
||||||
|
{#if value.$lookup?.department}
|
||||||
|
{value.$lookup.department.name}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Scroller>
|
||||||
|
{:else}
|
||||||
|
<div class="flex-col-center mt-5 create-container">
|
||||||
|
<div class="text-sm content-dark-color mt-2">
|
||||||
|
<Label label={contact.string.NoMembers} />
|
||||||
|
</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<div class="over-underline" on:click={add}><Label label={contact.string.AddMember} /></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
{/if}
|
||||||
<svelte:fragment slot="utils">
|
</div>
|
||||||
<Button label={hr.string.AddEmployee} kind={'primary'} on:click={add} />
|
|
||||||
</svelte:fragment>
|
|
||||||
|
|
||||||
<Scroller>
|
<style lang="scss">
|
||||||
{#each employees as value}
|
.container {
|
||||||
<StaffPresenter {value} />
|
display: flex;
|
||||||
{/each}
|
flex-direction: column;
|
||||||
</Scroller>
|
|
||||||
</Panel>
|
.title {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-container {
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
background: var(--theme-bg-accent-color);
|
||||||
|
border: 1px solid var(--theme-bg-accent-color);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -14,11 +14,9 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
|
import { createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
|
||||||
import { Panel } from '@anticrm/panel'
|
|
||||||
import { createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
|
import { createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
|
||||||
|
|
||||||
import { ActionContext } from '@anticrm/view-resources'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { Department } from '@anticrm/hr'
|
import { Department } from '@anticrm/hr'
|
||||||
import core, { getCurrentAccount, Ref, Space } from '@anticrm/core'
|
import core, { getCurrentAccount, Ref, Space } from '@anticrm/core'
|
||||||
import hr from '../plugin'
|
import hr from '../plugin'
|
||||||
@ -27,20 +25,10 @@
|
|||||||
import { ChannelsEditor } from '@anticrm/contact-resources'
|
import { ChannelsEditor } from '@anticrm/contact-resources'
|
||||||
import setting, { IntegrationType } from '@anticrm/setting'
|
import setting, { IntegrationType } from '@anticrm/setting'
|
||||||
|
|
||||||
export let _id: Ref<Department>
|
export let object: Department
|
||||||
let object: Department | undefined
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const query = createQuery()
|
|
||||||
query.query(
|
|
||||||
hr.class.Department,
|
|
||||||
{ _id },
|
|
||||||
(res) => {
|
|
||||||
object = res[0]
|
|
||||||
},
|
|
||||||
{ limit: 1 }
|
|
||||||
)
|
|
||||||
|
|
||||||
async function onAvatarDone (e: any) {
|
async function onAvatarDone (e: any) {
|
||||||
if (object === undefined) return
|
if (object === undefined) return
|
||||||
@ -77,10 +65,6 @@
|
|||||||
|
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
|
|
||||||
const _update = (result: any): void => {
|
|
||||||
dispatch('update', result)
|
|
||||||
}
|
|
||||||
|
|
||||||
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
|
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
|
||||||
const accountId = getCurrentAccount()._id
|
const accountId = getCurrentAccount()._id
|
||||||
const settingsQuery = createQuery()
|
const settingsQuery = createQuery()
|
||||||
@ -91,63 +75,46 @@
|
|||||||
integrations = new Set(res.map((p) => p.type))
|
integrations = new Set(res.map((p) => p.type))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
|
||||||
|
|
||||||
<ActionContext
|
onMount(() => {
|
||||||
context={{
|
dispatch('open', {
|
||||||
mode: 'editor'
|
ignoreKeys: ['comments', 'name', 'channels', 'private', 'archived'],
|
||||||
}}
|
collectionArrays: ['members']
|
||||||
/>
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<FocusHandler {manager} />
|
<FocusHandler {manager} />
|
||||||
|
|
||||||
{#if object !== undefined}
|
{#if object !== undefined}
|
||||||
<Panel
|
<div class="flex-row-stretch flex-grow">
|
||||||
icon={hr.icon.Department}
|
<div class="mr-8">
|
||||||
title={object.name}
|
{#key object}
|
||||||
{object}
|
<EditableAvatar
|
||||||
isHeader={false}
|
avatar={object.avatar}
|
||||||
isAside={true}
|
size={'x-large'}
|
||||||
on:update={(ev) => _update(ev.detail)}
|
icon={hr.icon.Department}
|
||||||
on:close={() => {
|
on:done={onAvatarDone}
|
||||||
dispatch('close')
|
on:remove={removeAvatar}
|
||||||
}}
|
/>
|
||||||
>
|
{/key}
|
||||||
<div class="flex-row-stretch flex-grow">
|
</div>
|
||||||
<div class="mr-8">
|
<div class="flex-grow flex-col">
|
||||||
{#key object}
|
<div class="name">
|
||||||
<EditableAvatar
|
<EditBox
|
||||||
avatar={object.avatar}
|
placeholder={core.string.Name}
|
||||||
size={'x-large'}
|
maxWidth="20rem"
|
||||||
icon={hr.icon.Department}
|
bind:value={object.name}
|
||||||
on:done={onAvatarDone}
|
on:change={nameChange}
|
||||||
on:remove={removeAvatar}
|
focusIndex={1}
|
||||||
/>
|
/>
|
||||||
{/key}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow flex-col">
|
<div class="separator" />
|
||||||
<div class="name">
|
<div class="flex-row-center">
|
||||||
<EditBox
|
<ChannelsEditor attachedTo={object._id} attachedClass={object._class} {integrations} focusIndex={10} on:click />
|
||||||
placeholder={core.string.Name}
|
|
||||||
maxWidth="20rem"
|
|
||||||
bind:value={object.name}
|
|
||||||
on:change={nameChange}
|
|
||||||
focusIndex={1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="separator" />
|
|
||||||
<div class="flex-row-center">
|
|
||||||
<ChannelsEditor
|
|
||||||
attachedTo={object._id}
|
|
||||||
attachedClass={object._class}
|
|
||||||
{integrations}
|
|
||||||
focusIndex={10}
|
|
||||||
on:click
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -1,36 +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 { formatName } from '@anticrm/contact'
|
|
||||||
import { WithLookup } from '@anticrm/core'
|
|
||||||
import { Staff } from '@anticrm/hr'
|
|
||||||
import { Avatar } from '@anticrm/presentation'
|
|
||||||
|
|
||||||
export let value: WithLookup<Staff>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex-between w-full p-4">
|
|
||||||
<div class="flex-row-center">
|
|
||||||
<Avatar avatar={value.avatar} size={'medium'} />
|
|
||||||
<div class="fs-title ml-2">
|
|
||||||
{formatName(value.name)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{#if value.$lookup?.department}
|
|
||||||
{value.$lookup.department.name}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Employee } from '@anticrm/contact'
|
import type { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||||
import type { Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
|
import type { Arr, Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
|
||||||
import type { Asset, Plugin } from '@anticrm/platform'
|
import type { Asset, Plugin } from '@anticrm/platform'
|
||||||
import { plugin } from '@anticrm/platform'
|
import { plugin } from '@anticrm/platform'
|
||||||
|
|
||||||
@ -25,8 +25,17 @@ export interface Department extends Space {
|
|||||||
space: Ref<Department>
|
space: Ref<Department>
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
teamLead: Ref<Employee> | null
|
teamLead: Ref<Employee> | null
|
||||||
|
attachments?: number
|
||||||
|
comments?: number
|
||||||
|
channels?: number
|
||||||
|
members: Arr<Ref<DepartmentMember>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface DepartmentMember extends EmployeeAccount {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -47,7 +56,8 @@ const hr = plugin(hrId, {
|
|||||||
HR: '' as Ref<Doc>
|
HR: '' as Ref<Doc>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
Department: '' as Ref<Class<Department>>
|
Department: '' as Ref<Class<Department>>,
|
||||||
|
DepartmentMember: '' as Ref<Class<DepartmentMember>>
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
Staff: '' as Ref<Mixin<Staff>>
|
Staff: '' as Ref<Mixin<Staff>>
|
||||||
|
@ -22,23 +22,7 @@
|
|||||||
export let allowedCollections: string[] = []
|
export let allowedCollections: string[] = []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ClassAttributeBar
|
<ClassAttributeBar _class={object._class} {object} {ignoreKeys} to={undefined} {allowedCollections} on:update />
|
||||||
_class={object._class}
|
|
||||||
{object}
|
|
||||||
{ignoreKeys}
|
|
||||||
to={undefined}
|
|
||||||
{allowedCollections}
|
|
||||||
vertical
|
|
||||||
on:update
|
|
||||||
/>
|
|
||||||
{#each mixins as mixin}
|
{#each mixins as mixin}
|
||||||
<ClassAttributeBar
|
<ClassAttributeBar _class={mixin._id} {object} {ignoreKeys} to={object._class} {allowedCollections} on:update />
|
||||||
_class={mixin._id}
|
|
||||||
{object}
|
|
||||||
{ignoreKeys}
|
|
||||||
to={object._class}
|
|
||||||
{allowedCollections}
|
|
||||||
vertical
|
|
||||||
on:update
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -91,6 +91,7 @@
|
|||||||
|
|
||||||
let ignoreKeys: string[] = []
|
let ignoreKeys: string[] = []
|
||||||
let allowedCollections: string[] = []
|
let allowedCollections: string[] = []
|
||||||
|
let collectionArrays: string[] = []
|
||||||
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||||
|
|
||||||
async function updateKeys (): Promise<void> {
|
async function updateKeys (): Promise<void> {
|
||||||
@ -102,12 +103,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const filtredKeys = Array.from(keysMap.values())
|
const filtredKeys = Array.from(keysMap.values())
|
||||||
keys = collectionsFilter(hierarchy, filtredKeys, false)
|
keys = collectionsFilter(hierarchy, filtredKeys, false, allowedCollections)
|
||||||
|
|
||||||
const collectionKeys = collectionsFilter(hierarchy, filtredKeys, true)
|
const collectionKeys = collectionsFilter(hierarchy, filtredKeys, true, collectionArrays)
|
||||||
const editors: { key: KeyedAttribute; editor: AnyComponent }[] = []
|
const editors: { key: KeyedAttribute; editor: AnyComponent }[] = []
|
||||||
for (const k of collectionKeys) {
|
for (const k of collectionKeys) {
|
||||||
|
if (allowedCollections.includes(k.key)) continue
|
||||||
const editor = await getCollectionEditor(k)
|
const editor = await getCollectionEditor(k)
|
||||||
|
if (editor === undefined) continue
|
||||||
editors.push({ key: k, editor })
|
editors.push({ key: k, editor })
|
||||||
}
|
}
|
||||||
collectionEditors = editors
|
collectionEditors = editors
|
||||||
@ -129,10 +132,11 @@
|
|||||||
updateKeys()
|
updateKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
|
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent | undefined> {
|
||||||
const attrClass = getAttributePresenterClass(hierarchy, key.attr)
|
const attrClass = getAttributePresenterClass(hierarchy, key.attr)
|
||||||
const clazz = hierarchy.getClass(attrClass.attrClass)
|
const clazz = hierarchy.getClass(attrClass.attrClass)
|
||||||
const editorMixin = hierarchy.as(clazz, view.mixin.CollectionEditor)
|
const mixinRef = attrClass.category === 'array' ? view.mixin.ArrayEditor : view.mixin.CollectionEditor
|
||||||
|
const editorMixin = hierarchy.as(clazz, mixinRef)
|
||||||
return editorMixin.editor
|
return editorMixin.editor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +247,13 @@
|
|||||||
on:update={updateKeys}
|
on:update={updateKeys}
|
||||||
/>
|
/>
|
||||||
{:else if dir === 'column'}
|
{:else if dir === 'column'}
|
||||||
<DocAttributeBar {object} {mixins} {ignoreKeys} {allowedCollections} on:update={updateKeys} />
|
<DocAttributeBar
|
||||||
|
{object}
|
||||||
|
{mixins}
|
||||||
|
ignoreKeys={[...ignoreKeys, ...collectionArrays]}
|
||||||
|
{allowedCollections}
|
||||||
|
on:update={updateKeys}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<AttributesBar {object} _class={realObjectClass} {keys} />
|
<AttributesBar {object} _class={realObjectClass} {keys} />
|
||||||
{/if}
|
{/if}
|
||||||
@ -258,12 +268,13 @@
|
|||||||
ignoreKeys = ev.detail.ignoreKeys
|
ignoreKeys = ev.detail.ignoreKeys
|
||||||
ignoreMixins = new Set(ev.detail.ignoreMixins)
|
ignoreMixins = new Set(ev.detail.ignoreMixins)
|
||||||
allowedCollections = ev.detail.allowedCollections ?? []
|
allowedCollections = ev.detail.allowedCollections ?? []
|
||||||
|
collectionArrays = ev.detail.collectionArrays ?? []
|
||||||
getMixins(parentClass, object)
|
getMixins(parentClass, object)
|
||||||
updateKeys()
|
updateKeys()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#each collectionEditors.filter((it) => !allowedCollections.includes(it.key.key)) as collection}
|
{#each collectionEditors as collection}
|
||||||
{#if collection.editor}
|
{#if collection.editor}
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<Component
|
<Component
|
||||||
|
@ -381,10 +381,19 @@ export function getFiltredKeys (
|
|||||||
return filterKeys(hierarchy, keys, ignoreKeys)
|
return filterKeys(hierarchy, keys, ignoreKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectionsFilter (hierarchy: Hierarchy, keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
|
export function collectionsFilter (
|
||||||
|
hierarchy: Hierarchy,
|
||||||
|
keys: KeyedAttribute[],
|
||||||
|
get: boolean,
|
||||||
|
include: string[]
|
||||||
|
): KeyedAttribute[] {
|
||||||
const result: KeyedAttribute[] = []
|
const result: KeyedAttribute[] = []
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (isCollectionAttr(hierarchy, key) === get) result.push(key)
|
if (include.includes(key.key)) {
|
||||||
|
result.push(key)
|
||||||
|
} else if (isCollectionAttr(hierarchy, key) === get) {
|
||||||
|
result.push(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,8 @@ export interface CollectionEditor extends Class<Doc> {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ArrayEditor extends Class<Doc> {
|
export interface ArrayEditor extends Class<Doc> {
|
||||||
inlineEditor: AnyComponent
|
editor?: AnyComponent
|
||||||
|
inlineEditor?: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import contact, { Employee } from '@anticrm/contact'
|
import contact, { Employee } from '@anticrm/contact'
|
||||||
import core, { Account, Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core'
|
import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core'
|
||||||
import hr, { Department, Staff } from '@anticrm/hr'
|
import hr, { Department, DepartmentMember, Staff } from '@anticrm/hr'
|
||||||
import { extractTx, TriggerControl } from '@anticrm/server-core'
|
import { extractTx, TriggerControl } from '@anticrm/server-core'
|
||||||
|
|
||||||
async function getOldDepartment (
|
async function getOldDepartment (
|
||||||
@ -67,7 +67,7 @@ function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Depar
|
|||||||
|
|
||||||
function getTxes (
|
function getTxes (
|
||||||
factory: TxFactory,
|
factory: TxFactory,
|
||||||
account: Ref<Account>,
|
account: Ref<DepartmentMember>,
|
||||||
added: Ref<Department>[],
|
added: Ref<Department>[],
|
||||||
removed?: Ref<Department>[]
|
removed?: Ref<Department>[]
|
||||||
): Tx[] {
|
): Tx[] {
|
||||||
|
Loading…
Reference in New Issue
Block a user