From ce71d23f490313cfb8be24b912eabb45cb07d22f Mon Sep 17 00:00:00 2001 From: Andrey Sobolev <haiodo@users.noreply.github.com> Date: Fri, 1 Jul 2022 13:12:37 +0700 Subject: [PATCH] Fix TSK-241 (#2182) Signed-off-by: Andrey Sobolev <haiodo@gmail.com> --- models/hr/src/index.ts | 21 ++- plugins/contact-assets/lang/ru.json | 2 +- plugins/hr-assets/lang/en.json | 6 +- plugins/hr-assets/lang/ru.json | 7 +- .../src/components/DepartmentCard.svelte | 33 +++-- .../src/components/DepartmentStaff.svelte | 123 +++++++++--------- .../src/components/PersonsPresenter.svelte | 56 ++++++++ .../src/components/Structure.svelte | 12 +- plugins/hr-resources/src/plugin.ts | 6 +- plugins/hr/package.json | 3 +- plugins/hr/src/index.ts | 4 + 11 files changed, 187 insertions(+), 86 deletions(-) create mode 100644 plugins/hr-resources/src/components/PersonsPresenter.svelte diff --git a/models/hr/src/index.ts b/models/hr/src/index.ts index d0fefe090a..d0dd70c791 100644 --- a/models/hr/src/index.ts +++ b/models/hr/src/index.ts @@ -78,7 +78,7 @@ export class TDepartment extends TSpace implements Department { export class TDepartmentMember extends TEmployeeAccount implements DepartmentMember {} @Mixin(hr.mixin.Staff, contact.class.Employee) -@UX(contact.string.Employee, hr.icon.HR) +@UX(hr.string.Staff, hr.icon.HR) export class TStaff extends TEmployee implements Staff { @Prop(TypeRef(hr.class.Department), hr.string.Department) department!: Ref<Department> @@ -314,6 +314,25 @@ export function createModel (builder: Builder): void { }, hr.action.EditRequest ) + + builder.createDoc( + view.class.Viewlet, + core.space.Model, + { + attachTo: hr.mixin.Staff, + descriptor: view.viewlet.Table, + config: [ + '', + { + key: '$lookup.channels', + sortingKey: ['$lookup.channels.lastMessage', 'channels'] + }, + 'modifiedOn' + ], + hiddenKeys: [] + }, + hr.viewlet.TableMember + ) } export { hrOperation } from './migration' diff --git a/plugins/contact-assets/lang/ru.json b/plugins/contact-assets/lang/ru.json index 247d620884..c1aae20f64 100644 --- a/plugins/contact-assets/lang/ru.json +++ b/plugins/contact-assets/lang/ru.json @@ -60,7 +60,7 @@ "CopyToClipboard": "Скопировать в буфер обмена", "Copied": "Скопировано", "ViewFullProfile": "Посмотреть профиль", - "Member": "Сотрудник", + "Member": "Сотрудник компании", "Members": "Сотрудники", "NoMembers": "Нет добавленных сотрудников", "AddMember": "Добавить сотрудника", diff --git a/plugins/hr-assets/lang/en.json b/plugins/hr-assets/lang/en.json index 7dc06767fe..d32dac1cc1 100644 --- a/plugins/hr-assets/lang/en.json +++ b/plugins/hr-assets/lang/en.json @@ -30,6 +30,10 @@ "PTO2": "PTO/2", "Overtime2": "Overtime/2", "EditRequest": "Edit {type}", - "Request": "Request" + "Request": "Request", + "Staff": "Worker", + "Members": "Members", + "NoMembers": "No members added", + "AddMember": "Add member" } } \ No newline at end of file diff --git a/plugins/hr-assets/lang/ru.json b/plugins/hr-assets/lang/ru.json index 8e459ef19e..5c06b1ff80 100644 --- a/plugins/hr-assets/lang/ru.json +++ b/plugins/hr-assets/lang/ru.json @@ -30,6 +30,11 @@ "PTO2": "PTO/2", "Overtime2": "Переработка/2", "EditRequest": "Редактировать {type}", - "Request": "Запрос" + "Request": "Запрос", + "Staff": "Работник", + "Member": "Сотрудник", + "Members": "Сотрудники", + "NoMembers": "Нет добавленных сотрудников", + "AddMember": "Добавить сотрудника" } } \ No newline at end of file diff --git a/plugins/hr-resources/src/components/DepartmentCard.svelte b/plugins/hr-resources/src/components/DepartmentCard.svelte index ffa1e3f816..954c152a75 100644 --- a/plugins/hr-resources/src/components/DepartmentCard.svelte +++ b/plugins/hr-resources/src/components/DepartmentCard.svelte @@ -13,20 +13,22 @@ // limitations under the License. --> <script lang="ts"> - import { Ref, WithLookup } from '@anticrm/core' - import { Department } from '@anticrm/hr' - import { Avatar, getClient, UsersPopup } from '@anticrm/presentation' - import CreateDepartment from './CreateDepartment.svelte' - import DepartmentCard from './DepartmentCard.svelte' - import hr from '../plugin' - import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, showPanel } from '@anticrm/ui' import contact, { Employee } from '@anticrm/contact' import { EmployeePresenter } from '@anticrm/contact-resources' - import { Menu } from '@anticrm/view-resources' + 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 view from '@anticrm/view' + import { Menu } from '@anticrm/view-resources' + import hr from '../plugin' + import CreateDepartment from './CreateDepartment.svelte' + import DepartmentCard from './DepartmentCard.svelte' + import PersonsPresenter from './PersonsPresenter.svelte' export let value: WithLookup<Department> export let descendants: Map<Ref<Department>, WithLookup<Department>[]> + export let allEmployees: WithLookup<Staff>[] = [] $: currentDescendants = descendants.get(value._id) ?? [] @@ -79,6 +81,8 @@ function edit (e: MouseEvent): void { showPanel(view.component.EditDoc, value._id, value._class, 'content') } + + $: values = allEmployees.filter((it) => it.department === value._id) </script> <div class="flex-center w-full px-4"> @@ -91,7 +95,7 @@ <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} on:click={createChild} /> + <Button icon={IconAdd} kind={'link-bordered'} on:click={createChild} /> </div> <Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} /> <div class="flex-row ml-2"> @@ -100,6 +104,7 @@ </div> <Label label={hr.string.MemberCount} params={{ count: value.members.length }} /> </div> + <PersonsPresenter value={values} /> </div> <div class="flex-center mr-2"> <div class="mr-2"> @@ -116,21 +121,23 @@ onEmployeeEdit={openLeadEditor} /> </div> - <Button icon={IconMoreV} kind={'transparent'} on:click={showMenu} /> </div> </div> </div> </div> <div class="ml-8"> {#each currentDescendants as nested} - <DepartmentCard value={nested} {descendants} /> + <DepartmentCard value={nested} {descendants} {allEmployees} /> {/each} </div> <style lang="scss"> .container { - border-radius: 0.5rem; - border: 1px solid var(--theme-zone-border); background-color: var(--board-card-bg-color); + + &:hover { + background-color: var(--board-card-bg-hover); + cursor: pointer; + } } </style> diff --git a/plugins/hr-resources/src/components/DepartmentStaff.svelte b/plugins/hr-resources/src/components/DepartmentStaff.svelte index 88dc501664..805fa64f69 100644 --- a/plugins/hr-resources/src/components/DepartmentStaff.svelte +++ b/plugins/hr-resources/src/components/DepartmentStaff.svelte @@ -14,22 +14,19 @@ --> <script lang="ts"> 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 { Department, DepartmentMember, Staff } from '@anticrm/hr' + 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 { 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' export let objectId: Ref<Department> | undefined let value: Department | undefined - let employees: WithLookup<Staff>[] = [] - let accounts: DepartmentMember[] = [] const departmentQuery = createQuery() - const query = createQuery() - const accountsQuery = createQuery() const client = getClient() $: objectId && @@ -42,36 +39,6 @@ (res) => ([value] = res) ) - $: value && - accountsQuery.query( - contact.class.EmployeeAccount, - { - _id: { $in: value.members } - }, - (res) => { - accounts = res - } - ) - - $: accounts.length && - query.query( - hr.mixin.Staff, - { - _id: { $in: accounts.map((p) => p.employee) as Ref<Staff>[] } - }, - (res) => { - employees = res - }, - { - sort: { - name: SortingOrder.Descending - }, - lookup: { - department: hr.class.Department - } - } - ) - function add (e: MouseEvent) { showPopup( UsersPopup, @@ -80,13 +47,20 @@ docQuery: { active: true }, - ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id) + ignoreUsers: memberItems.map((it) => it._id) }, eventToHTMLElement(e), addMember ) } + let memberItems: Staff[] = [] + + const membersQuery = createQuery() + $: membersQuery.query(hr.mixin.Staff, { department: objectId }, (result) => { + memberItems = result + }) + async function addMember (employee: Employee | undefined): Promise<void> { if (employee === null || employee === undefined || value === undefined) { return @@ -130,45 +104,66 @@ } } } + + const preferenceQuery = createQuery() + let preference: ViewletPreference | undefined + let loading = false + let descr: WithLookup<Viewlet> | undefined + + $: updateDescriptor(hr.viewlet.TableMember) + + function updateDescriptor (id: Ref<Viewlet>) { + loading = true + client + .findOne<Viewlet>(view.class.Viewlet, { + _id: id + }) + .then((res) => { + descr = res + if (res !== undefined) { + preferenceQuery.query( + view.class.ViewletPreference, + { + attachedTo: res._id + }, + (res) => { + preference = res[0] + loading = false + }, + { limit: 1 } + ) + } + }) + } </script> <div class="antiSection"> <div class="antiSection-header"> <span class="antiSection-header__title"> - <Label label={contact.string.Members} /> + <Label label={hr.string.Members} /> </span> - <Button id={hr.string.AddEmployee} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={add} /> + <div class="buttons-group xsmall-gap"> + <ViewletSettingButton viewlet={descr} /> + <Button id={hr.string.AddEmployee} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={add} /> + </div> </div> - {#if employees.length > 0} + {#if (value?.members.length ?? 0) > 0} <Scroller> - <table class="antiTable"> - <thead class="scroller-thead"> - <tr class="scroller-thead__tr"> - <th><Label label={contact.string.Member} /></th> - <th><Label label={hr.string.Department} /></th> - </tr> - </thead> - <tbody> - {#each employees as value} - <tr class="antiTable-body__row"> - <td><EmployeePresenter {value} /></td> - <td> - {#if value.$lookup?.department} - {value.$lookup.department.name} - {/if} - </td> - </tr> - {/each} - </tbody> - </table> + <Table + _class={hr.mixin.Staff} + config={preference?.config ?? descr?.config ?? []} + options={descr?.options} + query={{ department: objectId }} + loadingProps={{ length: value?.members.length ?? 0 }} + /> </Scroller> {:else} <div class="antiSection-empty solid flex-col-center mt-3"> <span class="text-sm dark-color"> - <Label label={contact.string.NoMembers} /> + <Label label={hr.string.NoMembers} /> </span> <span class="text-sm content-accent-color over-underline" on:click={add}> - <Label label={contact.string.AddMember} /> + <Label label={hr.string.AddMember} /> </span> </div> {/if} diff --git a/plugins/hr-resources/src/components/PersonsPresenter.svelte b/plugins/hr-resources/src/components/PersonsPresenter.svelte new file mode 100644 index 0000000000..6ac68dd28c --- /dev/null +++ b/plugins/hr-resources/src/components/PersonsPresenter.svelte @@ -0,0 +1,56 @@ +<!-- +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 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 { Employee } from '@anticrm/contact' + import { EmployeePresenter } from '@anticrm/contact-resources' + import { WithLookup } from '@anticrm/core' + import { Staff } from '@anticrm/hr' + import hr from '../plugin' + + export let value: WithLookup<Staff> | WithLookup<Staff>[] + export let inline: boolean = false + + let persons: WithLookup<Employee>[] = [] + $: persons = Array.isArray(value) ? value : [value] +</script> + +{#if value} + <div class="flex persons"> + {#each persons as p} + <div class="ml-2 hover-trans"> + <EmployeePresenter + value={p} + shouldShowName={false} + {inline} + tooltipLabels={{ + personLabel: hr.string.TeamLeadTooltip, + placeholderLabel: hr.string.AssignLead + }} + /> + </div> + {/each} + </div> +{/if} + +<style lang="scss"> + .persons { + display: grid; + grid-template-columns: repeat(4, min-content); + .icon { + margin: 0.25rem; + } + } +</style> diff --git a/plugins/hr-resources/src/components/Structure.svelte b/plugins/hr-resources/src/components/Structure.svelte index 066a91982f..64fe540ff6 100644 --- a/plugins/hr-resources/src/components/Structure.svelte +++ b/plugins/hr-resources/src/components/Structure.svelte @@ -14,8 +14,8 @@ --> <script lang="ts"> import contact from '@anticrm/contact' - import { DocumentQuery, Ref } from '@anticrm/core' - import type { Department } from '@anticrm/hr' + import { DocumentQuery, Ref, WithLookup } from '@anticrm/core' + import type { Department, Staff } from '@anticrm/hr' import { createQuery } from '@anticrm/presentation' import { Button, eventToHTMLElement, Icon, IconAdd, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui' import hr from '../plugin' @@ -34,8 +34,10 @@ } const query = createQuery() + const spaceMembers = createQuery() let descendants: Map<Ref<Department>, Department[]> = new Map<Ref<Department>, Department[]>() + let allEmployees: WithLookup<Staff>[] = [] let head: Department | undefined query.query( @@ -57,6 +59,10 @@ } } ) + + spaceMembers.query(hr.mixin.Staff, {}, (res) => { + allEmployees = res + }) </script> <div class="ac-header full divide"> @@ -82,6 +88,6 @@ <Scroller> {#if head} - <DepartmentCard value={head} {descendants} /> + <DepartmentCard value={head} {descendants} {allEmployees} /> {/if} </Scroller> diff --git a/plugins/hr-resources/src/plugin.ts b/plugins/hr-resources/src/plugin.ts index 349bc9371e..62bbfb9890 100644 --- a/plugins/hr-resources/src/plugin.ts +++ b/plugins/hr-resources/src/plugin.ts @@ -37,6 +37,10 @@ export default mergeIds(hrId, hr, { EditRequest: '' as IntlString, CreateRequest: '' as IntlString, Today: '' as IntlString, - NoEmployeesInDepartment: '' as IntlString + NoEmployeesInDepartment: '' as IntlString, + Staff: '' as IntlString, + Members: '' as IntlString, + NoMembers: '' as IntlString, + AddMember: '' as IntlString } }) diff --git a/plugins/hr/package.json b/plugins/hr/package.json index 4da9e47ae2..0e015a6676 100644 --- a/plugins/hr/package.json +++ b/plugins/hr/package.json @@ -28,6 +28,7 @@ "dependencies": { "@anticrm/contact": "~0.6.5", "@anticrm/core": "~0.6.16", - "@anticrm/platform": "~0.6.6" + "@anticrm/platform": "~0.6.6", + "@anticrm/view": "~0.6.0" } } diff --git a/plugins/hr/src/index.ts b/plugins/hr/src/index.ts index e1284f1b6e..45432ee999 100644 --- a/plugins/hr/src/index.ts +++ b/plugins/hr/src/index.ts @@ -17,6 +17,7 @@ import type { Employee, EmployeeAccount } from '@anticrm/contact' import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core' import type { Asset, IntlString, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' +import { Viewlet } from '@anticrm/view' /** * @public @@ -115,6 +116,9 @@ const hr = plugin(hrId, { Remote: '' as Ref<RequestType>, Overtime: '' as Ref<RequestType>, Overtime2: '' as Ref<RequestType> + }, + viewlet: { + TableMember: '' as Ref<Viewlet> } })