Fix HR statistics (#2242)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2022-07-20 22:21:52 +07:00
committed by GitHub
parent 0e0b8ab766
commit 261284df29
22 changed files with 559 additions and 308 deletions
@@ -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()
}
@@ -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}
@@ -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 } }}
/>
@@ -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} />
@@ -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}
@@ -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)
}
}
}}
/>
@@ -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} />
@@ -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}
@@ -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)}
@@ -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)
@@ -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() &&
+7 -1
View File
@@ -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
}
})
+24 -17
View File
@@ -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) {
+17 -8
View File
@@ -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>
}
})
@@ -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}
/>
@@ -228,6 +228,7 @@
</div>
<Component
is={notification.component.NotificationPresenter}
showLoading={false}
props={{ value: docObject, kind: 'table' }}
/>
</div>