From 261284df292e232dafc8d1f13d4f9a6e53c5eefb Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Wed, 20 Jul 2022 22:21:52 +0700
Subject: [PATCH] Fix HR statistics (#2242)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 changelog.md                                  |  12 +-
 models/hr/src/index.ts                        |  56 +++-
 models/hr/src/migration.ts                    | 156 ++++++-----
 models/hr/src/plugin.ts                       |   5 +-
 packages/model/src/migration.ts               |  12 +-
 packages/ui/src/components/Component.svelte   |   5 +-
 .../src/components/CreateRequest.svelte       |  14 +-
 .../src/components/RequestPresenter.svelte    |  44 +++
 .../src/components/RequestsPopup.svelte       |  12 +-
 .../src/components/Schedule.svelte            |  19 --
 .../src/components/ScheduleView.svelte        |  22 +-
 .../src/components/TzDateEditor.svelte        |  51 ++++
 .../src/components/TzDatePresenter.svelte     |  26 ++
 .../components/schedule/MonthTableView.svelte | 259 +++++++++++++-----
 .../src/components/schedule/MonthView.svelte  |  26 +-
 .../components/schedule/StatPresenter.svelte  |  20 +-
 .../src/components/schedule/YearView.svelte   |  11 +-
 plugins/hr-resources/src/index.ts             |   8 +-
 plugins/hr-resources/src/utils.ts             |  41 +--
 plugins/hr/src/index.ts                       |  25 +-
 .../src/components/ProjectSelector.svelte     |  42 +--
 .../src/components/issues/IssuesList.svelte   |   1 +
 22 files changed, 559 insertions(+), 308 deletions(-)
 create mode 100644 plugins/hr-resources/src/components/RequestPresenter.svelte
 create mode 100644 plugins/hr-resources/src/components/TzDateEditor.svelte
 create mode 100644 plugins/hr-resources/src/components/TzDatePresenter.svelte

diff --git a/changelog.md b/changelog.md
index 176e6deff5..7c6f724255 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,11 +1,21 @@
 # Changelog
 
-## 0.6.31 (upcoming)
+## 0.6.32 (upcoming)
+
+## 0.6.31
 
 Core:
 
 - Fix password change settings
 - Fix settings collapse
+- Allow to add multiple enum values
+- Fix password change issues
+- Fix minxin query
+
+HR:
+
+- Talant with Active/Inactive Application filter
+- Improve PTO table statistics
 
 ## 0.6.30
 
diff --git a/models/hr/src/index.ts b/models/hr/src/index.ts
index 77762ccf2f..903a9cc023 100644
--- a/models/hr/src/index.ts
+++ b/models/hr/src/index.ts
@@ -14,8 +14,8 @@
 //
 
 import { Employee } from '@anticrm/contact'
-import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
-import { Department, DepartmentMember, hrId, Request, RequestType, Staff } from '@anticrm/hr'
+import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@anticrm/core'
+import { Department, DepartmentMember, hrId, Request, RequestType, Staff, TzDate } from '@anticrm/hr'
 import {
   ArrOf,
   Builder,
@@ -25,7 +25,6 @@ import {
   Mixin,
   Model,
   Prop,
-  TypeDate,
   TypeIntlString,
   TypeMarkup,
   TypeRef,
@@ -36,8 +35,8 @@ import attachment from '@anticrm/model-attachment'
 import calendar from '@anticrm/model-calendar'
 import chunter from '@anticrm/model-chunter'
 import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
-import core, { TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
-import view, { createAction } from '@anticrm/model-view'
+import core, { TAttachedDoc, TDoc, TSpace, TType } from '@anticrm/model-core'
+import view, { classPresenter, createAction } from '@anticrm/model-view'
 import workbench from '@anticrm/model-workbench'
 import { Asset, IntlString } from '@anticrm/platform'
 import hr from './plugin'
@@ -95,6 +94,22 @@ export class TRequestType extends TDoc implements RequestType {
   color!: number
 }
 
+@Model(hr.class.TzDate, core.class.Type)
+@UX(core.string.Timestamp)
+export class TTzDate extends TType {
+  year!: number
+  month!: number
+  day!: number
+  offset!: number
+}
+
+/**
+ * @public
+ */
+export function TypeTzDate (): Type<TzDate> {
+  return { _class: hr.class.TzDate, label: core.string.Timestamp }
+}
+
 @Model(hr.class.Request, core.class.AttachedDoc, DOMAIN_HR)
 @UX(hr.string.Request, hr.icon.PTO)
 export class TRequest extends TAttachedDoc implements Request {
@@ -123,18 +138,15 @@ export class TRequest extends TAttachedDoc implements Request {
   @Index(IndexKind.FullText)
   description!: Markup
 
-  @Prop(TypeDate(false), calendar.string.Date)
-  date!: Timestamp
+  @Prop(TypeTzDate(), calendar.string.Date)
+  tzDate!: TzDate
 
-  @Prop(TypeDate(false), calendar.string.DueTo)
-  dueDate!: Timestamp
-
-  // @Prop(TypeNumber(), calendar.string.Date)
-  timezoneOffset!: number
+  @Prop(TypeTzDate(), calendar.string.DueTo)
+  tzDueDate!: TzDate
 }
 
 export function createModel (builder: Builder): void {
-  builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff)
+  builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff, TTzDate)
 
   builder.createDoc(
     workbench.class.Application,
@@ -183,6 +195,8 @@ export function createModel (builder: Builder): void {
     editor: hr.component.DepartmentStaff
   })
 
+  classPresenter(builder, hr.class.TzDate, hr.component.TzDatePresenter, hr.component.TzDateEditor)
+
   builder.createDoc(
     hr.class.RequestType,
     core.space.Model,
@@ -337,6 +351,18 @@ export function createModel (builder: Builder): void {
     hr.viewlet.TableMember
   )
 
+  builder.createDoc(
+    view.class.Viewlet,
+    core.space.Model,
+    {
+      attachTo: hr.mixin.Staff,
+      descriptor: view.viewlet.Table,
+      config: [''],
+      hiddenKeys: []
+    },
+    hr.viewlet.StaffStats
+  )
+
   createAction(builder, {
     action: view.actionImpl.ValueSelector,
     actionPopup: view.component.ValueSelector,
@@ -359,6 +385,10 @@ export function createModel (builder: Builder): void {
       group: 'associate'
     }
   })
+
+  builder.mixin(hr.class.Request, core.class.Class, view.mixin.AttributePresenter, {
+    presenter: hr.component.RequestPresenter
+  })
 }
 
 export { hrOperation } from './migration'
diff --git a/models/hr/src/migration.ts b/models/hr/src/migration.ts
index a873b1e31f..d7bb1ab16d 100644
--- a/models/hr/src/migration.ts
+++ b/models/hr/src/migration.ts
@@ -13,8 +13,9 @@
 // limitations under the License.
 //
 
-import { DOMAIN_TX, SortingOrder, TxCreateDoc, TxOperations, TxUpdateDoc } from '@anticrm/core'
-import { Request } from '@anticrm/hr'
+import { Employee } from '@anticrm/contact'
+import { DOMAIN_TX, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@anticrm/core'
+import { Request, TzDate } from '@anticrm/hr'
 import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
 import core from '@anticrm/model-core'
 import hr, { DOMAIN_HR } from './index'
@@ -40,102 +41,72 @@ async function createSpace (tx: TxOperations): Promise<void> {
   }
 }
 
-function toUTC (date: Date | number): number {
+function toTzDate (date: number): TzDate {
   const res = new Date(date)
-  if (res.getUTCFullYear() !== res.getFullYear()) {
-    res.setUTCFullYear(res.getFullYear())
+  return {
+    year: res.getFullYear(),
+    month: res.getMonth(),
+    day: res.getDate(),
+    offset: res.getTimezoneOffset()
   }
-  if (res.getUTCMonth() !== res.getMonth()) {
-    res.setUTCMonth(res.getMonth())
-  }
-  if (res.getUTCDate() !== res.getDate()) {
-    res.setUTCDate(res.getDate())
-  }
-  return res.setUTCHours(12, 0, 0, 0)
-}
-
-function isDefault (date: number, due: number): boolean {
-  const start = new Date(date)
-  const end = new Date(due)
-  if (start.getDate() === end.getDate() && end.getHours() - start.getHours() === 12) {
-    return true
-  }
-  if (start.getDate() + 1 === end.getDate() && end.getHours() === start.getHours()) {
-    return true
-  }
-  return false
 }
 
 async function migrateRequestTime (client: MigrationClient, request: Request): Promise<void> {
-  const date = toUTC(request.date)
-  const dueDate = isDefault(request.date, request.dueDate) ? date : toUTC(request.dueDate)
+  const date = toTzDate((request as any).date as unknown as number)
+  const dueDate = toTzDate((request as any).dueDate as unknown as number)
   await client.update(
     DOMAIN_HR,
     { _id: request._id },
     {
-      date,
-      dueDate
+      tzDate: date,
+      tzDueDate: dueDate
     }
   )
 
-  const updateDateTx = (
-    await client.find<TxUpdateDoc<Request>>(
-      DOMAIN_TX,
-      { _class: core.class.TxUpdateDoc, objectId: request._id, 'operations.date': { $exists: true } },
-      { sort: { modifiedOn: SortingOrder.Descending } }
-    )
-  )[0]
-  if (updateDateTx !== undefined) {
-    const operations = updateDateTx.operations
-    operations.dueDate = date
-    await client.update(
-      DOMAIN_TX,
-      { _id: updateDateTx._id },
-      {
-        operations
-      }
-    )
-  }
-  const updateDueTx = (
-    await client.find<TxUpdateDoc<Request>>(
-      DOMAIN_TX,
-      { _class: core.class.TxUpdateDoc, objectId: request._id, 'operations.dueDate': { $exists: true } },
-      { sort: { modifiedOn: SortingOrder.Descending } }
-    )
-  )[0]
-  if (updateDueTx !== undefined) {
-    const operations = updateDueTx.operations
-    operations.dueDate = dueDate
-    await client.update(
-      DOMAIN_TX,
-      { _id: updateDateTx._id },
-      {
-        operations
-      }
-    )
-  }
+  const txes = await client.find<TxCollectionCUD<Employee, Request>>(DOMAIN_TX, {
+    'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc] },
+    'tx.objectId': request._id
+  })
 
-  if (updateDueTx === undefined || updateDateTx === undefined) {
-    const createTx = (
-      await client.find<TxCreateDoc<Request>>(
+  for (const utx of txes) {
+    if (utx.tx._class === core.class.TxCreateDoc) {
+      const ctx = utx.tx as TxCreateDoc<Request>
+      const { date, dueDate, ...attributes } = ctx.attributes as any
+      await client.update(
         DOMAIN_TX,
-        { _class: core.class.TxCreateDoc, objectId: request._id },
-        { sort: { modifiedOn: SortingOrder.Descending } }
+        { _id: utx._id },
+        {
+          tx: {
+            ...ctx,
+            attributes: {
+              ...attributes,
+              tzDate: toTzDate(date as unknown as number),
+              tzDueDate: toTzDate((dueDate ?? date) as unknown as number)
+            }
+          }
+        }
       )
-    )[0]
-    if (createTx !== undefined) {
-      const attributes = createTx.attributes
-      if (updateDateTx === undefined) {
-        attributes.date = date
+    }
+    if (utx.tx._class === core.class.TxUpdateDoc) {
+      const ctx = utx.tx as TxUpdateDoc<Request>
+      const { date, dueDate, ...operations } = ctx.operations as any
+      const ops: any = {
+        ...operations
       }
-      if (updateDueTx === undefined) {
-        attributes.dueDate = dueDate
+      if (date !== undefined) {
+        ops.tzDate = toTzDate(date as unknown as number)
+      }
+      if (dueDate !== undefined) {
+        ops.tzDueDate = toTzDate(dueDate as unknown as number)
       }
       await client.update(
         DOMAIN_TX,
-        { _id: createTx._id },
+        { _id: utx._id },
         {
-          attributes
+          tx: {
+            ...ctx,
+            operations: ops
+          }
         }
       )
     }
@@ -143,7 +114,34 @@ async function migrateRequestTime (client: MigrationClient, request: Request): P
 }
 
 async function migrateTime (client: MigrationClient): Promise<void> {
-  const requests = await client.find<Request>(DOMAIN_HR, { _class: hr.class.Request })
+  const createTxes = await client.find<TxCreateDoc<Request>>(DOMAIN_TX, {
+    _class: core.class.TxCreateDoc,
+    objectClass: hr.class.Request
+  })
+  for (const tx of createTxes) {
+    await client.update(
+      DOMAIN_TX,
+      { _id: tx._id },
+      {
+        _class: core.class.TxCollectionCUD,
+        tx: tx,
+        collection: tx.attributes.collection,
+        objectId: tx.attributes.attachedTo,
+        objectClass: tx.attributes.attachedToClass
+      }
+    )
+    await client.update(
+      DOMAIN_TX,
+      { _id: tx._id },
+      {
+        $unset: {
+          attributes: ''
+        }
+      }
+    )
+  }
+
+  const requests = await client.find<Request>(DOMAIN_HR, { _class: hr.class.Request, tzDate: { $exists: false } })
   for (const request of requests) {
     await migrateRequestTime(client, request)
   }
diff --git a/models/hr/src/plugin.ts b/models/hr/src/plugin.ts
index 39f07c5821..3248b33135 100644
--- a/models/hr/src/plugin.ts
+++ b/models/hr/src/plugin.ts
@@ -39,7 +39,10 @@ export default mergeIds(hrId, hr, {
     DepartmentStaff: '' as AnyComponent,
     DepartmentEditor: '' as AnyComponent,
     Schedule: '' as AnyComponent,
-    EditRequest: '' as AnyComponent
+    EditRequest: '' as AnyComponent,
+    TzDatePresenter: '' as AnyComponent,
+    TzDateEditor: '' as AnyComponent,
+    RequestPresenter: '' as AnyComponent
   },
   category: {
     HR: '' as Ref<ActionCategory>
diff --git a/packages/model/src/migration.ts b/packages/model/src/migration.ts
index 7c09eeea8e..1f8aad02a3 100644
--- a/packages/model/src/migration.ts
+++ b/packages/model/src/migration.ts
@@ -1,4 +1,5 @@
 import {
+  ArrayAsElementPosition,
   Client,
   Doc,
   DocumentQuery,
@@ -6,16 +7,25 @@ import {
   FindOptions,
   IncOptions,
   ObjQueryType,
+  OmitNever,
   PushOptions,
   Ref
 } from '@anticrm/core'
 
+/**
+ * @public
+ */
+export interface UnsetOptions<T extends object> {
+  $unset?: Partial<OmitNever<ArrayAsElementPosition<Required<T>>>>
+}
+
 /**
  * @public
  */
 export type MigrateUpdate<T extends Doc> = Partial<T> &
 Omit<PushOptions<T>, '$move'> &
-IncOptions<T> & {
+IncOptions<T> &
+UnsetOptions<T> & {
   // For any other mongo stuff
   [key: string]: any
 }
diff --git a/packages/ui/src/components/Component.svelte b/packages/ui/src/components/Component.svelte
index 9d5200630d..7e3953a897 100644
--- a/packages/ui/src/components/Component.svelte
+++ b/packages/ui/src/components/Component.svelte
@@ -23,13 +23,16 @@
   export let is: AnyComponent
   export let props = {}
   export let shrink: boolean = false
+  export let showLoading = true
 
   $: component = is != null ? getResource(is) : Promise.reject(new Error('is not defined'))
 </script>
 
 {#if is}
   {#await component}
-    <Loading {shrink} />
+    {#if showLoading}
+      <Loading {shrink} />
+    {/if}
   {:then Ctor}
     <ErrorBoundary>
       <Ctor {...props} on:change on:close on:open on:click on:delete>
diff --git a/plugins/hr-resources/src/components/CreateRequest.svelte b/plugins/hr-resources/src/components/CreateRequest.svelte
index bb5a9e169f..f27e49253b 100644
--- a/plugins/hr-resources/src/components/CreateRequest.svelte
+++ b/plugins/hr-resources/src/components/CreateRequest.svelte
@@ -22,7 +22,7 @@
   import ui, { Button, DateRangePresenter, DropdownLabelsIntl, IconAttachment } from '@anticrm/ui'
   import { createEventDispatcher } from 'svelte'
   import hr from '../plugin'
-  import { toUTC } from '../utils'
+  import { toTzDate } from '../utils'
 
   export let staff: Staff
   export let date: Date
@@ -59,15 +59,11 @@
     if (value != null) date = value
     if (date === undefined) return
     if (type === undefined) return
-    await client.createDoc(hr.class.Request, staff.department, {
-      attachedTo: staff._id,
-      attachedToClass: staff._class,
+    await client.addCollection(hr.class.Request, staff.department, staff._id, staff._class, 'requests', {
       type: type._id,
-      date: toUTC(date),
-      dueDate: toUTC(dueDate),
-      description,
-      collection: 'requests',
-      timezoneOffset: new Date(date).getTimezoneOffset()
+      tzDate: toTzDate(new Date(date)),
+      tzDueDate: toTzDate(new Date(dueDate)),
+      description
     })
     await descriptionBox.createAttachments()
   }
diff --git a/plugins/hr-resources/src/components/RequestPresenter.svelte b/plugins/hr-resources/src/components/RequestPresenter.svelte
new file mode 100644
index 0000000000..fc91943302
--- /dev/null
+++ b/plugins/hr-resources/src/components/RequestPresenter.svelte
@@ -0,0 +1,44 @@
+<!--
+// 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 { Request } from '@anticrm/hr'
+  import { getClient } from '@anticrm/presentation'
+  import { DateRangePresenter, Label } from '@anticrm/ui'
+  import { fromTzDate, tzDateEqual } from '../utils'
+
+  export let value: Request | null | undefined
+  export let noShift: boolean = false
+
+  const client = getClient()
+
+  $: type = value?.type !== undefined ? client.getModel().getObject(value?.type) : undefined
+</script>
+
+{#if type && value != null}
+  <div class="flex-row-center gap-2">
+    <div class="fs-title">
+      <Label label={type.label} />
+    </div>
+    {#if value.tzDate && tzDateEqual(value.tzDate, value.tzDueDate)}
+      <DateRangePresenter value={fromTzDate(value.tzDate)} {noShift} />
+    {:else if value.tzDate}
+      <DateRangePresenter value={fromTzDate(value.tzDate)} {noShift} />
+      {#if value.tzDueDate}
+        <DateRangePresenter value={fromTzDate(value.tzDueDate)} {noShift} />
+      {/if}
+    {/if}
+  </div>
+{/if}
diff --git a/plugins/hr-resources/src/components/RequestsPopup.svelte b/plugins/hr-resources/src/components/RequestsPopup.svelte
index 3b01d511c6..3db1660b6a 100644
--- a/plugins/hr-resources/src/components/RequestsPopup.svelte
+++ b/plugins/hr-resources/src/components/RequestsPopup.svelte
@@ -14,21 +14,17 @@
 -->
 <script lang="ts">
   import { Ref, SortingOrder } from '@anticrm/core'
-  import hr, { Staff } from '@anticrm/hr'
+  import hr, { Request } from '@anticrm/hr'
   import { Table } from '@anticrm/view-resources'
 
-  export let date: Date
-  export let endDate: number
-  export let employee: Ref<Staff>
+  export let requests: Ref<Request>[]
 </script>
 
 <Table
   _class={hr.class.Request}
   query={{
-    attachedTo: employee,
-    dueDate: { $gt: date.getTime() },
-    date: { $lt: endDate }
+    _id: { $in: requests }
   }}
-  config={['$lookup.type.label', 'date', 'dueDate']}
+  config={['$lookup.type.label', 'tzDate', 'tzDueDate']}
   options={{ sort: { date: SortingOrder.Ascending } }}
 />
diff --git a/plugins/hr-resources/src/components/Schedule.svelte b/plugins/hr-resources/src/components/Schedule.svelte
index cbaff39b3f..035fe0336c 100644
--- a/plugins/hr-resources/src/components/Schedule.svelte
+++ b/plugins/hr-resources/src/components/Schedule.svelte
@@ -17,12 +17,10 @@
   import calendar from '@anticrm/calendar-resources/src/plugin'
   import { Ref } from '@anticrm/core'
   import { Department } from '@anticrm/hr'
-  import { getEmbeddedLabel } from '@anticrm/platform'
   import { createQuery, SpaceSelector } from '@anticrm/presentation'
   import { Button, Icon, IconBack, IconForward, Label } from '@anticrm/ui'
   import view from '@anticrm/view'
   import hr from '../plugin'
-  import { tableToCSV } from '../utils'
   import ScheduleMonthView from './ScheduleView.svelte'
 
   let department = hr.ids.Head
@@ -140,23 +138,6 @@
         }}
       />
     </div>
-    {#if display === 'stats'}
-      <Button
-        label={getEmbeddedLabel('Export')}
-        on:click={() => {
-          // Download it
-          const filename = 'exportStaff' + new Date().toLocaleDateString() + '.csv'
-          const link = document.createElement('a')
-          link.style.display = 'none'
-          link.setAttribute('target', '_blank')
-          link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(tableToCSV('exportableData')))
-          link.setAttribute('download', filename)
-          document.body.appendChild(link)
-          link.click()
-          document.body.removeChild(link)
-        }}
-      />
-    {/if}
   {/if}
 
   <SpaceSelector _class={hr.class.Department} label={hr.string.Department} bind:space={department} />
diff --git a/plugins/hr-resources/src/components/ScheduleView.svelte b/plugins/hr-resources/src/components/ScheduleView.svelte
index cf6d933fc2..d1a70179fa 100644
--- a/plugins/hr-resources/src/components/ScheduleView.svelte
+++ b/plugins/hr-resources/src/components/ScheduleView.svelte
@@ -15,7 +15,7 @@
 <script lang="ts">
   import { CalendarMode } from '@anticrm/calendar-resources'
   import { Employee } from '@anticrm/contact'
-  import { Ref, Timestamp } from '@anticrm/core'
+  import { Ref } from '@anticrm/core'
   import type { Department, Request, RequestType, Staff } from '@anticrm/hr'
   import { createQuery } from '@anticrm/presentation'
   import { Label } from '@anticrm/ui'
@@ -33,11 +33,12 @@
 
   $: startDate = new Date(
     new Date(mode === CalendarMode.Year ? new Date(currentDate).setMonth(1) : currentDate).setDate(1)
-  ).setHours(0, 0, 0, 0)
-  $: endDate =
+  )
+  $: endDate = new Date(
     mode === CalendarMode.Year
       ? new Date(startDate).setFullYear(new Date(startDate).getFullYear() + 1)
       : new Date(startDate).setMonth(new Date(startDate).getMonth() + 1)
+  )
   $: departments = [department, ...getDescendants(department, descendants)]
 
   const lq = createQuery()
@@ -76,12 +77,14 @@
     return res
   }
 
-  function update (departments: Ref<Department>[], startDate: Timestamp, endDate: Timestamp) {
+  function update (departments: Ref<Department>[], startDate: Date, endDate: Date) {
     lq.query(
       hr.class.Request,
       {
-        dueDate: { $gte: startDate },
-        date: { $lt: endDate },
+        'tzDueDate.year': { $gte: startDate.getFullYear() },
+        'tzDueDate.month': { $gte: startDate.getMonth() },
+        'tzDate.year': { $lte: endDate.getFullYear() },
+        'tzDate.month': { $lte: endDate.getFullYear() },
         space: { $in: departments }
       },
       (res) => {
@@ -116,14 +119,13 @@
       <MonthView
         {departmentStaff}
         {employeeRequests}
-        {startDate}
-        {endDate}
-        teamLead={getTeamLead(department)}
         {types}
+        {startDate}
+        teamLead={getTeamLead(department)}
         {currentDate}
       />
     {:else if display === 'stats'}
-      <MonthTableView {departmentStaff} {employeeRequests} {startDate} {endDate} {types} {currentDate} />
+      <MonthTableView {departmentStaff} {employeeRequests} {types} {currentDate} />
     {/if}
   {/if}
 {:else}
diff --git a/plugins/hr-resources/src/components/TzDateEditor.svelte b/plugins/hr-resources/src/components/TzDateEditor.svelte
new file mode 100644
index 0000000000..bf46ead2b4
--- /dev/null
+++ b/plugins/hr-resources/src/components/TzDateEditor.svelte
@@ -0,0 +1,51 @@
+<!--
+// 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 { TzDate } from '@anticrm/hr'
+
+  // import { IntlString } from '@anticrm/platform'
+  import { DateRangePresenter } from '@anticrm/ui'
+  import { fromTzDate, toTzDate } from '../utils'
+
+  export let value: TzDate | null | undefined
+  export let onChange: (value: TzDate | null | undefined) => void
+  export let kind: 'no-border' | 'link' = 'no-border'
+  export let noShift: boolean = false
+
+  $: _value = value != null ? fromTzDate(value) : null
+</script>
+
+<DateRangePresenter
+  value={_value}
+  withTime={false}
+  editable
+  {kind}
+  {noShift}
+  on:change={(res) => {
+    if (res.detail != null) {
+      const dte = new Date(res.detail)
+      const tzdte = {
+        year: dte.getFullYear(),
+        month: dte.getMonth(),
+        day: dte.getDate(),
+        offset: dte.getTimezoneOffset()
+      }
+      const tzd = toTzDate(new Date(_value ?? Date.now()))
+      if (tzd.year !== tzdte.year || tzd.month !== tzdte.month || tzd.day !== tzdte.day) {
+        onChange(tzdte)
+      }
+    }
+  }}
+/>
diff --git a/plugins/hr-resources/src/components/TzDatePresenter.svelte b/plugins/hr-resources/src/components/TzDatePresenter.svelte
new file mode 100644
index 0000000000..8c01cda344
--- /dev/null
+++ b/plugins/hr-resources/src/components/TzDatePresenter.svelte
@@ -0,0 +1,26 @@
+<!--
+// 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 { TzDate } from '@anticrm/hr'
+  import { DateRangePresenter } from '@anticrm/ui'
+
+  export let value: TzDate | null | undefined
+  export let noShift: boolean = false
+
+  $: _value = value != null ? new Date().setFullYear(value?.year ?? 0, value?.month, value?.day) : null
+</script>
+
+<DateRangePresenter value={_value} {noShift} />
diff --git a/plugins/hr-resources/src/components/schedule/MonthTableView.svelte b/plugins/hr-resources/src/components/schedule/MonthTableView.svelte
index 63bf1e11f6..6c20f6cebf 100644
--- a/plugins/hr-resources/src/components/schedule/MonthTableView.svelte
+++ b/plugins/hr-resources/src/components/schedule/MonthTableView.svelte
@@ -13,20 +13,19 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { FindOptions, Ref } from '@anticrm/core'
+  import { Doc, Ref } from '@anticrm/core'
   import type { Request, RequestType, Staff } from '@anticrm/hr'
   import { getEmbeddedLabel } from '@anticrm/platform'
-  import { Label, Scroller } from '@anticrm/ui'
-  import { Table } from '@anticrm/view-resources'
+  import { createQuery, getClient } from '@anticrm/presentation'
+  import { Button, Label, Loading, Scroller } from '@anticrm/ui'
+  import view, { BuildModelKey, Viewlet, ViewletPreference } from '@anticrm/view'
+  import { Table, ViewletSettingButton } from '@anticrm/view-resources'
   import hr from '../../plugin'
-  import { getMonth, getTotal, weekDays } from '../../utils'
+  import { fromTzDate, getMonth, getTotal, tableToCSV, weekDays } from '../../utils'
   import NumberPresenter from './StatPresenter.svelte'
 
   export let currentDate: Date = new Date()
 
-  export let startDate: number
-  export let endDate: number
-
   export let departmentStaff: Staff[]
   export let types: Map<Ref<RequestType>, RequestType>
 
@@ -35,91 +34,203 @@
   $: month = getMonth(currentDate, currentDate.getMonth())
   $: wDays = weekDays(month.getUTCFullYear(), month.getUTCMonth())
 
-  const options: FindOptions<Staff> = {
-    lookup: {
-      department: hr.class.Department
-    }
-  }
-
-  function getDateRange (req: Request): string {
-    const st = new Date(req.date).getDate()
-    let days = Math.abs((req.dueDate - req.date) / 1000 / 60 / 60 / 24)
-    if (days === 0) {
-      days = 1
-    }
-    const stDate = new Date(req.date)
+  function getDateRange (request: Request): string {
+    const st = new Date(fromTzDate(request.tzDate)).getDate()
+    const days =
+      Math.floor(Math.abs((1 + fromTzDate(request.tzDueDate) - fromTzDate(request.tzDate)) / 1000 / 60 / 60 / 24)) + 1
+    const stDate = new Date(fromTzDate(request.tzDate))
     let ds = Array.from(Array(days).keys()).map((it) => st + it)
-    const type = types.get(req.type)
+    const type = types.get(request.type)
     if ((type?.value ?? -1) < 0) {
       ds = ds.filter((it) => ![0, 6].includes(new Date(stDate.setDate(it)).getDay()))
     }
     return ds.join(' ')
   }
 
-  $: typevals = Array.from(
-    Array.from(types.values()).map((it) => ({
-      key: '',
-      label: it.label,
-      presenter: NumberPresenter,
-      props: {
-        month: month ?? getMonth(currentDate, currentDate.getMonth()),
-        employeeRequests,
-        display: (req: Request[]) =>
-          req
-            .filter((r) => r.type === it._id)
-            .map((it) => getDateRange(it))
-            .join(' ')
+  function getEndDate (date: Date): number {
+    return new Date(date).setMonth(date.getMonth() + 1)
+  }
+  function getRequests (employee: Ref<Staff>, date: Date): Request[] {
+    const requests = employeeRequests.get(employee)
+    if (requests === undefined) return []
+    const res: Request[] = []
+    const time = date.getTime()
+    const endTime = getEndDate(date)
+    for (const request of requests) {
+      if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
+        res.push(request)
       }
-    }))
+    }
+    return res
+  }
+
+  $: typevals = new Map<string, BuildModelKey>(
+    Array.from(types.values()).map((it) => [
+      it.label as string,
+      {
+        key: '',
+        label: it.label,
+        presenter: NumberPresenter,
+        props: {
+          month: month ?? getMonth(currentDate, currentDate.getMonth()),
+          display: (req: Request[]) =>
+            req
+              .filter((r) => r.type === it._id)
+              .map((it) => getDateRange(it))
+              .join(' '),
+          getRequests
+        }
+      }
+    ])
   )
 
-  $: config = [
-    '',
-    '$lookup.department',
-    {
-      key: '',
-      label: getEmbeddedLabel('Working days'),
-      presenter: NumberPresenter,
-      props: {
-        month: month ?? getMonth(currentDate, currentDate.getMonth()),
-        employeeRequests,
-        display: (req: Request[]) => wDays + getTotal(req, types)
+  $: overrideConfig = new Map<string, BuildModelKey>([
+    [
+      '@wdCount',
+      {
+        key: '',
+        label: getEmbeddedLabel('Working days'),
+        presenter: NumberPresenter,
+        props: {
+          month: month ?? getMonth(currentDate, currentDate.getMonth()),
+          display: (req: Request[]) => wDays + getTotal(req, types),
+          getRequests
+        },
+        sortingKey: '@wdCount',
+        sortingFunction: (a: Doc, b: Doc) =>
+          getTotal(getRequests(b._id as Ref<Staff>, month), types) -
+          getTotal(getRequests(a._id as Ref<Staff>, month), types)
       }
-    },
-    {
-      key: '',
-      label: getEmbeddedLabel('PTOs'),
-      presenter: NumberPresenter,
-      props: {
-        month: month ?? getMonth(currentDate, currentDate.getMonth()),
-        employeeRequests,
-        display: (req: Request[]) => getTotal(req, types, (a) => (a < 0 ? Math.abs(a) : 0))
+    ],
+    [
+      '@ptoCount',
+      {
+        key: '',
+        label: getEmbeddedLabel('PTOs'),
+        presenter: NumberPresenter,
+        props: {
+          month: month ?? getMonth(currentDate, currentDate.getMonth()),
+          display: (req: Request[]) => getTotal(req, types, (a) => (a < 0 ? Math.abs(a) : 0)),
+          getRequests
+        },
+        sortingKey: '@ptoCount',
+        sortingFunction: (a: Doc, b: Doc) =>
+          getTotal(getRequests(b._id as Ref<Staff>, month), types, (a) => (a < 0 ? Math.abs(a) : 0)) -
+          getTotal(getRequests(a._id as Ref<Staff>, month), types, (a) => (a < 0 ? Math.abs(a) : 0))
       }
-    },
-    {
-      key: '',
-      label: getEmbeddedLabel('EXTRa'),
-      presenter: NumberPresenter,
-      props: {
-        month: month ?? getMonth(currentDate, currentDate.getMonth()),
-        employeeRequests,
-        display: (req: Request[]) => getTotal(req, types, (a) => (a > 0 ? Math.abs(a) : 0))
+    ],
+    [
+      '@extraCount',
+      {
+        key: '',
+        label: getEmbeddedLabel('EXTRa'),
+        presenter: NumberPresenter,
+        props: {
+          month: month ?? getMonth(currentDate, currentDate.getMonth()),
+          display: (req: Request[]) => getTotal(req, types, (a) => (a > 0 ? Math.abs(a) : 0)),
+          getRequests
+        },
+        sortingKey: '@extraCount',
+        sortingFunction: (a: Doc, b: Doc) =>
+          getTotal(getRequests(b._id as Ref<Staff>, month), types, (a) => (a > 0 ? Math.abs(a) : 0)) -
+          getTotal(getRequests(a._id as Ref<Staff>, month), types, (a) => (a > 0 ? Math.abs(a) : 0))
       }
-    },
-    ...(typevals ?? [])
-  ]
+    ],
+    ...typevals
+  ])
+
+  const preferenceQuery = createQuery()
+  let preference: ViewletPreference | undefined
+  let descr: Viewlet | undefined
+
+  $: updateDescriptor(hr.viewlet.StaffStats)
+
+  const client = getClient()
+
+  let loading = false
+
+  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 }
+          )
+        }
+      })
+  }
+
+  function createConfig (descr: Viewlet, preference: ViewletPreference | undefined): (string | BuildModelKey)[] {
+    const base = preference?.config ?? descr.config
+    const result: (string | BuildModelKey)[] = []
+
+    for (const c of overrideConfig.values()) {
+      base.push(c)
+    }
+    for (const key of base) {
+      if (typeof key === 'string') {
+        result.push(overrideConfig.get(key) ?? key)
+      } else {
+        result.push(overrideConfig.get(key.key) ?? key)
+      }
+    }
+    return result
+  }
 </script>
 
 {#if departmentStaff.length}
   <Scroller tableFade>
     <div class="p-2">
-      <Table
-        tableId={'exportableData'}
-        _class={hr.mixin.Staff}
-        query={{ _id: { $in: departmentStaff.map((it) => it._id) } }}
-        {config}
-        {options}
-      />
+      {#if descr}
+        {#if loading}
+          <Loading />
+        {:else}
+          <div class="flex-row-center flex-reverse">
+            <div class="ml-1">
+              <ViewletSettingButton viewlet={descr} />
+            </div>
+            <Button
+              label={getEmbeddedLabel('Export')}
+              size={'small'}
+              on:click={() => {
+                // Download it
+                const filename = 'exportStaff' + new Date().toLocaleDateString() + '.csv'
+                const link = document.createElement('a')
+                link.style.display = 'none'
+                link.setAttribute('target', '_blank')
+                link.setAttribute(
+                  'href',
+                  'data:text/csv;charset=utf-8,' + encodeURIComponent(tableToCSV('exportableData'))
+                )
+                link.setAttribute('download', filename)
+                document.body.appendChild(link)
+                link.click()
+                document.body.removeChild(link)
+              }}
+            />
+          </div>
+          <Table
+            tableId={'exportableData'}
+            _class={hr.mixin.Staff}
+            query={{ _id: { $in: departmentStaff.map((it) => it._id) } }}
+            config={createConfig(descr, preference)}
+            options={descr.options}
+          />
+        {/if}
+      {/if}
     </div>
   </Scroller>
 {:else}
diff --git a/plugins/hr-resources/src/components/schedule/MonthView.svelte b/plugins/hr-resources/src/components/schedule/MonthView.svelte
index c622e026c0..55c6acd26d 100644
--- a/plugins/hr-resources/src/components/schedule/MonthView.svelte
+++ b/plugins/hr-resources/src/components/schedule/MonthView.svelte
@@ -32,20 +32,20 @@
     tooltip
   } from '@anticrm/ui'
   import hr from '../../plugin'
+  import { fromTzDate, getTotal } from '../../utils'
   import CreateRequest from '../CreateRequest.svelte'
   import RequestsPopup from '../RequestsPopup.svelte'
   import ScheduleRequests from '../ScheduleRequests.svelte'
 
   export let currentDate: Date = new Date()
 
-  export let startDate: number
-  export let endDate: number
+  export let startDate: Date
 
   export let departmentStaff: Staff[]
-  export let types: Map<Ref<RequestType>, RequestType>
 
   export let employeeRequests: Map<Ref<Staff>, Request[]>
   export let teamLead: Ref<Employee> | undefined
+  export let types: Map<Ref<RequestType>, RequestType>
 
   const todayDate = new Date()
 
@@ -56,7 +56,7 @@
     const time = date.getTime()
     const endTime = getEndDate(date)
     for (const request of requests) {
-      if (request.date <= endTime && request.dueDate > time) {
+      if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
         res.push(request)
       }
     }
@@ -85,15 +85,14 @@
   }
 
   function getEndDate (date: Date): number {
-    return new Date(date).setDate(date.getDate() + 1) - 1
+    return new Date(date).setDate(date.getDate() + 1)
   }
 
-  function getTooltip (requests: Request[], employee: Staff, date: Date): LabelAndProps | undefined {
+  function getTooltip (requests: Request[]): LabelAndProps | undefined {
     if (requests.length === 0) return
-    const endDate = getEndDate(date)
     return {
       component: RequestsPopup,
-      props: { date, endDate, employee: employee._id }
+      props: { requests: requests.map((it) => it._id) }
     }
   }
 
@@ -110,8 +109,9 @@
           <th>
             <Label label={contact.string.Employee} />
           </th>
+          <th>#</th>
           {#each values as value, i}
-            {@const day = getDay(new Date(startDate), value)}
+            {@const day = getDay(startDate, value)}
             <th
               class:today={areDatesEqual(todayDate, day)}
               class:weekend={isWeekend(day)}
@@ -130,15 +130,19 @@
       </thead>
       <tbody>
         {#each departmentStaff as employee, row}
+          {@const requests = employeeRequests.get(employee._id) ?? []}
           <tr>
             <td>
               <EmployeePresenter value={employee} />
             </td>
+            <td class="flex-center p-1" class:firstLine={row === 0} class:lastLine={row === departmentStaff.length - 1}>
+              {getTotal(requests, types)}
+            </td>
             {#each values as value, i}
-              {@const date = getDay(new Date(startDate), value)}
+              {@const date = getDay(startDate, value)}
               {@const requests = getRequests(employee._id, date)}
               {@const editable = isEditable(employee)}
-              {@const tooltipValue = getTooltip(requests, employee, date)}
+              {@const tooltipValue = getTooltip(requests)}
               {#key [tooltipValue, editable]}
                 <td
                   class:today={areDatesEqual(todayDate, date)}
diff --git a/plugins/hr-resources/src/components/schedule/StatPresenter.svelte b/plugins/hr-resources/src/components/schedule/StatPresenter.svelte
index 23bc78ba60..e14fdef18b 100644
--- a/plugins/hr-resources/src/components/schedule/StatPresenter.svelte
+++ b/plugins/hr-resources/src/components/schedule/StatPresenter.svelte
@@ -18,27 +18,9 @@
   import { Request, Staff } from '@anticrm/hr'
 
   export let value: Staff
-  export let employeeRequests: Map<Ref<Staff>, Request[]>
   export let display: (requests: Request[]) => number | string
   export let month: Date
-
-  function getEndDate (date: Date): number {
-    return new Date(date).setMonth(date.getMonth() + 1)
-  }
-
-  function getRequests (employee: Ref<Staff>, date: Date): Request[] {
-    const requests = employeeRequests.get(employee)
-    if (requests === undefined) return []
-    const res: Request[] = []
-    const time = date.getTime()
-    const endTime = getEndDate(date)
-    for (const request of requests) {
-      if (request.date <= endTime && request.dueDate > time) {
-        res.push(request)
-      }
-    }
-    return res
-  }
+  export let getRequests: (employee: Ref<Staff>, date: Date) => Request[]
 
   $: reqs = getRequests(value._id, month)
   $: _value = display(reqs)
diff --git a/plugins/hr-resources/src/components/schedule/YearView.svelte b/plugins/hr-resources/src/components/schedule/YearView.svelte
index 5b314f558f..7ea6b2214a 100644
--- a/plugins/hr-resources/src/components/schedule/YearView.svelte
+++ b/plugins/hr-resources/src/components/schedule/YearView.svelte
@@ -19,7 +19,7 @@
   import type { Request, RequestType, Staff } from '@anticrm/hr'
   import { Label, LabelAndProps, Scroller, tooltip } from '@anticrm/ui'
   import hr from '../../plugin'
-  import { getMonth, getTotal, weekDays } from '../../utils'
+  import { fromTzDate, getMonth, getTotal, weekDays } from '../../utils'
   import RequestsPopup from '../RequestsPopup.svelte'
 
   export let currentDate: Date = new Date()
@@ -38,7 +38,7 @@
     const time = date.getTime()
     const endTime = getEndDate(date)
     for (const request of requests) {
-      if (request.date <= endTime && request.dueDate > time) {
+      if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
         res.push(request)
       }
     }
@@ -49,12 +49,11 @@
     return new Date(date).setMonth(date.getMonth() + 1)
   }
 
-  function getTooltip (requests: Request[], employee: Staff, date: Date): LabelAndProps | undefined {
+  function getTooltip (requests: Request[]): LabelAndProps | undefined {
     if (requests.length === 0) return
-    const endDate = getEndDate(date)
     return {
       component: RequestsPopup,
-      props: { date, endDate, employee: employee._id }
+      props: { requests: requests.map((it) => it._id) }
     }
   }
 
@@ -119,7 +118,7 @@
             {#each values as value, i}
               {@const month = getMonth(currentDate, value)}
               {@const requests = getRequests(employeeRequests, employee._id, month)}
-              {@const tooltipValue = getTooltip(requests, employee, month)}
+              {@const tooltipValue = getTooltip(requests)}
               {#key tooltipValue}
                 <td
                   class:today={month.getFullYear() === todayDate.getFullYear() &&
diff --git a/plugins/hr-resources/src/index.ts b/plugins/hr-resources/src/index.ts
index 5aebc05a1d..2994426cee 100644
--- a/plugins/hr-resources/src/index.ts
+++ b/plugins/hr-resources/src/index.ts
@@ -20,6 +20,9 @@ import EditDepartment from './components/EditDepartment.svelte'
 import EditRequest from './components/EditRequest.svelte'
 import Schedule from './components/Schedule.svelte'
 import Structure from './components/Structure.svelte'
+import TzDatePresenter from './components/TzDatePresenter.svelte'
+import TzDateEditor from './components/TzDateEditor.svelte'
+import RequestPresenter from './components/RequestPresenter.svelte'
 
 export default async (): Promise<Resources> => ({
   component: {
@@ -28,6 +31,9 @@ export default async (): Promise<Resources> => ({
     DepartmentStaff,
     DepartmentEditor,
     Schedule,
-    EditRequest
+    EditRequest,
+    TzDatePresenter,
+    TzDateEditor,
+    RequestPresenter
   }
 })
diff --git a/plugins/hr-resources/src/utils.ts b/plugins/hr-resources/src/utils.ts
index 868dde6341..2ad1bb17d7 100644
--- a/plugins/hr-resources/src/utils.ts
+++ b/plugins/hr-resources/src/utils.ts
@@ -1,6 +1,6 @@
 import { Employee, formatName } from '@anticrm/contact'
 import { Ref, TxOperations } from '@anticrm/core'
-import { Department, Request, RequestType } from '@anticrm/hr'
+import { Department, Request, RequestType, TzDate } from '@anticrm/hr'
 import { MessageBox } from '@anticrm/presentation'
 import { showPopup } from '@anticrm/ui'
 import hr from './plugin'
@@ -56,18 +56,27 @@ export async function addMember (client: TxOperations, employee?: Employee, valu
 /**
  * @public
  */
-export function toUTC (date: Date | number, hours = 12, mins = 0, sec = 0): number {
-  const res = new Date(date)
-  if (res.getUTCFullYear() !== res.getFullYear()) {
-    res.setUTCFullYear(res.getFullYear())
+export function toTzDate (date: Date): TzDate {
+  return {
+    year: date.getFullYear(),
+    month: date.getMonth(),
+    day: date.getDate(),
+    offset: date.getTimezoneOffset()
   }
-  if (res.getUTCMonth() !== res.getMonth()) {
-    res.setUTCMonth(res.getMonth())
-  }
-  if (res.getUTCDate() !== res.getDate()) {
-    res.setUTCDate(res.getDate())
-  }
-  return res.setUTCHours(hours, mins, sec, 0)
+}
+
+/**
+ * @public
+ */
+export function fromTzDate (tzDate: TzDate): number {
+  return new Date().setFullYear(tzDate?.year ?? 0, tzDate.month, tzDate.day)
+}
+
+/**
+ * @public
+ */
+export function tzDateEqual (tzDate: TzDate, tzDate2: TzDate): boolean {
+  return tzDate.year === tzDate2.year && tzDate.month === tzDate2.month && tzDate.day === tzDate2.day
 }
 
 /**
@@ -97,11 +106,9 @@ export function getTotal (
   let total = 0
   for (const request of requests) {
     const type = types.get(request.type)
-    let days = Math.abs((request.dueDate - request.date) / 1000 / 60 / 60 / 24)
-    if (days === 0) {
-      days = 1
-    }
-    const stDate = new Date(request.date)
+    const days =
+      Math.floor(Math.abs((1 + fromTzDate(request.tzDueDate) - fromTzDate(request.tzDate)) / 1000 / 60 / 60 / 24)) + 1
+    const stDate = new Date(fromTzDate(request.tzDate))
     const stDateDate = stDate.getDate()
     let ds = Array.from(Array(days).keys()).map((it) => stDateDate + it)
     if ((type?.value ?? -1) < 0) {
diff --git a/plugins/hr/src/index.ts b/plugins/hr/src/index.ts
index d8598fb871..e9ffa03da9 100644
--- a/plugins/hr/src/index.ts
+++ b/plugins/hr/src/index.ts
@@ -14,7 +14,7 @@
 //
 
 import type { Employee, EmployeeAccount } from '@anticrm/contact'
-import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
+import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Type } from '@anticrm/core'
 import type { Asset, IntlString, Plugin } from '@anticrm/platform'
 import { plugin } from '@anticrm/platform'
 import { Viewlet } from '@anticrm/view'
@@ -54,6 +54,16 @@ export interface RequestType extends Doc {
   color: number
 }
 
+/**
+ * @public
+ */
+export interface TzDate {
+  year: number
+  month: number
+  day: number
+  offset: number
+}
+
 /**
  * @public
  */
@@ -71,11 +81,8 @@ export interface Request extends AttachedDoc {
   attachments?: number
 
   // Date always in UTC
-  date: Timestamp
-  dueDate: Timestamp
-
-  // Timezone offset in minutes.
-  timezoneOffset: number
+  tzDate: TzDate
+  tzDueDate: TzDate
 }
 
 /**
@@ -94,7 +101,8 @@ const hr = plugin(hrId, {
     Department: '' as Ref<Class<Department>>,
     DepartmentMember: '' as Ref<Class<DepartmentMember>>,
     Request: '' as Ref<Class<Request>>,
-    RequestType: '' as Ref<Class<RequestType>>
+    RequestType: '' as Ref<Class<RequestType>>,
+    TzDate: '' as Ref<Class<Type<TzDate>>>
   },
   mixin: {
     Staff: '' as Ref<Mixin<Staff>>
@@ -121,7 +129,8 @@ const hr = plugin(hrId, {
     Overtime2: '' as Ref<RequestType>
   },
   viewlet: {
-    TableMember: '' as Ref<Viewlet>
+    TableMember: '' as Ref<Viewlet>,
+    StaffStats: '' as Ref<Viewlet>
   }
 })
 
diff --git a/plugins/tracker-resources/src/components/ProjectSelector.svelte b/plugins/tracker-resources/src/components/ProjectSelector.svelte
index e9fe226952..c634548efa 100644
--- a/plugins/tracker-resources/src/components/ProjectSelector.svelte
+++ b/plugins/tracker-resources/src/components/ProjectSelector.svelte
@@ -14,7 +14,7 @@
 -->
 <script lang="ts">
   import { Ref, SortingOrder } from '@anticrm/core'
-  import { IntlString, translate } from '@anticrm/platform'
+  import { getEmbeddedLabel, IntlString, translate } from '@anticrm/platform'
   import { createQuery } from '@anticrm/presentation'
   import { Project } from '@anticrm/tracker'
   import type { ButtonKind, ButtonSize } from '@anticrm/ui'
@@ -89,32 +89,14 @@
   }
 </script>
 
-{#if onlyIcon}
-  <Button
-    {kind}
-    {size}
-    {shape}
-    {width}
-    {justify}
-    icon={projectIcon}
-    disabled={!isEditable}
-    on:click={handleProjectEditorOpened}
-  />
-{:else}
-  <Button
-    {kind}
-    {size}
-    {shape}
-    {width}
-    {justify}
-    icon={projectIcon}
-    disabled={!isEditable}
-    on:click={handleProjectEditorOpened}
-  >
-    <svelte:fragment slot="content">
-      {#if projectText}
-        <span class="overflow-label disabled">{projectText}</span>
-      {/if}
-    </svelte:fragment>
-  </Button>
-{/if}
+<Button
+  {kind}
+  {size}
+  {shape}
+  {width}
+  {justify}
+  label={onlyIcon || projectText === undefined ? undefined : getEmbeddedLabel(projectText)}
+  icon={projectIcon}
+  disabled={!isEditable}
+  on:click={handleProjectEditorOpened}
+/>
diff --git a/plugins/tracker-resources/src/components/issues/IssuesList.svelte b/plugins/tracker-resources/src/components/issues/IssuesList.svelte
index 8b53774c0a..eafd76cf67 100644
--- a/plugins/tracker-resources/src/components/issues/IssuesList.svelte
+++ b/plugins/tracker-resources/src/components/issues/IssuesList.svelte
@@ -228,6 +228,7 @@
                   </div>
                   <Component
                     is={notification.component.NotificationPresenter}
+                    showLoading={false}
                     props={{ value: docObject, kind: 'table' }}
                   />
                 </div>