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