mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 01:15:03 +00:00
Leaves schedule (#2135)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
dbca41afee
commit
3b8690248b
@ -11,6 +11,10 @@ Chunter:
|
||||
|
||||
- Reactions on messages
|
||||
|
||||
HR:
|
||||
|
||||
- Leaves schedule
|
||||
|
||||
## 0.6.28
|
||||
|
||||
Core:
|
||||
|
@ -51,7 +51,6 @@ export default mergeIds(contactId, contact, {
|
||||
Location: '' as IntlString,
|
||||
Channel: '' as IntlString,
|
||||
ChannelProvider: '' as IntlString,
|
||||
Employee: '' as IntlString,
|
||||
Value: '' as IntlString,
|
||||
Phone: '' as IntlString,
|
||||
PhonePlaceholder: '' as IntlString,
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@anticrm/model-workbench": "~0.6.1",
|
||||
"@anticrm/model-contact": "~0.6.1",
|
||||
"@anticrm/model-chunter": "~0.6.0",
|
||||
"@anticrm/model-calendar": "~0.6.0",
|
||||
"@anticrm/model-attachment": "~0.6.0",
|
||||
"@anticrm/hr": "~0.6.0",
|
||||
"@anticrm/hr-resources": "~0.6.0",
|
||||
|
@ -14,16 +14,35 @@
|
||||
//
|
||||
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
|
||||
import { Arr, IndexKind, Ref } from '@anticrm/core'
|
||||
import type { Department, DepartmentMember, Staff } from '@anticrm/hr'
|
||||
import { Builder, Index, Mixin, Model, Prop, TypeRef, Collection, TypeString, UX, ArrOf } from '@anticrm/model'
|
||||
import core, { TSpace } from '@anticrm/model-core'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import hr from './plugin'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
|
||||
import type { Department, DepartmentMember, Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
Collection,
|
||||
Hidden,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
TypeDate,
|
||||
TypeIntlString,
|
||||
TypeMarkup,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX
|
||||
} from '@anticrm/model'
|
||||
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 workbench from '@anticrm/model-workbench'
|
||||
import { Asset, IntlString } from '@anticrm/platform'
|
||||
import hr from './plugin'
|
||||
|
||||
export const DOMAIN_HR = 'hr' as Domain
|
||||
|
||||
@Model(hr.class.Department, core.class.Space)
|
||||
@UX(hr.string.Department, hr.icon.Department)
|
||||
@ -64,8 +83,51 @@ export class TStaff extends TEmployee implements Staff {
|
||||
department!: Ref<Department>
|
||||
}
|
||||
|
||||
@Model(hr.class.RequestType, core.class.Doc, DOMAIN_MODEL)
|
||||
@UX(hr.string.RequestType)
|
||||
export class TRequestType extends TDoc implements RequestType {
|
||||
@Prop(TypeIntlString(), core.string.Name)
|
||||
label!: IntlString
|
||||
|
||||
icon!: Asset
|
||||
value!: number
|
||||
color!: number
|
||||
}
|
||||
|
||||
@Model(hr.class.Request, core.class.AttachedDoc, DOMAIN_HR)
|
||||
@UX(hr.string.Request, hr.icon.PTO)
|
||||
export class TRequest extends TAttachedDoc implements Request {
|
||||
@Prop(TypeRef(hr.mixin.Staff), contact.string.Employee)
|
||||
declare attachedTo: Ref<Staff>
|
||||
|
||||
declare attachedToClass: Ref<Class<Staff>>
|
||||
|
||||
@Prop(TypeRef(hr.class.Department), hr.string.Department)
|
||||
declare space: Ref<Department>
|
||||
|
||||
@Prop(TypeRef(hr.class.RequestType), hr.string.RequestType)
|
||||
@Hidden()
|
||||
type!: Ref<RequestType>
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, undefined, attachment.string.Files)
|
||||
attachments?: number
|
||||
|
||||
@Prop(TypeMarkup(), core.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
|
||||
@Prop(TypeDate(false), calendar.string.Date)
|
||||
date!: Timestamp
|
||||
|
||||
@Prop(TypeDate(false), calendar.string.DueTo)
|
||||
dueDate!: Timestamp
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TDepartment, TDepartmentMember, TStaff)
|
||||
builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
@ -82,6 +144,13 @@ export function createModel (builder: Builder): void {
|
||||
icon: hr.icon.Structure,
|
||||
label: hr.string.Structure,
|
||||
position: 'top'
|
||||
},
|
||||
{
|
||||
id: 'schedule',
|
||||
component: hr.component.Schedule,
|
||||
icon: calendar.icon.Calendar,
|
||||
label: hr.string.Schedule,
|
||||
position: 'top'
|
||||
}
|
||||
],
|
||||
spaces: []
|
||||
@ -98,17 +167,103 @@ export function createModel (builder: Builder): void {
|
||||
editor: hr.component.EditDepartment
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Request, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: hr.component.EditRequest
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.DepartmentMember, core.class.Class, view.mixin.ArrayEditor, {
|
||||
editor: hr.component.DepartmentStaff
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.Vacation,
|
||||
icon: hr.icon.Vacation,
|
||||
color: 2,
|
||||
value: 0
|
||||
},
|
||||
hr.ids.Vacation
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.Sick,
|
||||
icon: hr.icon.Sick,
|
||||
color: 11,
|
||||
value: -1
|
||||
},
|
||||
hr.ids.Sick
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.PTO,
|
||||
icon: hr.icon.PTO,
|
||||
color: 9,
|
||||
value: -1
|
||||
},
|
||||
hr.ids.PTO
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.PTO2,
|
||||
icon: hr.icon.PTO,
|
||||
color: 9,
|
||||
value: -0.5
|
||||
},
|
||||
hr.ids.PTO2
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.Overtime,
|
||||
icon: hr.icon.Overtime,
|
||||
color: 5,
|
||||
value: 1
|
||||
},
|
||||
hr.ids.Overtime
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.Overtime2,
|
||||
icon: hr.icon.Overtime,
|
||||
color: 5,
|
||||
value: 0.5
|
||||
},
|
||||
hr.ids.Overtime2
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: hr.string.Remote,
|
||||
icon: hr.icon.Remote,
|
||||
color: 4,
|
||||
value: 0
|
||||
},
|
||||
hr.ids.Remote
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPanel,
|
||||
actionProps: {
|
||||
component: hr.component.EditDepartment
|
||||
},
|
||||
actionProps: {},
|
||||
label: view.string.Open,
|
||||
icon: view.icon.Open,
|
||||
keyBinding: ['e'],
|
||||
@ -138,6 +293,22 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
hr.action.DeleteDepartment
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPanel,
|
||||
actionProps: {},
|
||||
label: view.string.Open,
|
||||
icon: view.icon.Open,
|
||||
keyBinding: ['e'],
|
||||
input: 'any',
|
||||
category: hr.category.HR,
|
||||
target: hr.class.Request,
|
||||
context: { mode: 'context', application: hr.app.HR, group: 'top' }
|
||||
},
|
||||
hr.action.EditRequest
|
||||
)
|
||||
}
|
||||
|
||||
export { hrOperation } from './migration'
|
||||
|
@ -23,19 +23,31 @@ import { Action, ActionCategory } from '@anticrm/view'
|
||||
export default mergeIds(hrId, hr, {
|
||||
string: {
|
||||
HRApplication: '' as IntlString,
|
||||
Departments: '' as IntlString
|
||||
Departments: '' as IntlString,
|
||||
Request: '' as IntlString,
|
||||
Vacation: '' as IntlString,
|
||||
Sick: '' as IntlString,
|
||||
PTO: '' as IntlString,
|
||||
PTO2: '' as IntlString,
|
||||
Remote: '' as IntlString,
|
||||
Overtime: '' as IntlString,
|
||||
Overtime2: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
Structure: '' as AnyComponent,
|
||||
EditDepartment: '' as AnyComponent,
|
||||
DepartmentStaff: '' as AnyComponent,
|
||||
DepartmentEditor: '' as AnyComponent
|
||||
DepartmentEditor: '' as AnyComponent,
|
||||
Schedule: '' as AnyComponent,
|
||||
EditRequest: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
HR: '' as Ref<ActionCategory>
|
||||
},
|
||||
action: {
|
||||
EditDepartment: '' as Ref<Action>,
|
||||
DeleteDepartment: '' as Ref<Action>
|
||||
DeleteDepartment: '' as Ref<Action>,
|
||||
EditRequest: '' as Ref<Action>,
|
||||
DeleteRequest: '' as Ref<Action>
|
||||
}
|
||||
})
|
||||
|
@ -133,7 +133,7 @@ export default plugin(coreId, {
|
||||
Array: '' as IntlString,
|
||||
Bag: '' as IntlString,
|
||||
Name: '' as IntlString,
|
||||
Description: '' as IntlString,
|
||||
Enum: '' as IntlString
|
||||
Enum: '' as IntlString,
|
||||
Description: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -213,6 +213,7 @@ const contactPlugin = plugin(contactId, {
|
||||
string: {
|
||||
PersonAlreadyExists: '' as IntlString,
|
||||
Person: '' as IntlString,
|
||||
Employee: '' as IntlString,
|
||||
CreateOrganization: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
|
@ -10,4 +10,23 @@
|
||||
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
|
||||
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
|
||||
</symbol>
|
||||
<symbol id="vacation" viewBox="0 0 34 34">
|
||||
<path d="M20.431,30.09c0.555-2.625,1.982-11.16-1.273-17.413c0,0,6.072,0.623,5.762,8.408c0,0,5.295-10.588-3.581-12.612 c0,0,2.803-6.696,9.653-4.049c0,0-5.513-6.188-13.417,1.082C16.751,3.23,17.289,0,17.289,0c-3.264,1.591-3.846,4.54-3.817,6.557 c-2.88-0.541-7.92,0.586-9.382,5.806c0,0,5.5-3.833,9.833-1.5c0,0-7.689,2.126-8.001,9.443c0,0,3.205-3.939,6.51-4.904 c-0.268,0.256-0.437,0.613-0.437,1.013c0,0.774,0.628,1.401,1.401,1.401c0.281,0,0.542-0.084,0.761-0.227 c-0.182,0.236-0.294,0.527-0.294,0.85c0,0.773,0.628,1.402,1.401,1.402c0.674,0,1.235-0.478,1.37-1.109 c0.408,2.896,0.452,7.51-2.268,11.199c-1.687-0.976-2.462-2.771-2.796-3.969c0.16,0.223,0.46,0.312,0.722,0.196 c0.302-0.131,0.438-0.481,0.307-0.783c-0.055-0.125-0.147-0.221-0.259-0.28c0.109,0.02,0.226,0.006,0.335-0.041 c0.302-0.133,0.438-0.482,0.307-0.785c-0.067-0.154-0.194-0.267-0.342-0.318c1.451-0.188,3.37,0.801,3.37,0.801 c-1.367-2.797-4.725-2.314-4.725-2.314c1.29-1.646,4.085-1.09,4.085-1.09c-1.459-1.785-3.613-1.365-4.643-0.664 c-0.333-0.789-1.062-1.838-2.604-1.901c0,0,0.76,1.166,0.826,2.192c-4.317-1.483-5.409,1.863-5.409,1.863 c2.218-2.197,4.449-0.066,4.449-0.066c-3.111,2.3,0.755,5.521,0.755,5.521c-1.448-2.979,0.811-4.256,0.811-4.256 c-0.163,2.396,1.092,4.842,1.94,6.2c-4.803,0.354-8.146,1.136-8.146,2.046c0,1.241,6.231,2.25,13.917,2.25 c7.687,0,13.917-1.009,13.917-2.25C31.182,31.213,26.589,30.324,20.431,30.09z M14.083,15.2c0.182,0.013,0.362,0.042,0.541,0.082 c-0.053,0.085-0.096,0.177-0.13,0.272C14.381,15.411,14.243,15.29,14.083,15.2z M14.503,17.264c0.087-0.114,0.157-0.24,0.206-0.379 c0.059,0.075,0.124,0.143,0.196,0.204C14.761,17.127,14.625,17.185,14.503,17.264z M16.166,17.375 c0.075-0.02,0.148-0.043,0.218-0.074c0.028,0.136,0.058,0.283,0.086,0.437C16.39,17.6,16.287,17.478,16.166,17.375z M12.003,24.915 c0.042,0.045,0.091,0.082,0.145,0.11c-0.062-0.01-0.124-0.008-0.187,0.003C11.979,24.994,11.993,24.954,12.003,24.915z M11.959,24.154c-0.046,0.062-0.08,0.133-0.1,0.207c-0.029-0.031-0.062-0.061-0.097-0.084 C11.826,24.23,11.891,24.189,11.959,24.154z M11.462,25.549c-0.015-0.066-0.028-0.127-0.041-0.187c0.032,0,0.065-0.005,0.098-0.009 C11.49,25.415,11.47,25.48,11.462,25.549z"/>
|
||||
</symbol>
|
||||
<symbol id="sick" viewBox="0 0 24 24">
|
||||
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
|
||||
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
|
||||
</symbol>
|
||||
<symbol id="pto" viewBox="0 0 24 24">
|
||||
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
|
||||
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
|
||||
</symbol>
|
||||
<symbol id="remote" viewBox="0 0 24 24">
|
||||
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
|
||||
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
|
||||
</symbol>
|
||||
<symbol id="overtime" viewBox="0 0 24 24">
|
||||
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
|
||||
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 7.1 KiB |
@ -15,6 +15,21 @@
|
||||
"MoveStaff": "Employee transfer",
|
||||
"MoveStaffDescr": "Do you want to transfer employee from {current} to {department}",
|
||||
"Departments": "Departments",
|
||||
"AddEmployee": "Add employee"
|
||||
"ShowEmployees": "Show employees",
|
||||
"AddEmployee": "Add employee",
|
||||
"Schedule": "Schedule",
|
||||
"RequestType": "Type",
|
||||
"CreateRequest": "Create {type}",
|
||||
"Today": "Today",
|
||||
"NoEmployeesInDepartment": "There are no employees in the selected department",
|
||||
"Vacation": "Vacation",
|
||||
"Sick": "Sick",
|
||||
"PTO": "PTO",
|
||||
"Remote": "Remote",
|
||||
"Overtime": "Overtime",
|
||||
"PTO2": "PTO/2",
|
||||
"Overtime2": "Overtime/2",
|
||||
"EditRequest": "Edit {type}",
|
||||
"Request": "Request"
|
||||
}
|
||||
}
|
@ -15,6 +15,21 @@
|
||||
"MoveStaff": "Перевод сотрудника",
|
||||
"MoveStaffDescr": "Вы действительно хотите перевести сотрудника из {current} в {department}",
|
||||
"Departments": "Департаменты",
|
||||
"AddEmployee": "Добавить сотрудника"
|
||||
"ShowEmployees": "Просмотреть сотрудников",
|
||||
"AddEmployee": "Добавить сотрудника",
|
||||
"Schedule": "График",
|
||||
"RequestType": "Тип",
|
||||
"CreateRequest": "Создать {type}",
|
||||
"Today": "Сегодня",
|
||||
"NoEmployeesInDepartment": "Нет сотрудников в выбранном департаменте",
|
||||
"Vacation": "Отпуск",
|
||||
"Sick": "Больничный",
|
||||
"PTO": "PTO",
|
||||
"Remote": "Удаленно",
|
||||
"Overtime": "Переработка",
|
||||
"PTO2": "PTO/2",
|
||||
"Overtime2": "Переработка/2",
|
||||
"EditRequest": "Редактировать {type}",
|
||||
"Request": "Запрос"
|
||||
}
|
||||
}
|
@ -20,7 +20,12 @@ const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(hr.icon, {
|
||||
HR: `${icons}#hr`,
|
||||
Department: `${icons}#department`,
|
||||
Structure: `${icons}#structure`
|
||||
Structure: `${icons}#structure`,
|
||||
Vacation: `${icons}#vacation`,
|
||||
Sick: `${icons}#sick`,
|
||||
PTO: `${icons}#pto`,
|
||||
Overtime: `${icons}#overtime`,
|
||||
Remote: `${icons}#remote`
|
||||
})
|
||||
|
||||
addStringsLoader(hrId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
@ -32,6 +32,7 @@
|
||||
"dependencies": {
|
||||
"@anticrm/platform": "~0.6.6",
|
||||
"svelte": "^3.47",
|
||||
"@anticrm/calendar": "~0.6.0",
|
||||
"@anticrm/hr": "~0.6.0",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/presentation": "~0.6.2",
|
||||
@ -41,6 +42,8 @@
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
"@anticrm/contact-resources": "~0.6.0",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/text-editor": "~0.6.0",
|
||||
"@anticrm/setting": "~0.6.1",
|
||||
"@anticrm/attachment": "~0.6.1"
|
||||
}
|
||||
|
119
plugins/hr-resources/src/components/CreateRequest.svelte
Normal file
119
plugins/hr-resources/src/components/CreateRequest.svelte
Normal file
@ -0,0 +1,119 @@
|
||||
<!--
|
||||
// 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 { AttachmentStyledBox } from '@anticrm/attachment-resources'
|
||||
import calendar from '@anticrm/calendar'
|
||||
import core, { generateId, Ref } from '@anticrm/core'
|
||||
import { Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import { Card, createQuery, getClient } from '@anticrm/presentation'
|
||||
import ui, { Button, DateRangePresenter, DropdownLabelsIntl, IconAttachment } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import hr from '../plugin'
|
||||
|
||||
export let staff: Staff
|
||||
export let date: Date
|
||||
let description: string = ''
|
||||
|
||||
const objectId: Ref<Request> = generateId()
|
||||
let descriptionBox: AttachmentStyledBox
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const typesQuery = createQuery()
|
||||
|
||||
let types: RequestType[] = []
|
||||
let type: RequestType | undefined = undefined
|
||||
let typeLabel = ''
|
||||
$: type && translate(type.label, {}).then((p) => (typeLabel = p))
|
||||
|
||||
typesQuery.query(hr.class.RequestType, {}, (res) => {
|
||||
types = res
|
||||
if (type === undefined) {
|
||||
type = types[0]
|
||||
}
|
||||
})
|
||||
|
||||
$: value = new Date(date).getTime()
|
||||
$: dueDate = new Date(value).setDate(new Date(value).getDate() + 1)
|
||||
|
||||
export function canClose (): boolean {
|
||||
return description.length === 0
|
||||
}
|
||||
|
||||
async function saveRequest () {
|
||||
let date: number | undefined
|
||||
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,
|
||||
type: type._id,
|
||||
date,
|
||||
dueDate,
|
||||
description,
|
||||
collection: 'requests'
|
||||
})
|
||||
await descriptionBox.createAttachments()
|
||||
}
|
||||
|
||||
function typeSelected (_id: Ref<RequestType>): void {
|
||||
type = types.find((p) => p._id === _id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={hr.string.CreateRequest}
|
||||
labelProps={{ type: typeLabel }}
|
||||
okAction={saveRequest}
|
||||
canSave={value !== undefined}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<DropdownLabelsIntl
|
||||
items={types.map((p) => {
|
||||
return { id: p._id, label: p.label }
|
||||
})}
|
||||
placeholder={hr.string.RequestType}
|
||||
label={hr.string.RequestType}
|
||||
on:selected={(e) => typeSelected(e.detail)}
|
||||
/>
|
||||
<AttachmentStyledBox
|
||||
bind:this={descriptionBox}
|
||||
{objectId}
|
||||
_class={hr.class.Request}
|
||||
space={staff.department}
|
||||
alwaysEdit
|
||||
showButtons={false}
|
||||
maxHeight={'card'}
|
||||
bind:content={description}
|
||||
placeholder={core.string.Description}
|
||||
/>
|
||||
<svelte:fragment slot="pool">
|
||||
<DateRangePresenter bind:value editable labelNull={ui.string.SelectDate} />
|
||||
<DateRangePresenter bind:value={dueDate} labelNull={calendar.string.DueTo} editable />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button
|
||||
icon={IconAttachment}
|
||||
kind={'transparent'}
|
||||
on:click={() => {
|
||||
descriptionBox.attach()
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Card>
|
55
plugins/hr-resources/src/components/EditRequest.svelte
Normal file
55
plugins/hr-resources/src/components/EditRequest.svelte
Normal file
@ -0,0 +1,55 @@
|
||||
<!--
|
||||
// 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 core from '@anticrm/core'
|
||||
import { Request } from '@anticrm/hr'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { StyledTextArea } from '@anticrm/text-editor'
|
||||
import { createFocusManager, FocusHandler } from '@anticrm/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
export let object: Request
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
async function onChangeDescription (): Promise<void> {
|
||||
if (object === undefined) return
|
||||
await client.update(object, {
|
||||
description: object.description
|
||||
})
|
||||
}
|
||||
|
||||
const manager = createFocusManager()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['comments', 'description']
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-stretch flex-grow">
|
||||
<StyledTextArea
|
||||
bind:content={object.description}
|
||||
placeholder={core.string.Description}
|
||||
showButtons={false}
|
||||
on:value={onChangeDescription}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
35
plugins/hr-resources/src/components/RequestsPopup.svelte
Normal file
35
plugins/hr-resources/src/components/RequestsPopup.svelte
Normal file
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
// 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 { Ref, SortingOrder } from '@anticrm/core'
|
||||
import hr, { Staff } from '@anticrm/hr'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
|
||||
export let date: Date
|
||||
export let employee: Ref<Staff>
|
||||
|
||||
$: endDate = new Date(date).setDate(date.getDate() + 1)
|
||||
</script>
|
||||
|
||||
<Table
|
||||
_class={hr.class.Request}
|
||||
query={{
|
||||
attachedTo: employee,
|
||||
dueDate: { $gte: date.getTime() },
|
||||
date: { $lt: endDate }
|
||||
}}
|
||||
config={['$lookup.type.label', 'date', 'dueDate']}
|
||||
options={{ sort: { date: SortingOrder.Ascending } }}
|
||||
/>
|
94
plugins/hr-resources/src/components/Schedule.svelte
Normal file
94
plugins/hr-resources/src/components/Schedule.svelte
Normal file
@ -0,0 +1,94 @@
|
||||
<!--
|
||||
// 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 calendar from '@anticrm/calendar'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { Department } from '@anticrm/hr'
|
||||
import { createQuery, SpaceSelector } from '@anticrm/presentation'
|
||||
import { Button, Icon, IconBack, IconForward, Label } from '@anticrm/ui'
|
||||
import hr from '../plugin'
|
||||
import ScheduleView from './ScheduleView.svelte'
|
||||
|
||||
let department = hr.ids.Head
|
||||
let currentDate: Date = new Date()
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let descendants: Map<Ref<Department>, Department[]> = new Map<Ref<Department>, Department[]>()
|
||||
let departments: Map<Ref<Department>, Department> = new Map<Ref<Department>, Department>()
|
||||
|
||||
query.query(hr.class.Department, {}, (res) => {
|
||||
departments.clear()
|
||||
descendants.clear()
|
||||
for (const doc of res) {
|
||||
const current = descendants.get(doc.space) ?? []
|
||||
current.push(doc)
|
||||
descendants.set(doc.space, current)
|
||||
departments.set(doc._id, doc)
|
||||
}
|
||||
departments = departments
|
||||
descendants = descendants
|
||||
})
|
||||
|
||||
function inc (val: number): void {
|
||||
currentDate.setMonth(currentDate.getMonth() + val)
|
||||
currentDate = currentDate
|
||||
}
|
||||
|
||||
function getMonthName (date: Date): string {
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
month: 'long'
|
||||
}).format(date)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<div class="ac-header__icon"><Icon icon={calendar.icon.Calendar} size={'small'} /></div>
|
||||
<span class="ac-header__title"><Label label={hr.string.Schedule} /></span>
|
||||
<div class="flex ml-4 gap-2">
|
||||
<Button
|
||||
icon={IconBack}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
inc(-1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={'small'}
|
||||
label={hr.string.Today}
|
||||
on:click={() => {
|
||||
currentDate = new Date()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconForward}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
inc(1)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="fs-title ml-4 flex-row-center">
|
||||
{getMonthName(currentDate)}
|
||||
{currentDate.getFullYear()}
|
||||
</div>
|
||||
</div>
|
||||
<SpaceSelector _class={hr.class.Department} label={hr.string.Department} bind:space={department} />
|
||||
</div>
|
||||
|
||||
<div class="mr-6 h-full">
|
||||
<ScheduleView {department} {descendants} departmentById={departments} {currentDate} />
|
||||
</div>
|
76
plugins/hr-resources/src/components/ScheduleRequests.svelte
Normal file
76
plugins/hr-resources/src/components/ScheduleRequests.svelte
Normal file
@ -0,0 +1,76 @@
|
||||
<!--
|
||||
// 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 hr, { Request, RequestType } from '@anticrm/hr'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { areDatesEqual, getPlatformColor, Icon, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
|
||||
export let requests: Request[]
|
||||
export let date: Date
|
||||
export let editable: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function getType (request: Request): Promise<RequestType | undefined> {
|
||||
return await client.findOne(hr.class.RequestType, {
|
||||
_id: request.type
|
||||
})
|
||||
}
|
||||
|
||||
function getStyle (type: RequestType): string {
|
||||
let res = `background-color: ${getPlatformColor(type.color)};`
|
||||
if (Math.abs(type.value % 1) === 0.5) {
|
||||
res += ' height: 50%;'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function click (e: MouseEvent, request: Request) {
|
||||
if (!editable) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
showPopup(ContextMenu, { object: request }, e.target as HTMLElement)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full relative">
|
||||
{#each requests as request}
|
||||
{#await getType(request) then type}
|
||||
{#if type}
|
||||
<div
|
||||
class="request"
|
||||
class:cursor-pointer={editable}
|
||||
style={getStyle(type)}
|
||||
on:click={(e) => {
|
||||
click(e, request)
|
||||
}}
|
||||
>
|
||||
{#if areDatesEqual(new Date(request.date), date) || date.getDate() === 1}
|
||||
<Icon icon={type.icon} size="large" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.request {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
244
plugins/hr-resources/src/components/ScheduleView.svelte
Normal file
244
plugins/hr-resources/src/components/ScheduleView.svelte
Normal file
@ -0,0 +1,244 @@
|
||||
<!--
|
||||
// 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 { getCurrentAccount, Ref, Timestamp } from '@anticrm/core'
|
||||
import type { Department, Request, Staff } from '@anticrm/hr'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import {
|
||||
Label,
|
||||
daysInMonth,
|
||||
isWeekend,
|
||||
areDatesEqual,
|
||||
day as getDay,
|
||||
getWeekDayName,
|
||||
showPopup,
|
||||
eventToHTMLElement,
|
||||
Scroller,
|
||||
tooltip,
|
||||
LabelAndProps
|
||||
} from '@anticrm/ui'
|
||||
import { EmployeePresenter } from '@anticrm/contact-resources'
|
||||
import contact from '@anticrm/contact-resources/src/plugin'
|
||||
import hr from '../plugin'
|
||||
import { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import CreateRequest from './CreateRequest.svelte'
|
||||
import ScheduleRequests from './ScheduleRequests.svelte'
|
||||
import RequestsPopup from './RequestsPopup.svelte'
|
||||
|
||||
export let department: Ref<Department>
|
||||
export let descendants: Map<Ref<Department>, Department[]>
|
||||
export let departmentById: Map<Ref<Department>, Department>
|
||||
export let currentDate: Date = new Date()
|
||||
|
||||
$: startDate = new Date(new Date(currentDate).setDate(1)).setHours(0, 0, 0, 0)
|
||||
$: endDate = new Date(startDate).setMonth(new Date(startDate).getMonth() + 1)
|
||||
$: departments = [department, ...getDescendants(department, descendants)]
|
||||
|
||||
const lq = createQuery()
|
||||
const staffQuery = createQuery()
|
||||
let staff: Staff[] = []
|
||||
|
||||
staffQuery.query(hr.mixin.Staff, {}, (res) => {
|
||||
staff = res
|
||||
})
|
||||
|
||||
let employeeRequests = new Map<Ref<Staff>, Request[]>()
|
||||
|
||||
function getDescendants (
|
||||
department: Ref<Department>,
|
||||
descendants: Map<Ref<Department>, Department[]>
|
||||
): Ref<Department>[] {
|
||||
const res = (descendants.get(department) ?? []).map((p) => p._id)
|
||||
for (const department of res) {
|
||||
res.push(...getDescendants(department, descendants))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function update (departments: Ref<Department>[], startDate: Timestamp, endDate: Timestamp) {
|
||||
lq.query(
|
||||
hr.class.Request,
|
||||
{
|
||||
dueDate: { $gte: startDate },
|
||||
date: { $lt: endDate },
|
||||
space: { $in: departments }
|
||||
},
|
||||
(res) => {
|
||||
employeeRequests.clear()
|
||||
for (const request of res) {
|
||||
const requests = employeeRequests.get(request.attachedTo) ?? []
|
||||
requests.push(request)
|
||||
employeeRequests.set(request.attachedTo, requests)
|
||||
}
|
||||
employeeRequests = employeeRequests
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$: update(departments, startDate, endDate)
|
||||
|
||||
const todayDate = new Date()
|
||||
|
||||
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 = new Date(date).setDate(date.getDate() + 1)
|
||||
for (const request of requests) {
|
||||
if (request.date < endTime && request.dueDate > time) {
|
||||
res.push(request)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function createRequest (e: MouseEvent, date: Date, staff: Staff): void {
|
||||
if (!isEditable(staff)) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
showPopup(
|
||||
CreateRequest,
|
||||
{
|
||||
staff,
|
||||
date
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
const currentEmployee = (getCurrentAccount() as EmployeeAccount).employee
|
||||
|
||||
function getTeamLead (_id: Ref<Department>): Ref<Employee> | undefined {
|
||||
const department = departmentById.get(_id)
|
||||
if (department === undefined) return
|
||||
if (department.teamLead != null) return department.teamLead
|
||||
return getTeamLead(department.space)
|
||||
}
|
||||
|
||||
function isEditable (employee: Staff): boolean {
|
||||
if (employee._id === currentEmployee) return true
|
||||
const lead = getTeamLead(employee.department)
|
||||
return lead === currentEmployee
|
||||
}
|
||||
|
||||
function getTooltip (employee: Staff, date: Date): LabelAndProps | undefined {
|
||||
const requests = getRequests(employee._id, date)
|
||||
if (requests.length === 0) return
|
||||
return {
|
||||
component: RequestsPopup,
|
||||
props: { date, employee: employee._id }
|
||||
}
|
||||
}
|
||||
|
||||
$: departmentStaff = staff.filter((p) => departments.includes(p.department) || employeeRequests.has(p._id))
|
||||
</script>
|
||||
|
||||
{#if departmentStaff.length}
|
||||
<Scroller>
|
||||
<table>
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<th>
|
||||
<Label label={contact.string.Employee} />
|
||||
</th>
|
||||
{#each [...Array(daysInMonth(currentDate)).keys()] as dayOfMonth}
|
||||
{@const day = getDay(new Date(startDate), dayOfMonth)}
|
||||
<th class:today={areDatesEqual(todayDate, day)} class:weekend={isWeekend(day)}>
|
||||
<div class="cursor-pointer uppercase flex-col-center">
|
||||
<div class="flex-center">{getWeekDayName(day, 'short')}</div>
|
||||
<div class="flex-center">{day.getDate()}</div>
|
||||
</div>
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each departmentStaff as employee}
|
||||
<tr>
|
||||
<td>
|
||||
<EmployeePresenter value={employee} />
|
||||
</td>
|
||||
{#each [...Array(daysInMonth(currentDate)).keys()] as dayOfMonth}
|
||||
{@const date = getDay(new Date(startDate), dayOfMonth)}
|
||||
{@const requests = getRequests(employee._id, date)}
|
||||
{@const editable = isEditable(employee)}
|
||||
<td
|
||||
class:today={areDatesEqual(todayDate, date)}
|
||||
class:weekend={isWeekend(date)}
|
||||
class:cursor-pointer={editable}
|
||||
use:tooltip={getTooltip(employee, date)}
|
||||
on:click={(e) => createRequest(e, date, employee)}
|
||||
>
|
||||
{#if requests.length}
|
||||
<ScheduleRequests {requests} {date} {editable} />
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</Scroller>
|
||||
{:else}
|
||||
<div class="flex-center h-full flex-grow fs-title">
|
||||
<Label label={hr.string.NoEmployeesInDepartment} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: auto;
|
||||
position: relative;
|
||||
|
||||
td,
|
||||
th {
|
||||
width: auto;
|
||||
min-width: 1rem;
|
||||
&:first-child {
|
||||
width: 15rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
th {
|
||||
padding: 0.5rem;
|
||||
height: 2.5rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
color: var(--dark-color);
|
||||
box-shadow: inset 0 -1px 0 0 var(--divider-color);
|
||||
user-select: none;
|
||||
&.today {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
&.weekend:not(.today) {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
}
|
||||
td {
|
||||
height: 3.5rem;
|
||||
border: 1px solid var(--divider-color);
|
||||
color: var(--caption-color);
|
||||
&.today {
|
||||
background-color: var(--theme-bg-accent-hover);
|
||||
}
|
||||
&.weekend:not(.today) {
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,14 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { DocumentQuery, Ref } from '@anticrm/core'
|
||||
import { Button, Icon, Label, Scroller, SearchEdit, showPopup, IconAdd, eventToHTMLElement } from '@anticrm/ui'
|
||||
import type { Department } from '@anticrm/hr'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Button, eventToHTMLElement, Icon, IconAdd, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
|
||||
import hr from '../plugin'
|
||||
import CreateDepartment from './CreateDepartment.svelte'
|
||||
import DepartmentCard from './DepartmentCard.svelte'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import contact from '@anticrm/contact'
|
||||
|
||||
let search = ''
|
||||
let resultQuery: DocumentQuery<Department> = {}
|
||||
@ -45,14 +45,10 @@
|
||||
head = res.find((p) => p._id === hr.ids.Head)
|
||||
descendants.clear()
|
||||
for (const doc of res) {
|
||||
const current = descendants.get(doc.space)
|
||||
if (!current) {
|
||||
descendants.set(doc.space, [doc])
|
||||
} else {
|
||||
const current = descendants.get(doc.space) ?? []
|
||||
current.push(doc)
|
||||
descendants.set(doc.space, current)
|
||||
}
|
||||
}
|
||||
descendants = descendants
|
||||
},
|
||||
{
|
||||
|
@ -14,16 +14,20 @@
|
||||
//
|
||||
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import Structure from './components/Structure.svelte'
|
||||
import DepartmentEditor from './components/DepartmentEditor.svelte'
|
||||
import DepartmentStaff from './components/DepartmentStaff.svelte'
|
||||
import EditDepartment from './components/EditDepartment.svelte'
|
||||
import DepartmentEditor from './components/DepartmentEditor.svelte'
|
||||
import EditRequest from './components/EditRequest.svelte'
|
||||
import Schedule from './components/Schedule.svelte'
|
||||
import Structure from './components/Structure.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
Structure,
|
||||
EditDepartment,
|
||||
DepartmentStaff,
|
||||
DepartmentEditor
|
||||
DepartmentEditor,
|
||||
Schedule,
|
||||
EditRequest
|
||||
}
|
||||
})
|
||||
|
@ -31,6 +31,12 @@ export default mergeIds(hrId, hr, {
|
||||
TeamLeadTooltip: '' as IntlString,
|
||||
MoveStaff: '' as IntlString,
|
||||
MoveStaffDescr: '' as IntlString,
|
||||
AddEmployee: '' as IntlString
|
||||
AddEmployee: '' as IntlString,
|
||||
RequestType: '' as IntlString,
|
||||
Schedule: '' as IntlString,
|
||||
EditRequest: '' as IntlString,
|
||||
CreateRequest: '' as IntlString,
|
||||
Today: '' as IntlString,
|
||||
NoEmployeesInDepartment: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -14,8 +14,8 @@
|
||||
//
|
||||
|
||||
import type { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import type { Arr, Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
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'
|
||||
|
||||
/**
|
||||
@ -43,6 +43,37 @@ export interface Staff extends Employee {
|
||||
department: Ref<Department>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface RequestType extends Doc {
|
||||
label: IntlString
|
||||
icon: Asset
|
||||
value: number
|
||||
color: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Request extends AttachedDoc {
|
||||
attachedTo: Ref<Staff>
|
||||
|
||||
attachedToClass: Ref<Class<Staff>>
|
||||
|
||||
space: Ref<Department>
|
||||
|
||||
type: Ref<RequestType>
|
||||
|
||||
description: Markup
|
||||
comments?: number
|
||||
attachments?: number
|
||||
|
||||
date: Timestamp
|
||||
|
||||
dueDate: Timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -57,7 +88,9 @@ const hr = plugin(hrId, {
|
||||
},
|
||||
class: {
|
||||
Department: '' as Ref<Class<Department>>,
|
||||
DepartmentMember: '' as Ref<Class<DepartmentMember>>
|
||||
DepartmentMember: '' as Ref<Class<DepartmentMember>>,
|
||||
Request: '' as Ref<Class<Request>>,
|
||||
RequestType: '' as Ref<Class<RequestType>>
|
||||
},
|
||||
mixin: {
|
||||
Staff: '' as Ref<Mixin<Staff>>
|
||||
@ -65,10 +98,23 @@ const hr = plugin(hrId, {
|
||||
icon: {
|
||||
HR: '' as Asset,
|
||||
Department: '' as Asset,
|
||||
Structure: '' as Asset
|
||||
Structure: '' as Asset,
|
||||
Vacation: '' as Asset,
|
||||
Sick: '' as Asset,
|
||||
PTO: '' as Asset,
|
||||
Remote: '' as Asset,
|
||||
Overtime: '' as Asset
|
||||
},
|
||||
ids: {
|
||||
Head: '' as Ref<Department>
|
||||
Head: '' as Ref<Department>,
|
||||
Vacation: '' as Ref<RequestType>,
|
||||
Leave: '' as Ref<RequestType>,
|
||||
Sick: '' as Ref<RequestType>,
|
||||
PTO: '' as Ref<RequestType>,
|
||||
PTO2: '' as Ref<RequestType>,
|
||||
Remote: '' as Ref<RequestType>,
|
||||
Overtime: '' as Ref<RequestType>,
|
||||
Overtime2: '' as Ref<RequestType>
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user