mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-11 09:51:53 +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
|
- Reactions on messages
|
||||||
|
|
||||||
|
HR:
|
||||||
|
|
||||||
|
- Leaves schedule
|
||||||
|
|
||||||
## 0.6.28
|
## 0.6.28
|
||||||
|
|
||||||
Core:
|
Core:
|
||||||
|
@ -51,7 +51,6 @@ export default mergeIds(contactId, contact, {
|
|||||||
Location: '' as IntlString,
|
Location: '' as IntlString,
|
||||||
Channel: '' as IntlString,
|
Channel: '' as IntlString,
|
||||||
ChannelProvider: '' as IntlString,
|
ChannelProvider: '' as IntlString,
|
||||||
Employee: '' as IntlString,
|
|
||||||
Value: '' as IntlString,
|
Value: '' as IntlString,
|
||||||
Phone: '' as IntlString,
|
Phone: '' as IntlString,
|
||||||
PhonePlaceholder: '' as IntlString,
|
PhonePlaceholder: '' as IntlString,
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"@anticrm/model-workbench": "~0.6.1",
|
"@anticrm/model-workbench": "~0.6.1",
|
||||||
"@anticrm/model-contact": "~0.6.1",
|
"@anticrm/model-contact": "~0.6.1",
|
||||||
"@anticrm/model-chunter": "~0.6.0",
|
"@anticrm/model-chunter": "~0.6.0",
|
||||||
|
"@anticrm/model-calendar": "~0.6.0",
|
||||||
"@anticrm/model-attachment": "~0.6.0",
|
"@anticrm/model-attachment": "~0.6.0",
|
||||||
"@anticrm/hr": "~0.6.0",
|
"@anticrm/hr": "~0.6.0",
|
||||||
"@anticrm/hr-resources": "~0.6.0",
|
"@anticrm/hr-resources": "~0.6.0",
|
||||||
|
@ -14,16 +14,35 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Employee } from '@anticrm/contact'
|
import { Employee } from '@anticrm/contact'
|
||||||
import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
|
import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
|
||||||
import { Arr, IndexKind, Ref } from '@anticrm/core'
|
import type { Department, DepartmentMember, Request, RequestType, Staff } from '@anticrm/hr'
|
||||||
import type { Department, DepartmentMember, Staff } from '@anticrm/hr'
|
import {
|
||||||
import { Builder, Index, Mixin, Model, Prop, TypeRef, Collection, TypeString, UX, ArrOf } from '@anticrm/model'
|
ArrOf,
|
||||||
import core, { TSpace } from '@anticrm/model-core'
|
Builder,
|
||||||
import workbench from '@anticrm/model-workbench'
|
Collection,
|
||||||
import hr from './plugin'
|
Hidden,
|
||||||
import view, { createAction } from '@anticrm/model-view'
|
Index,
|
||||||
|
Mixin,
|
||||||
|
Model,
|
||||||
|
Prop,
|
||||||
|
TypeDate,
|
||||||
|
TypeIntlString,
|
||||||
|
TypeMarkup,
|
||||||
|
TypeRef,
|
||||||
|
TypeString,
|
||||||
|
UX
|
||||||
|
} from '@anticrm/model'
|
||||||
import attachment from '@anticrm/model-attachment'
|
import attachment from '@anticrm/model-attachment'
|
||||||
|
import calendar from '@anticrm/model-calendar'
|
||||||
import chunter from '@anticrm/model-chunter'
|
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)
|
@Model(hr.class.Department, core.class.Space)
|
||||||
@UX(hr.string.Department, hr.icon.Department)
|
@UX(hr.string.Department, hr.icon.Department)
|
||||||
@ -64,8 +83,51 @@ export class TStaff extends TEmployee implements Staff {
|
|||||||
department!: Ref<Department>
|
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 {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TDepartment, TDepartmentMember, TStaff)
|
builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff)
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
workbench.class.Application,
|
workbench.class.Application,
|
||||||
@ -82,6 +144,13 @@ export function createModel (builder: Builder): void {
|
|||||||
icon: hr.icon.Structure,
|
icon: hr.icon.Structure,
|
||||||
label: hr.string.Structure,
|
label: hr.string.Structure,
|
||||||
position: 'top'
|
position: 'top'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'schedule',
|
||||||
|
component: hr.component.Schedule,
|
||||||
|
icon: calendar.icon.Calendar,
|
||||||
|
label: hr.string.Schedule,
|
||||||
|
position: 'top'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
spaces: []
|
spaces: []
|
||||||
@ -98,17 +167,103 @@ export function createModel (builder: Builder): void {
|
|||||||
editor: hr.component.EditDepartment
|
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, {
|
builder.mixin(hr.class.DepartmentMember, core.class.Class, view.mixin.ArrayEditor, {
|
||||||
editor: hr.component.DepartmentStaff
|
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(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: view.actionImpl.ShowPanel,
|
action: view.actionImpl.ShowPanel,
|
||||||
actionProps: {
|
actionProps: {},
|
||||||
component: hr.component.EditDepartment
|
|
||||||
},
|
|
||||||
label: view.string.Open,
|
label: view.string.Open,
|
||||||
icon: view.icon.Open,
|
icon: view.icon.Open,
|
||||||
keyBinding: ['e'],
|
keyBinding: ['e'],
|
||||||
@ -138,6 +293,22 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
hr.action.DeleteDepartment
|
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'
|
export { hrOperation } from './migration'
|
||||||
|
@ -23,19 +23,31 @@ import { Action, ActionCategory } from '@anticrm/view'
|
|||||||
export default mergeIds(hrId, hr, {
|
export default mergeIds(hrId, hr, {
|
||||||
string: {
|
string: {
|
||||||
HRApplication: '' as IntlString,
|
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: {
|
component: {
|
||||||
Structure: '' as AnyComponent,
|
Structure: '' as AnyComponent,
|
||||||
EditDepartment: '' as AnyComponent,
|
EditDepartment: '' as AnyComponent,
|
||||||
DepartmentStaff: '' as AnyComponent,
|
DepartmentStaff: '' as AnyComponent,
|
||||||
DepartmentEditor: '' as AnyComponent
|
DepartmentEditor: '' as AnyComponent,
|
||||||
|
Schedule: '' as AnyComponent,
|
||||||
|
EditRequest: '' as AnyComponent
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
HR: '' as Ref<ActionCategory>
|
HR: '' as Ref<ActionCategory>
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
EditDepartment: '' as Ref<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,
|
Array: '' as IntlString,
|
||||||
Bag: '' as IntlString,
|
Bag: '' as IntlString,
|
||||||
Name: '' 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: {
|
string: {
|
||||||
PersonAlreadyExists: '' as IntlString,
|
PersonAlreadyExists: '' as IntlString,
|
||||||
Person: '' as IntlString,
|
Person: '' as IntlString,
|
||||||
|
Employee: '' as IntlString,
|
||||||
CreateOrganization: '' as IntlString
|
CreateOrganization: '' as IntlString
|
||||||
},
|
},
|
||||||
viewlet: {
|
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,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" />
|
<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>
|
||||||
|
<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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 7.1 KiB |
@ -15,6 +15,21 @@
|
|||||||
"MoveStaff": "Employee transfer",
|
"MoveStaff": "Employee transfer",
|
||||||
"MoveStaffDescr": "Do you want to transfer employee from {current} to {department}",
|
"MoveStaffDescr": "Do you want to transfer employee from {current} to {department}",
|
||||||
"Departments": "Departments",
|
"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": "Перевод сотрудника",
|
"MoveStaff": "Перевод сотрудника",
|
||||||
"MoveStaffDescr": "Вы действительно хотите перевести сотрудника из {current} в {department}",
|
"MoveStaffDescr": "Вы действительно хотите перевести сотрудника из {current} в {department}",
|
||||||
"Departments": "Департаменты",
|
"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, {
|
loadMetadata(hr.icon, {
|
||||||
HR: `${icons}#hr`,
|
HR: `${icons}#hr`,
|
||||||
Department: `${icons}#department`,
|
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`))
|
addStringsLoader(hrId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anticrm/platform": "~0.6.6",
|
"@anticrm/platform": "~0.6.6",
|
||||||
"svelte": "^3.47",
|
"svelte": "^3.47",
|
||||||
|
"@anticrm/calendar": "~0.6.0",
|
||||||
"@anticrm/hr": "~0.6.0",
|
"@anticrm/hr": "~0.6.0",
|
||||||
"@anticrm/ui": "~0.6.0",
|
"@anticrm/ui": "~0.6.0",
|
||||||
"@anticrm/presentation": "~0.6.2",
|
"@anticrm/presentation": "~0.6.2",
|
||||||
@ -41,6 +42,8 @@
|
|||||||
"@anticrm/view": "~0.6.0",
|
"@anticrm/view": "~0.6.0",
|
||||||
"@anticrm/view-resources": "~0.6.0",
|
"@anticrm/view-resources": "~0.6.0",
|
||||||
"@anticrm/contact-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/setting": "~0.6.1",
|
||||||
"@anticrm/attachment": "~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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import contact from '@anticrm/contact'
|
||||||
import { DocumentQuery, Ref } from '@anticrm/core'
|
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 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 hr from '../plugin'
|
||||||
import CreateDepartment from './CreateDepartment.svelte'
|
import CreateDepartment from './CreateDepartment.svelte'
|
||||||
import DepartmentCard from './DepartmentCard.svelte'
|
import DepartmentCard from './DepartmentCard.svelte'
|
||||||
import { createQuery } from '@anticrm/presentation'
|
|
||||||
import contact from '@anticrm/contact'
|
|
||||||
|
|
||||||
let search = ''
|
let search = ''
|
||||||
let resultQuery: DocumentQuery<Department> = {}
|
let resultQuery: DocumentQuery<Department> = {}
|
||||||
@ -45,14 +45,10 @@
|
|||||||
head = res.find((p) => p._id === hr.ids.Head)
|
head = res.find((p) => p._id === hr.ids.Head)
|
||||||
descendants.clear()
|
descendants.clear()
|
||||||
for (const doc of res) {
|
for (const doc of res) {
|
||||||
const current = descendants.get(doc.space)
|
const current = descendants.get(doc.space) ?? []
|
||||||
if (!current) {
|
|
||||||
descendants.set(doc.space, [doc])
|
|
||||||
} else {
|
|
||||||
current.push(doc)
|
current.push(doc)
|
||||||
descendants.set(doc.space, current)
|
descendants.set(doc.space, current)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
descendants = descendants
|
descendants = descendants
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -14,16 +14,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Resources } from '@anticrm/platform'
|
import { Resources } from '@anticrm/platform'
|
||||||
import Structure from './components/Structure.svelte'
|
import DepartmentEditor from './components/DepartmentEditor.svelte'
|
||||||
import DepartmentStaff from './components/DepartmentStaff.svelte'
|
import DepartmentStaff from './components/DepartmentStaff.svelte'
|
||||||
import EditDepartment from './components/EditDepartment.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> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
component: {
|
component: {
|
||||||
Structure,
|
Structure,
|
||||||
EditDepartment,
|
EditDepartment,
|
||||||
DepartmentStaff,
|
DepartmentStaff,
|
||||||
DepartmentEditor
|
DepartmentEditor,
|
||||||
|
Schedule,
|
||||||
|
EditRequest
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -31,6 +31,12 @@ export default mergeIds(hrId, hr, {
|
|||||||
TeamLeadTooltip: '' as IntlString,
|
TeamLeadTooltip: '' as IntlString,
|
||||||
MoveStaff: '' as IntlString,
|
MoveStaff: '' as IntlString,
|
||||||
MoveStaffDescr: '' 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 { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||||
import type { Arr, Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
|
import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
|
||||||
import type { Asset, Plugin } from '@anticrm/platform'
|
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
|
||||||
import { plugin } from '@anticrm/platform'
|
import { plugin } from '@anticrm/platform'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +43,37 @@ export interface Staff extends Employee {
|
|||||||
department: Ref<Department>
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -57,7 +88,9 @@ const hr = plugin(hrId, {
|
|||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
Department: '' as Ref<Class<Department>>,
|
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: {
|
mixin: {
|
||||||
Staff: '' as Ref<Mixin<Staff>>
|
Staff: '' as Ref<Mixin<Staff>>
|
||||||
@ -65,10 +98,23 @@ const hr = plugin(hrId, {
|
|||||||
icon: {
|
icon: {
|
||||||
HR: '' as Asset,
|
HR: '' as Asset,
|
||||||
Department: '' 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: {
|
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