Fix TSK-241 (#2182)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-07-01 13:12:37 +07:00 committed by GitHub
parent 7b79c59376
commit ce71d23f49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 187 additions and 86 deletions

View File

@ -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'

View File

@ -60,7 +60,7 @@
"CopyToClipboard": "Скопировать в буфер обмена",
"Copied": "Скопировано",
"ViewFullProfile": "Посмотреть профиль",
"Member": "Сотрудник",
"Member": "Сотрудник компании",
"Members": "Сотрудники",
"NoMembers": "Нет добавленных сотрудников",
"AddMember": "Добавить сотрудника",

View File

@ -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"
}
}

View File

@ -30,6 +30,11 @@
"PTO2": "PTO/2",
"Overtime2": "Переработка/2",
"EditRequest": "Редактировать {type}",
"Request": "Запрос"
"Request": "Запрос",
"Staff": "Работник",
"Member": "Сотрудник",
"Members": "Сотрудники",
"NoMembers": "Нет добавленных сотрудников",
"AddMember": "Добавить сотрудника"
}
}

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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
}
})

View File

@ -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"
}
}

View File

@ -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>
}
})