mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-07 18:27:44 +02:00
Candidate mixins (#745)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Vendored
+1
-1
@@ -51,7 +51,7 @@
|
||||
"name": "Debug tool",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/index.ts", "restore-workspace", "ws1", "../../temp/ws1/"],
|
||||
"args": ["src/index.ts", "upgrade-workspace", "ws1"],
|
||||
"env": {
|
||||
"MINIO_ACCESS_KEY":"minioadmin",
|
||||
"MINIO_SECRET_KEY":"minioadmin",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import contact, { Employee, EmployeeAccount, Person } from '@anticrm/contact'
|
||||
import core, {
|
||||
AttachedData,
|
||||
Data,
|
||||
generateId,
|
||||
MeasureContext,
|
||||
MeasureMetricsContext, metricsToString, Ref,
|
||||
MeasureMetricsContext, metricsToString, MixinUpdate, Ref,
|
||||
TxOperations
|
||||
} from '@anticrm/core'
|
||||
import recruit from '@anticrm/model-recruit'
|
||||
@@ -160,7 +160,7 @@ async function genApplicant (
|
||||
// Update or create candidate
|
||||
await findOrUpdateAttached(ctx, client, vacancyId, recruit.class.Applicant, applicantId, applicant, {
|
||||
attachedTo: candidateId,
|
||||
attachedClass: recruit.class.Candidate,
|
||||
attachedClass: recruit.mixin.Candidate,
|
||||
collection: 'applications'
|
||||
})
|
||||
|
||||
@@ -222,23 +222,28 @@ async function genCandidate (
|
||||
minio.putObject(dbName, imgId, jpegImageData.data, jpegImageData.data.length, { 'Content-Type': 'image/jpeg' })
|
||||
)
|
||||
}
|
||||
const candidate: Data<Candidate> = {
|
||||
const candidate: Data<Person> = {
|
||||
name: fName + ',' + lName,
|
||||
city: faker.address.city(),
|
||||
title: faker.name.title(),
|
||||
channels: [{ provider: contact.channelProvider.Email, value: faker.internet.email(fName, lName) }],
|
||||
avatar: imgId
|
||||
}
|
||||
|
||||
const candidateMixin: MixinUpdate<Person, Candidate> = {
|
||||
title: faker.name.title(),
|
||||
onsite: faker.datatype.boolean(),
|
||||
remote: faker.datatype.boolean(),
|
||||
avatar: imgId,
|
||||
source: faker.lorem.lines(1)
|
||||
}
|
||||
|
||||
const candidateId = (options.random ? `candidate-${generateId()}-${i}` : `candidate-genid-${i}`) as Ref<Candidate>
|
||||
candidates.push(candidateId)
|
||||
|
||||
// Update or create candidate
|
||||
await ctx.with('find-update', {}, () =>
|
||||
findOrUpdate(ctx, client, recruit.space.CandidatesPublic, recruit.class.Candidate, candidateId, candidate)
|
||||
)
|
||||
await ctx.with('find-update', {}, async () => {
|
||||
await findOrUpdate(ctx, client, recruit.space.CandidatesPublic, contact.class.Person, candidateId, candidate)
|
||||
await client.updateMixin(candidateId, contact.class.Person, recruit.space.CandidatesPublic, recruit.mixin.Candidate, candidateMixin)
|
||||
})
|
||||
|
||||
await ctx.with('add-comment', {}, () =>
|
||||
addComments(
|
||||
@@ -246,7 +251,7 @@ async function genCandidate (
|
||||
client,
|
||||
recruit.space.CandidatesPublic,
|
||||
candidateId,
|
||||
recruit.class.Candidate,
|
||||
contact.class.Person,
|
||||
'comments'
|
||||
)
|
||||
)
|
||||
@@ -260,7 +265,7 @@ async function genCandidate (
|
||||
dbName,
|
||||
recruit.space.CandidatesPublic,
|
||||
candidateId,
|
||||
recruit.class.Candidate,
|
||||
contact.class.Person,
|
||||
'attachments'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { AttachedData, AttachedDoc, Class, Data, Doc, DocumentUpdate, MeasureContext, Ref, Space, TxOperations } from '@anticrm/core'
|
||||
|
||||
export async function findOrUpdate<T extends Doc> (ctx: MeasureContext, client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: Data<T>): Promise<void> {
|
||||
export async function findOrUpdate<T extends Doc> (ctx: MeasureContext, client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: Data<T>): Promise<boolean> {
|
||||
const existingObj = await client.findOne<Doc>(_class, { _id: objectId, space })
|
||||
if (existingObj !== undefined) {
|
||||
await client.updateDoc(_class, space, objectId, data)
|
||||
return false
|
||||
} else {
|
||||
await client.createDoc(_class, space, data, objectId)
|
||||
return true
|
||||
}
|
||||
}
|
||||
export async function findOrUpdateAttached<T extends AttachedDoc> (ctx: MeasureContext, client: TxOperations, space: Ref<Space>, _class: Ref<Class<T>>, objectId: Ref<T>, data: AttachedData<T>, attached: {attachedTo: Ref<Doc>, attachedClass: Ref<Class<Doc>>, collection: string}): Promise<void> {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import attachment, { Attachment } from '@anticrm/attachment'
|
||||
import chunter, { Comment } from '@anticrm/chunter'
|
||||
import contact, { ChannelProvider } from '@anticrm/contact'
|
||||
import core, { AttachedData, AttachedDoc, Class, Data, Doc, DocumentUpdate, Ref, SortingOrder, Space, TxOperations, TxResult } from '@anticrm/core'
|
||||
import contact, { ChannelProvider, Person } from '@anticrm/contact'
|
||||
import core, { AttachedData, AttachedDoc, Class, Data, Doc, DocumentUpdate, Ref, SortingOrder, Space, TxOperations, TxResult, MixinData } from '@anticrm/core'
|
||||
import recruit from '@anticrm/model-recruit'
|
||||
import { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
|
||||
import task, { calcRank, DoneState, genRanks, Kanban, State } from '@anticrm/task'
|
||||
@@ -156,7 +156,7 @@ export async function importXml (
|
||||
lastModified: stats.mtime.getTime()
|
||||
}, {
|
||||
attachedTo: candId,
|
||||
attachedClass: recruit.class.Candidate,
|
||||
attachedClass: contact.class.Person,
|
||||
collection: 'attachments'
|
||||
})
|
||||
|
||||
@@ -227,7 +227,7 @@ async function createApplicant (vacancyId: Ref<Vacancy>, candidateId: Ref<Candid
|
||||
}
|
||||
|
||||
// Update or create candidate
|
||||
await findOrUpdateAttached(client, vacancyId, recruit.class.Applicant, applicantId, applicant, { attachedTo: candidateId, attachedClass: recruit.class.Candidate, collection: 'applications' })
|
||||
await findOrUpdateAttached(client, vacancyId, recruit.class.Applicant, applicantId, applicant, { attachedTo: candidateId, attachedClass: contact.class.Person, collection: 'applications' })
|
||||
}
|
||||
|
||||
async function createUpdateVacancy (client: TxOperations, statuses: any): Promise<{states: Map<string, Ref<State>>, vacancyId: Ref<Vacancy>}> {
|
||||
@@ -259,9 +259,13 @@ async function createCandidate (_name: string, pos: number, len: number, c: any,
|
||||
|
||||
const { sourceFields, telegram, linkedin, github } = parseSocials(c)
|
||||
|
||||
const data: Data<Candidate> = {
|
||||
const data: Data<Person> = {
|
||||
name: names.slice(1).join(' ') + ',' + names[0],
|
||||
city: get(c, _.city) ?? '',
|
||||
channels: []
|
||||
}
|
||||
|
||||
const candidateData: MixinData<Person, Candidate> = {
|
||||
title: [
|
||||
get(c, _.vacancyKind),
|
||||
get(c, _.area)
|
||||
@@ -270,8 +274,7 @@ async function createCandidate (_name: string, pos: number, len: number, c: any,
|
||||
get(c, _.socialContacted),
|
||||
get(c, _.socialChannel),
|
||||
sourceFields.filter(onlyUniq).join(', ')
|
||||
].filter(p => p !== undefined && p.trim().length > 0).filter(onlyUniq).join('/'),
|
||||
channels: []
|
||||
].filter(p => p !== undefined && p.trim().length > 0).filter(onlyUniq).join('/')
|
||||
}
|
||||
|
||||
pushChannel(c, data, _.email, contact.channelProvider.Email)
|
||||
@@ -296,14 +299,15 @@ async function createCandidate (_name: string, pos: number, len: number, c: any,
|
||||
if (github !== undefined) {
|
||||
data.channels.push({ provider: contact.channelProvider.GitHub, value: github })
|
||||
}
|
||||
await findOrUpdate(client, recruit.space.CandidatesPublic, recruit.class.Candidate, candId, data)
|
||||
await findOrUpdate(client, recruit.space.CandidatesPublic, contact.class.Person, candId, data)
|
||||
await client.updateMixin(candId, contact.class.Person, recruit.space.CandidatesPublic, recruit.mixin.Candidate, candidateData)
|
||||
|
||||
const commentId = (candId + '.description.comment') as Ref<Comment>
|
||||
|
||||
if (commentData.length > 0) {
|
||||
await findOrUpdateAttached(client, recruit.space.CandidatesPublic, chunter.class.Comment, commentId, {
|
||||
message: commentData.join('\n<br/>')
|
||||
}, { attachedTo: candId, attachedClass: recruit.class.Candidate, collection: 'comments' })
|
||||
}, { attachedTo: candId, attachedClass: recruit.mixin.Candidate, collection: 'comments' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
DocumentQuery,
|
||||
Domain,
|
||||
FindOptions,
|
||||
isOperator, SortingOrder
|
||||
isOperator, Ref, SortingOrder
|
||||
} from '@anticrm/core'
|
||||
import { MigrationClient, MigrateUpdate, MigrationResult } from '@anticrm/model'
|
||||
import { Db, Document, Filter, Sort, UpdateFilter } from 'mongodb'
|
||||
@@ -87,4 +87,12 @@ export class MigrateClientImpl implements MigrationClient {
|
||||
await this.db.collection(sourceDomain).deleteMany(q)
|
||||
return result
|
||||
}
|
||||
|
||||
async create <T extends Doc>(domain: Domain, doc: T): Promise<void> {
|
||||
await this.db.collection(domain).insertOne(doc as Document)
|
||||
}
|
||||
|
||||
async delete <T extends Doc>(domain: Domain, _id: Ref<T>): Promise<void> {
|
||||
await this.db.collection(domain).deleteOne({ _id })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,11 @@ export async function createDeps (client: Client): Promise<void> {
|
||||
account.employee
|
||||
)
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
const u1 = await tx.createDoc(
|
||||
contact.class.Person,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'P.,Andrey',
|
||||
title: 'Chief Architect',
|
||||
city: 'Monte Carlo',
|
||||
channels: [
|
||||
{
|
||||
@@ -55,12 +54,15 @@ export async function createDeps (client: Client): Promise<void> {
|
||||
}
|
||||
)
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
await tx.createMixin(u1, contact.class.Person, recruit.space.CandidatesPublic, recruit.mixin.Candidate, {
|
||||
title: 'Chief Architect'
|
||||
})
|
||||
|
||||
const u2 = await tx.createDoc(
|
||||
contact.class.Person,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'M.,Marina',
|
||||
title: 'Chief Designer',
|
||||
city: 'Los Angeles',
|
||||
channels: [
|
||||
{
|
||||
@@ -70,13 +72,15 @@ export async function createDeps (client: Client): Promise<void> {
|
||||
]
|
||||
}
|
||||
)
|
||||
await tx.createMixin(u2, contact.class.Person, recruit.space.CandidatesPublic, recruit.mixin.Candidate, {
|
||||
title: 'Chief Designer'
|
||||
})
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
const u3 = await tx.createDoc(
|
||||
contact.class.Person,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'P.,Alex',
|
||||
title: 'Frontend Engineer',
|
||||
city: 'Krasnodar, Russia',
|
||||
channels: [
|
||||
{
|
||||
@@ -86,4 +90,7 @@ export async function createDeps (client: Client): Promise<void> {
|
||||
]
|
||||
}
|
||||
)
|
||||
await tx.createMixin(u3, contact.class.Person, recruit.space.CandidatesPublic, recruit.mixin.Candidate, {
|
||||
title: 'Frontend Engineer'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class TLead extends TTask implements Lead {
|
||||
}
|
||||
|
||||
@Mixin(lead.mixin.Customer, contact.class.Contact)
|
||||
@UX('Customer' as IntlString, contact.icon.Person) // <-- Use general customer icons here.
|
||||
@UX('Customer' as IntlString, lead.icon.LeadApplication)
|
||||
export class TCustomer extends TPerson implements Customer {
|
||||
@Prop(Collection(lead.class.Lead), 'Leads' as IntlString)
|
||||
leads?: number
|
||||
|
||||
+18
-22
@@ -15,7 +15,7 @@
|
||||
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import { Doc, FindOptions, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
@@ -50,8 +50,8 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
|
||||
@UX(recruit.string.CandidatePools, recruit.icon.RecruitApplication)
|
||||
export class TCandidates extends TSpace implements Candidates {}
|
||||
|
||||
@Model(recruit.class.Candidate, contact.class.Person)
|
||||
@UX('Candidate' as IntlString, contact.icon.Person)
|
||||
@Mixin(recruit.mixin.Candidate, contact.class.Person)
|
||||
@UX('Candidate' as IntlString, recruit.icon.RecruitApplication)
|
||||
export class TCandidate extends TPerson implements Candidate {
|
||||
@Prop(TypeString(), 'Title' as IntlString)
|
||||
title?: string
|
||||
@@ -73,7 +73,7 @@ export class TCandidate extends TPerson implements Candidate {
|
||||
@UX('Application' as IntlString, recruit.icon.Application, 'APP' as IntlString, 'number')
|
||||
export class TApplicant extends TTask implements Applicant {
|
||||
// We need to declare, to provide property with label
|
||||
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
|
||||
@Prop(TypeRef(recruit.mixin.Candidate), 'Candidate' as IntlString)
|
||||
declare attachedTo: Ref<Candidate>
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), 'Attachments' as IntlString)
|
||||
@@ -96,13 +96,6 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Candidates, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: recruit.class.Candidate,
|
||||
createItemDialog: recruit.component.CreateCandidate
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributeEditor, {
|
||||
editor: recruit.component.Applications
|
||||
})
|
||||
@@ -122,12 +115,15 @@ export function createModel (builder: Builder): void {
|
||||
addSpaceLabel: recruit.string.CreateVacancy,
|
||||
createComponent: recruit.component.CreateVacancy,
|
||||
component: recruit.component.EditVacancy
|
||||
},
|
||||
}
|
||||
],
|
||||
specials: [
|
||||
{
|
||||
id: 'candidates',
|
||||
component: recruit.component.Candidates,
|
||||
icon: contact.icon.Person,
|
||||
label: recruit.string.Candidates,
|
||||
spaceClass: recruit.class.Candidates,
|
||||
addSpaceLabel: recruit.string.CreateCandidates,
|
||||
createComponent: recruit.component.CreateCandidates
|
||||
position: 'bottom'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -148,7 +144,7 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: recruit.class.Candidate,
|
||||
attachTo: recruit.mixin.Candidate,
|
||||
descriptor: view.viewlet.Table,
|
||||
open: contact.component.EditContact,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
@@ -176,7 +172,7 @@ export function createModel (builder: Builder): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {
|
||||
lookup: {
|
||||
attachedTo: recruit.class.Candidate,
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State,
|
||||
assignee: contact.class.Employee,
|
||||
doneState: task.class.DoneState
|
||||
@@ -202,7 +198,7 @@ export function createModel (builder: Builder): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {
|
||||
lookup: {
|
||||
attachedTo: recruit.class.Candidate,
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State
|
||||
}
|
||||
} as FindOptions<Doc>, // TODO: fix
|
||||
@@ -216,7 +212,7 @@ export function createModel (builder: Builder): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {
|
||||
lookup: {
|
||||
attachedTo: recruit.class.Candidate,
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State,
|
||||
assignee: contact.class.Employee,
|
||||
doneState: task.class.DoneState
|
||||
@@ -239,7 +235,7 @@ export function createModel (builder: Builder): void {
|
||||
card: recruit.component.KanbanCard
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Candidate, core.class.Class, view.mixin.ObjectEditor, {
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: recruit.component.EditCandidate
|
||||
})
|
||||
|
||||
@@ -267,12 +263,12 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Candidate,
|
||||
target: recruit.mixin.Candidate,
|
||||
action: recruit.action.CreateApplication
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Candidate,
|
||||
target: recruit.mixin.Candidate,
|
||||
action: task.action.CreateTask
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,26 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Person } from '@anticrm/contact'
|
||||
import core, { AttachedDoc, Class, Doc, DOMAIN_TX, MixinData, Ref, TxCollectionCUD, TxCreateDoc, TxMixin, TxUpdateDoc } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import contact, { DOMAIN_CONTACT } from '@anticrm/model-contact'
|
||||
import recruit, { Candidate } from '@anticrm/recruit'
|
||||
|
||||
function toCandidateData (c: Pick<Candidate, 'onsite'|'title'|'remote'|'source'> | undefined): MixinData<Person, Candidate> {
|
||||
if (c === undefined) {
|
||||
return {}
|
||||
}
|
||||
const result: MixinData<Person, Candidate> = {
|
||||
onsite: c.onsite,
|
||||
title: c.title,
|
||||
remote: c.remote,
|
||||
source: c.source
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
Object.keys(result).forEach(key => (result as any)[key] == null && delete (result as any)[key])
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function logInfo (msg: string, result: MigrationResult): void {
|
||||
@@ -22,6 +41,160 @@ function logInfo (msg: string, result: MigrationResult): void {
|
||||
}
|
||||
}
|
||||
export const recruitOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
// Move all candidates to mixins.
|
||||
await client.update(DOMAIN_CONTACT, {
|
||||
_class: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}, {
|
||||
$rename: {
|
||||
title: `${recruit.mixin.Candidate}.title`,
|
||||
applications: `${recruit.mixin.Candidate}.applications`,
|
||||
remote: `${recruit.mixin.Candidate}.remote`,
|
||||
source: `${recruit.mixin.Candidate}.source`,
|
||||
onsite: `${recruit.mixin.Candidate}.onsite`
|
||||
}
|
||||
})
|
||||
|
||||
await client.update(DOMAIN_CONTACT, {
|
||||
_class: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}, {
|
||||
_class: contact.class.Person
|
||||
})
|
||||
|
||||
// Migrate Create operations.
|
||||
await client.update(DOMAIN_TX, {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}, {
|
||||
// objectClass: contact.class.Person,
|
||||
$rename: {
|
||||
'attributes.title': `attributes.${recruit.mixin.Candidate}.title`,
|
||||
'attributes.applications': `attributes.${recruit.mixin.Candidate}.applications`,
|
||||
'attributes.remote': `attributes.${recruit.mixin.Candidate}.remote`,
|
||||
'attributes.onsite': `attributes.${recruit.mixin.Candidate}.onsite`,
|
||||
'attributes.source': `attributes.${recruit.mixin.Candidate}.source`
|
||||
}
|
||||
})
|
||||
|
||||
await migrateCreateCandidateToPersonAndMixin(client)
|
||||
|
||||
// Migrate update operations.
|
||||
await client.update(DOMAIN_TX, {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}, {
|
||||
$rename: {
|
||||
'operations.title': `operations.${recruit.mixin.Candidate}.title`,
|
||||
'operations.applications': `operations.${recruit.mixin.Candidate}.applications`,
|
||||
'operations.remote': `operations.${recruit.mixin.Candidate}.remote`,
|
||||
'operations.onsite': `operations.${recruit.mixin.Candidate}.onsite`,
|
||||
'operations.source': `operations.${recruit.mixin.Candidate}.source`
|
||||
}
|
||||
})
|
||||
await migrateUpdateCandidateToPersonAndMixin(client)
|
||||
|
||||
await migrateTxCollectionCandidateToPerson(client)
|
||||
|
||||
await client.update(DOMAIN_TX, {
|
||||
_class: core.class.TxRemoveDoc,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}, {
|
||||
objectClass: contact.class.Person
|
||||
})
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
|
||||
}
|
||||
async function migrateUpdateCandidateToPersonAndMixin (client: MigrationClient): Promise<void> {
|
||||
const updateCandidates = await client.find(DOMAIN_TX, {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}) as TxUpdateDoc<Candidate>[]
|
||||
console.log('Processing update candidate operations:', updateCandidates.length)
|
||||
for (const c of updateCandidates) {
|
||||
const mixinOp: TxMixin<Person, Candidate> = {
|
||||
_class: core.class.TxMixin,
|
||||
_id: (c._id + '.m') as Ref<TxMixin<Person, Candidate>>,
|
||||
objectId: c.objectId,
|
||||
objectClass: contact.class.Person,
|
||||
mixin: recruit.mixin.Candidate,
|
||||
attributes: toCandidateData((c.operations as any)[recruit.mixin.Candidate] as unknown as Candidate),
|
||||
objectSpace: c.objectSpace,
|
||||
modifiedBy: c.modifiedBy,
|
||||
modifiedOn: c.modifiedOn,
|
||||
space: c.space
|
||||
}
|
||||
if (Object.keys(mixinOp.attributes).length > 0) {
|
||||
try {
|
||||
await client.create(DOMAIN_TX, mixinOp)
|
||||
} catch (ex) {
|
||||
// Ignore if existing
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete (c.operations as any)[recruit.mixin.Candidate]
|
||||
|
||||
if (Object.keys(c.operations).length === 0) {
|
||||
// Delete existing transaction, since there is nothing to change inside.
|
||||
await client.delete(DOMAIN_TX, c._id)
|
||||
} else {
|
||||
await client.update(DOMAIN_TX, { _id: c._id }, {
|
||||
$set: { objectClass: contact.class.Person },
|
||||
$unset: { [`operations.${recruit.mixin.Candidate}`]: '' }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
async function migrateTxCollectionCandidateToPerson (client: MigrationClient): Promise<void> {
|
||||
const collectionCandidates = await client.find(DOMAIN_TX, {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>
|
||||
}) as TxCollectionCUD<Candidate, AttachedDoc>[]
|
||||
console.log('Processing collection candidate operations:', collectionCandidates.length)
|
||||
|
||||
for (const c of collectionCandidates) {
|
||||
if (c.tx.objectClass === recruit.class.Applicant) {
|
||||
await client.update(DOMAIN_TX, { _id: c._id }, {
|
||||
objectClass: recruit.mixin.Candidate
|
||||
})
|
||||
} else {
|
||||
await client.update(DOMAIN_TX, { _id: c._id }, {
|
||||
objectClass: contact.class.Person
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCreateCandidateToPersonAndMixin (client: MigrationClient): Promise<void> {
|
||||
const createCandidates = await client.find(DOMAIN_TX, {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: 'recruit:class:Candidate' as Ref<Class<Doc>>,
|
||||
[`attributes.${recruit.mixin.Candidate}`]: { $exists: true }
|
||||
}) as TxCreateDoc<Candidate>[]
|
||||
console.log('Processing create candidate operations:', createCandidates.length)
|
||||
for (const c of createCandidates) {
|
||||
const mixinOp: TxMixin<Person, Candidate> = {
|
||||
_class: core.class.TxMixin,
|
||||
_id: (c._id + '.m') as Ref<TxMixin<Person, Candidate>>,
|
||||
objectId: c.objectId,
|
||||
objectClass: contact.class.Person,
|
||||
mixin: recruit.mixin.Candidate,
|
||||
attributes: toCandidateData((c.attributes as any)[recruit.mixin.Candidate] as unknown as Candidate),
|
||||
objectSpace: c.objectSpace,
|
||||
modifiedBy: c.modifiedBy,
|
||||
modifiedOn: c.modifiedOn,
|
||||
space: c.space
|
||||
}
|
||||
if (Object.keys(mixinOp.attributes).length > 0) {
|
||||
try {
|
||||
await client.create(DOMAIN_TX, mixinOp)
|
||||
} catch (ex) {
|
||||
// Ignore if existing
|
||||
}
|
||||
}
|
||||
|
||||
await client.update(DOMAIN_TX, { _id: c._id }, {
|
||||
$set: { objectClass: contact.class.Person },
|
||||
$unset: { [`attributes.${recruit.mixin.Candidate}`]: '' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Client, Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { Client, Doc, Ref } from '@anticrm/core'
|
||||
import type { IntlString, Resource, Status } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { recruitId } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit-resources/src/plugin'
|
||||
import { KanbanTemplate } from '@anticrm/task'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/view'
|
||||
import { Application } from '@anticrm/workbench'
|
||||
import { KanbanTemplate } from '@anticrm/task'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
app: {
|
||||
@@ -36,17 +36,13 @@ export default mergeIds(recruitId, recruit, {
|
||||
string: {
|
||||
RecruitApplication: '' as IntlString,
|
||||
Vacancies: '' as IntlString,
|
||||
CandidatePools: '' as IntlString,
|
||||
Candidates: '' as IntlString,
|
||||
Vacancy: '' as IntlString
|
||||
CandidatePools: '' as IntlString
|
||||
},
|
||||
validator: {
|
||||
ApplicantValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
|
||||
},
|
||||
component: {
|
||||
CreateVacancy: '' as AnyComponent,
|
||||
CreateCandidates: '' as AnyComponent,
|
||||
CreateCandidate: '' as AnyComponent,
|
||||
CreateApplication: '' as AnyComponent,
|
||||
EditCandidate: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent,
|
||||
@@ -55,10 +51,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
EditVacancy: '' as AnyComponent,
|
||||
EditApplication: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
Applications: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
Applications: '' as AnyComponent,
|
||||
Candidates: '' as AnyComponent
|
||||
},
|
||||
template: {
|
||||
DefaultVacancy: '' as Ref<KanbanTemplate>
|
||||
|
||||
@@ -70,6 +70,11 @@ export interface TxBulkWrite extends Tx {
|
||||
txes: TxCUD<Doc>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type MixinData<D extends Doc, M extends D> = Omit<M, keyof D> & PushOptions<Omit<M, keyof D>> & IncOptions<Omit<M, keyof D>>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -426,7 +431,7 @@ export class TxOperations implements Storage {
|
||||
objectClass: Ref<Class<D>>,
|
||||
objectSpace: Ref<Space>,
|
||||
mixin: Ref<Mixin<M>>,
|
||||
attributes: MixinUpdate<D, M>
|
||||
attributes: MixinData<D, M>
|
||||
): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxMixin(objectId, objectClass, objectSpace, mixin, attributes)
|
||||
return this.storage.tx(tx)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Doc, DocumentQuery, Domain, FindOptions, IncOptions, ObjQueryType, PushOptions } from '@anticrm/core'
|
||||
import { Client, Doc, DocumentQuery, Domain, FindOptions, IncOptions, ObjQueryType, PushOptions, Ref } from '@anticrm/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -43,6 +43,9 @@ export interface MigrationClient {
|
||||
|
||||
// Move documents per domain
|
||||
move: <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain) => Promise<MigrationResult>
|
||||
|
||||
create: <T extends Doc>(domain: Domain, doc: T) => Promise<void>
|
||||
delete: <T extends Doc>(domain: Domain, _id: Ref<T>) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -118,6 +118,11 @@ p:last-child { margin-block-end: 0; }
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.flex-row-streach {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.flex-row-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
const blackColors: string[] = [
|
||||
'#A5D179',
|
||||
'#77C07B',
|
||||
'#60B96E',
|
||||
'#45AEA3',
|
||||
'#46CBDE',
|
||||
'#47BDF6',
|
||||
'#5AADF6',
|
||||
'#73A6CD',
|
||||
'#B977CB',
|
||||
'#7C6FCD',
|
||||
'#6F7BC5',
|
||||
'#F28469'
|
||||
]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getPlatformColor (hash: number): string {
|
||||
return blackColors[Math.abs(hash) % blackColors.length]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getPlatformColorForText (text: string): string {
|
||||
return getPlatformColor(hashCode(text))
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getPlatformColorCount (): number {
|
||||
return blackColors.length
|
||||
}
|
||||
|
||||
function hashCode (str: string): number {
|
||||
return str.split('').reduce((prevHash, currVal) =>
|
||||
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0)) | 0, 0)
|
||||
}
|
||||
@@ -157,3 +157,4 @@ addStringsLoader(uiId, async (lang: string) => {
|
||||
})
|
||||
|
||||
export { default } from './plugin'
|
||||
export * from './colors'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="activity" viewBox="0 0 20 20">
|
||||
<path d="M8.4,8.5C8.2,8.4,8,8.3,7.9,8.3c-0.2,0-0.4,0.1-0.5,0.3l-2.9,3.8c-0.2,0.3-0.2,0.7,0.1,1c0.1,0.1,0.3,0.1,0.4,0.1 c0.2,0,0.4-0.1,0.6-0.3L8.1,10l2.8,2.2c0.1,0.1,0.3,0.2,0.5,0.1c0.2,0,0.4-0.1,0.5-0.3l2.8-3.7c0.2-0.3,0.2-0.7-0.1-1 c-0.3-0.2-0.7-0.2-1,0.1l-2.4,3.1L8.4,8.5z"/>
|
||||
<path d="M17.4,0c-1.4,0-2.6,1.2-2.6,2.6c0,1.4,1.2,2.6,2.6,2.6C18.8,5.1,20,4,20,2.5C20,1.1,18.8,0,17.4,0z M17.4,3.7 c-0.6,0-1.2-0.5-1.2-1.2c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2C18.6,3.2,18.1,3.7,17.4,3.7z"/>
|
||||
<path d="M18.5,6.8c-0.4,0-0.7,0.3-0.7,0.7v6.8c0,2.6-1.6,4.3-4,4.3H5.4c-2.5,0-4-1.6-4-4.3V6.5c0-2.6,1.6-4.3,4-4.3h7.1 c0.4,0,0.7-0.3,0.7-0.7s-0.3-0.7-0.7-0.7H5.4C2.2,0.8,0,3.1,0,6.5v7.9C0,17.7,2.2,20,5.4,20h8.4c3.3,0,5.4-2.3,5.4-5.7V7.5 C19.2,7.1,18.9,6.8,18.5,6.8z"/>
|
||||
<symbol id="activity" viewBox="0 0 20 20" width='20' height='20' fill="none">
|
||||
<path d="M6.03772 12.3181L8.532 9.07628L11.3772 11.3112L13.818 8.16095" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill='none'/>
|
||||
<ellipse cx="16.6632" cy="3.50027" rx="1.60183" ry="1.60183" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.4372 2.6001H6.38078C3.87125 2.6001 2.31519 4.37737 2.31519 6.8869V13.6222C2.31519 16.1318 3.84074 17.9014 6.38078 17.9014H13.5509C16.0604 17.9014 17.6165 16.1318 17.6165 13.6222V7.75647" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</symbol>
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 900 B After Width: | Height: | Size: 770 B |
@@ -25,10 +25,9 @@
|
||||
getClient,
|
||||
KeyedAttribute
|
||||
} from '@anticrm/presentation'
|
||||
import { ActionIcon, AnyComponent, Component, Label } from '@anticrm/ui'
|
||||
|
||||
import { AnyComponent, Component, getPlatformColorForText, Label } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import contact from '../plugin'
|
||||
|
||||
export let _id: Ref<Contact>
|
||||
@@ -58,14 +57,16 @@
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
let selectedMixin: Mixin<Doc> | undefined
|
||||
|
||||
|
||||
$: if (object && prevSelected !== object._class) {
|
||||
prevSelected = object._class
|
||||
selectedClass = objectClass._id
|
||||
selectedMixin = undefined
|
||||
const h = client.getHierarchy()
|
||||
mixins = h.getDescendants(contact.class.Contact)
|
||||
.filter((m) => h.getClass(m).kind === ClassifierKind.MIXIN && h.hasMixin(object, m)).map(m => h.getClass(m) as Mixin<Doc>)
|
||||
mixins = h
|
||||
.getDescendants(contact.class.Contact)
|
||||
.filter((m) => h.getClass(m).kind === ClassifierKind.MIXIN && h.hasMixin(object, m))
|
||||
.map((m) => h.getClass(m) as Mixin<Doc>)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -109,7 +110,10 @@
|
||||
return editorMixin.editor
|
||||
}
|
||||
|
||||
async function getEditorOrDefault (_class: Ref<Class<Doc>> | undefined, defaultClass: Ref<Class<Doc>>): Promise<AnyComponent> {
|
||||
async function getEditorOrDefault (
|
||||
_class: Ref<Class<Doc>> | undefined,
|
||||
defaultClass: Ref<Class<Doc>>
|
||||
): Promise<AnyComponent> {
|
||||
const editor = _class !== undefined ? await getEditor(_class) : undefined
|
||||
if (editor !== undefined) {
|
||||
return editor
|
||||
@@ -125,6 +129,34 @@
|
||||
}
|
||||
|
||||
$: icon = (objectClass?.icon ?? contact.class.Person) as Asset
|
||||
|
||||
function getStyle (id: Ref<Class<Doc>>, selected: boolean): string {
|
||||
const color = getPlatformColorForText(id as string)
|
||||
return `
|
||||
background: ${color + (selected ? 'ff' : '33')};
|
||||
border: 1px solid ${color + (selected ? '0f' : '66')};
|
||||
`
|
||||
}
|
||||
|
||||
let mainEditor: HTMLElement
|
||||
let prevEditor: HTMLElement
|
||||
let maxHeight = 0
|
||||
const observer = new ResizeObserver(() => {
|
||||
const curHeight = mainEditor.clientHeight
|
||||
maxHeight = Math.max(maxHeight, curHeight)
|
||||
})
|
||||
|
||||
$: if (mainEditor != null) {
|
||||
if (prevEditor != null) {
|
||||
observer.unobserve(prevEditor)
|
||||
}
|
||||
prevEditor = mainEditor
|
||||
observer.observe(mainEditor)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
@@ -138,32 +170,13 @@
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<div slot="subtitle" class="flex flex-reverse flex-grow">
|
||||
<div class='flex'>
|
||||
{#if mixins.length > 0}
|
||||
<div class='mixin-selector' class:selected={selectedClass === objectClass._id}>
|
||||
<ActionIcon icon={objectClass.icon} size={'medium'} label={objectClass.label} action={() => {
|
||||
selectedClass = objectClass._id
|
||||
selectedMixin = undefined
|
||||
}} />
|
||||
</div>
|
||||
{#each mixins as mixin}
|
||||
<div class='mixin-selector' class:selected={selectedClass === mixin._id}>
|
||||
<ActionIcon icon={mixin.icon} size={'medium'} label={mixin.label} action={() => {
|
||||
selectedClass = mixin._id
|
||||
selectedMixin = mixin
|
||||
}} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
{#if keys}
|
||||
<AttributesBar {object} {keys} />
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="subtitle">
|
||||
{#if keys}
|
||||
<AttributesBar {object} {keys} />
|
||||
{/if}
|
||||
</div>
|
||||
{#await getEditorOrDefault(selectedClass, object._class) then is}
|
||||
<div class='main-editor' bind:this={mainEditor} style={`min-height: ${maxHeight}px;`}>
|
||||
{#await getEditorOrDefault(selectedClass, object._class) then is}
|
||||
<Component
|
||||
{is}
|
||||
props={{ object }}
|
||||
@@ -175,7 +188,24 @@
|
||||
rightSection = ev.detail.presenter
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
{/await}
|
||||
</div>
|
||||
{#if mixins.length > 0}
|
||||
<div class="mixin-container">
|
||||
<div class="mixin-selector"
|
||||
style={getStyle(objectClass._id, selectedClass === objectClass._id)}
|
||||
on:click={() => { selectedClass = objectClass._id; selectedMixin = undefined }}>
|
||||
<Label label={objectClass.label} />
|
||||
</div>
|
||||
{#each mixins as mixin}
|
||||
<div class="mixin-selector"
|
||||
style={getStyle(mixin._id, selectedClass === mixin._id)}
|
||||
on:click={() => { selectedClass = mixin._id; selectedMixin = mixin }}>
|
||||
<Label label={mixin.label} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#each collectionKeys as collection}
|
||||
<div class="mt-14">
|
||||
{#await getCollectionEditor(collection) then is}
|
||||
@@ -183,53 +213,36 @@
|
||||
{/await}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<!-- {#each mixins as mixin}
|
||||
<div class="mixin-container">
|
||||
<div class="header">
|
||||
<div class="icon" />
|
||||
<Label label={mixin._class.label} />
|
||||
</div>
|
||||
<div class="attributes">
|
||||
{#if mixin.keys.length > 0}
|
||||
<AttributesBar {object} keys={mixin.keys} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="collections">
|
||||
{#each mixin.collectionKeys as collection}
|
||||
<div class="mt-14">
|
||||
{#await getCollectionEditor(collection) then is}
|
||||
<Component {is} props={{ objectId: object._id, _class: object._class, space: object.space }} />
|
||||
{/await}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each} -->
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.main-editor {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mixin-container {
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--theme-zone-bg);
|
||||
.header {
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
align-items: center;
|
||||
.icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
/* Dark / Green 01 */
|
||||
display: flex;
|
||||
.mixin-selector {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
min-width: 84px;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
background: #77c07b;
|
||||
border: 2px solid #18181e;
|
||||
border-radius: 50px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
|
||||
text-transform: uppercase;
|
||||
color: #FFFFFF;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.attributes {
|
||||
margin: 1rem;
|
||||
@@ -237,14 +250,5 @@
|
||||
.collections {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
}
|
||||
.mixin-selector {
|
||||
opacity: 0.6;
|
||||
margin: 0.25rem;
|
||||
|
||||
&.selected {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -64,17 +64,22 @@
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-row-streach flex-grow">
|
||||
<div class="mr-8">
|
||||
<Avatar avatar={object.avatar} size={'x-large'} />
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="name">
|
||||
<EditBox placeholder="John" maxWidth="20rem" bind:value={firstName} on:change={firstNameChange} />
|
||||
</div>
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="name">
|
||||
<EditBox placeholder="John" maxWidth="20rem" bind:value={firstName} on:change={firstNameChange} />
|
||||
</div>
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
<div class="flex-between channels">
|
||||
<div class="flex-row-center">
|
||||
{#if !object.channels || object.channels.length === 0}
|
||||
@@ -127,4 +132,10 @@
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 1rem 0;
|
||||
height: 1px;
|
||||
background-color: var(--theme-card-divider);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} bind:value={search} />
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} bind:value={search} on:change={() => { resultQuery = {} } } />
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -7,14 +7,19 @@
|
||||
"VacancyName": "Vacancy Title *",
|
||||
"VacancyDescription": "Vacancy Description",
|
||||
"CreateVacancy": "Create Vacancy",
|
||||
"CreateCandidate": "Create Candidate",
|
||||
"MakePrivate": "Make Private",
|
||||
"Vacancy": "Vacancy",
|
||||
"CreateCandidates": "Create pool",
|
||||
"CandidatesName": "Pool name *",
|
||||
"MakePrivateDescription": "Only members can see it",
|
||||
"CreateAnApplication": "Create an application",
|
||||
"NoApplicationsForCandidate": "There are no applications for this candidate."
|
||||
"NoApplicationsForCandidate": "There are no applications for this candidate.",
|
||||
"CreateApplication": "Create Application",
|
||||
"SelectVacancy": "Select vacancy",
|
||||
"Candidate": "Candidate",
|
||||
"AssignRecruiter": "Assigned recruiter",
|
||||
"UnAssignRecruiter": "Unassigned recruiter",
|
||||
"Recruiters": "Recruiters",
|
||||
"Create": "Create"
|
||||
},
|
||||
"status": {
|
||||
"CandidateRequired": "Please select candidate"
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
|
||||
import { Button, EditWithIcon, Icon, IconSearch, Label, ScrollBox, showPopup } from '@anticrm/ui'
|
||||
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import recruit from '../plugin'
|
||||
import view, { Viewlet } from '@anticrm/view'
|
||||
import CreateCandidate from './CreateCandidate.svelte'
|
||||
|
||||
let search = ''
|
||||
$: resultQuery = search === '' ? { } : { $search: search }
|
||||
|
||||
const client = getClient()
|
||||
const tableDescriptor = client.findOne<Viewlet>(view.class.Viewlet, { attachTo: recruit.mixin.Candidate, descriptor: view.viewlet.Table })
|
||||
|
||||
function showCreateDialog (ev: Event) {
|
||||
showPopup(CreateCandidate, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="candidates-header-container">
|
||||
<div class="header-container">
|
||||
<div class="flex-row-center">
|
||||
<span class="icon"><Icon icon={recruit.icon.Calendar} size={'small'}/></span>
|
||||
<span class="label"><Label label={recruit.string.Candidates}/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} bind:value={search} on:change={() => { resultQuery = {} } } />
|
||||
<Button label={recruit.string.Create} primary={true} size={'small'} on:click={(ev) => showCreateDialog(ev)}/>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel-component">
|
||||
<ScrollBox vertical stretch noShift>
|
||||
{#await tableDescriptor then descr}
|
||||
{#if descr}
|
||||
<Table
|
||||
_class={recruit.mixin.Candidate}
|
||||
config={descr.config}
|
||||
options={descr.options}
|
||||
query={ resultQuery }
|
||||
enableChecking
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
</ScrollBox>
|
||||
</div>
|
||||
</div>
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
.panel-component {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 1rem;
|
||||
height: 100%;
|
||||
border-radius: 1.25rem;
|
||||
background-color: var(--theme-bg-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.candidates-header-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: min-content;
|
||||
gap: .75rem;
|
||||
align-items: center;
|
||||
padding: 0 1.75rem 0 2.5rem;
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
opacity: .6;
|
||||
}
|
||||
.label, .description {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 35rem;
|
||||
}
|
||||
.label {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.description {
|
||||
font-size: .75rem;
|
||||
color: var(--theme-content-trans-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import type { Contact, Employee, Person } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import { Account, Class, Client, Doc, generateId, Ref, SortingOrder } from '@anticrm/core'
|
||||
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
|
||||
@@ -41,7 +41,7 @@
|
||||
assignee: assignee,
|
||||
rank: '',
|
||||
attachedTo: candidate,
|
||||
attachedToClass: recruit.class.Candidate,
|
||||
attachedToClass: recruit.mixin.Candidate,
|
||||
_class: recruit.class.Applicant,
|
||||
space: space,
|
||||
_id: generateId(),
|
||||
@@ -82,11 +82,20 @@
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const candidateInstance = await client.findOne(contact.class.Person, { _id: doc.attachedTo as Ref<Person> })
|
||||
if (candidateInstance === undefined) {
|
||||
throw new Error('contact not found')
|
||||
}
|
||||
if (!client.getHierarchy().hasMixin(candidateInstance, recruit.mixin.Candidate)) {
|
||||
await client.createMixin<Contact, Candidate>(candidateInstance._id, candidateInstance._class, candidateInstance.space, recruit.mixin.Candidate, {})
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
recruit.class.Applicant,
|
||||
doc.space,
|
||||
doc.attachedTo,
|
||||
recruit.class.Candidate,
|
||||
candidateInstance._class,
|
||||
'applications',
|
||||
{
|
||||
state: state._id,
|
||||
@@ -121,12 +130,12 @@
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={'Create Application'}
|
||||
label={recruit.string.CreateApplication}
|
||||
okAction={createApplication}
|
||||
canSave={status.severity === Severity.OK}
|
||||
spaceClass={recruit.class.Vacancy}
|
||||
spaceLabel={'Vacancy'}
|
||||
spacePlaceholder={'Select vacancy'}
|
||||
spaceLabel={recruit.string.Vacancy}
|
||||
spacePlaceholder={recruit.string.SelectVacancy}
|
||||
bind:space={doc.space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
@@ -135,15 +144,15 @@
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
{#if !preserveCandidate}
|
||||
<UserBox _class={recruit.class.Candidate} title="Candidate" caption="Candidates" bind:value={doc.attachedTo} />
|
||||
<UserBox _class={contact.class.Person} title={recruit.string.Candidate} caption={recruit.string.Candidates} bind:value={doc.attachedTo} />
|
||||
{/if}
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
title="Assigned recruiter"
|
||||
caption="Recruiters"
|
||||
title={recruit.string.AssignRecruiter}
|
||||
caption={recruit.string.Recruiters}
|
||||
bind:value={doc.assignee}
|
||||
allowDeselect
|
||||
titleDeselect={'Unassign recruiter'}
|
||||
titleDeselect={recruit.string.UnAssignRecruiter}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
@@ -14,28 +14,21 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Ref, Space, Data } from '@anticrm/core'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import { setPlatformStatus, unknownError, Severity } from '@anticrm/platform'
|
||||
import type { Status } from '@anticrm/platform'
|
||||
|
||||
import { getClient, Card, Channels, PDFViewer, Avatar } from '@anticrm/presentation'
|
||||
import { uploadFile } from '../utils'
|
||||
|
||||
import recruit from '../plugin'
|
||||
import chunter from '@anticrm/chunter'
|
||||
import type { Candidate } from '@anticrm/recruit'
|
||||
import attachment from '@anticrm/attachment'
|
||||
import type { Attachment } from '@anticrm/attachment'
|
||||
|
||||
import { EditBox, Link, showPopup, Component, CircleButton, IconFile as FileIcon, IconAdd, Spinner, Label, Status as StatusComponent } from '@anticrm/ui'
|
||||
import FileUpload from './icons/FileUpload.svelte'
|
||||
import contact, { combineName, Person } from '@anticrm/contact'
|
||||
import type { Data, MixinData, Ref, Space } from '@anticrm/core'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import { setPlatformStatus, unknownError } from '@anticrm/platform'
|
||||
import { Avatar, Card, Channels, getClient, PDFViewer } from '@anticrm/presentation'
|
||||
import type { Candidate } from '@anticrm/recruit'
|
||||
import { CircleButton, EditBox, IconAdd, IconFile as FileIcon, Label, Link, showPopup, Spinner } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
import { uploadFile } from '../utils'
|
||||
import Edit from './icons/Edit.svelte'
|
||||
import FileUpload from './icons/FileUpload.svelte'
|
||||
import YesNo from './YesNo.svelte'
|
||||
|
||||
import contact, { combineName } from '@anticrm/contact'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
let _space = space
|
||||
@@ -43,13 +36,13 @@
|
||||
let firstName = ''
|
||||
let lastName = ''
|
||||
|
||||
export function canClose(): boolean {
|
||||
export function canClose (): boolean {
|
||||
return firstName === '' && lastName === '' && resume.uuid === undefined
|
||||
}
|
||||
|
||||
const object: Candidate = {} as Candidate
|
||||
|
||||
let resume = {} as {
|
||||
const resume = {} as {
|
||||
name: string
|
||||
uuid: string
|
||||
size: number
|
||||
@@ -61,21 +54,25 @@
|
||||
const client = getClient()
|
||||
const candidateId = generateId()
|
||||
|
||||
async function createCandidate() {
|
||||
const candidate: Data<Candidate> = {
|
||||
async function createCandidate () {
|
||||
const candidate: Data<Person> = {
|
||||
name: combineName(firstName, lastName),
|
||||
title: object.title,
|
||||
city: object.city,
|
||||
channels: object.channels,
|
||||
channels: object.channels
|
||||
}
|
||||
const candidateData: MixinData<Person, Candidate> = {
|
||||
title: object.title,
|
||||
onsite: object.onsite,
|
||||
remote: object.remote
|
||||
}
|
||||
|
||||
const id = await client.createDoc(recruit.class.Candidate, _space, candidate, candidateId)
|
||||
const id = await client.createDoc(contact.class.Person, _space, candidate, candidateId)
|
||||
await client.createMixin(id as Ref<Person>, contact.class.Person, _space, recruit.mixin.Candidate, candidateData)
|
||||
|
||||
console.log('resume name', resume.name)
|
||||
|
||||
if (resume.uuid !== undefined) {
|
||||
client.addCollection(attachment.class.Attachment, space, id, recruit.class.Candidate, 'attachments', {
|
||||
client.addCollection(attachment.class.Attachment, space, id, contact.class.Person, 'attachments', {
|
||||
name: resume.name,
|
||||
file: resume.uuid,
|
||||
size: resume.size,
|
||||
@@ -91,7 +88,7 @@
|
||||
let loading = false
|
||||
let dragover = false
|
||||
|
||||
async function createAttachment(file: File) {
|
||||
async function createAttachment (file: File) {
|
||||
loading = true
|
||||
try {
|
||||
resume.uuid = await uploadFile(space, file, candidateId)
|
||||
@@ -108,13 +105,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function drop(event: DragEvent) {
|
||||
function drop (event: DragEvent) {
|
||||
dragover = false
|
||||
const droppedFile = event.dataTransfer?.files[0]
|
||||
if (droppedFile !== undefined) { createAttachment(droppedFile) }
|
||||
}
|
||||
}
|
||||
|
||||
function fileSelected() {
|
||||
function fileSelected () {
|
||||
console.log(inputFile.files)
|
||||
const file = inputFile.files?.[0]
|
||||
if (file !== undefined) { createAttachment(file) }
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
|
||||
|
||||
import { getClient, SpaceCreateCard } from '@anticrm/presentation'
|
||||
|
||||
import recruit from '../plugin'
|
||||
import core from '@anticrm/core'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let name: string = ''
|
||||
let description: string = ''
|
||||
|
||||
export function canClose(): boolean {
|
||||
return name === ''
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
function createCandidates() {
|
||||
client.createDoc(recruit.class.Candidates, core.space.Model, {
|
||||
name,
|
||||
description,
|
||||
private: false,
|
||||
archived: false,
|
||||
members: []
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
label={recruit.string.CreateCandidates}
|
||||
okAction={createCandidates}
|
||||
canSave={name ? true : false}
|
||||
on:close={() => { dispatch('close') }}
|
||||
>
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox label={recruit.string.CandidatesName} icon={IconFolder} bind:value={name} placeholder={'Talent Pool'} maxWidth={'16rem'} focus/>
|
||||
<ToggleWithLabel label={recruit.string.MakePrivate} description={recruit.string.MakePrivateDescription}/>
|
||||
</Grid>
|
||||
</SpaceCreateCard>
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
const candidateQuery = createQuery()
|
||||
$: if (object !== undefined) {
|
||||
candidateQuery.query(recruit.class.Candidate, { _id: object.attachedTo as Ref<Candidate> }, (result) => {
|
||||
candidateQuery.query(recruit.mixin.Candidate, { _id: object.attachedTo as Ref<Candidate> }, (result) => {
|
||||
candidate = result[0]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
|
||||
import { getCurrentAccount, Ref, Space } from '@anticrm/core'
|
||||
import { CircleButton, EditBox, showPopup, IconAdd, Label, IconActivity } from '@anticrm/ui'
|
||||
import { getClient, createQuery, Channels, AttributeEditor, Avatar } from '@anticrm/presentation'
|
||||
@@ -37,18 +37,18 @@
|
||||
function saveChannels (result: any) {
|
||||
if (result !== undefined) {
|
||||
object.channels = result
|
||||
client.updateDoc(recruit.class.Candidate, object.space, object._id, { channels: result })
|
||||
client.updateDoc(object._class, object.space, object._id, { channels: result })
|
||||
}
|
||||
}
|
||||
|
||||
function firstNameChange () {
|
||||
client.updateDoc(recruit.class.Candidate, object.space, object._id, {
|
||||
client.updateDoc(object._class, object.space, object._id, {
|
||||
name: combineName(firstName, getLastName(object.name))
|
||||
})
|
||||
}
|
||||
|
||||
function lastNameChange () {
|
||||
client.updateDoc(recruit.class.Candidate, object.space, object._id, {
|
||||
client.updateDoc(object._class, object.space, object._id, {
|
||||
name: combineName(getFirstName(object.name), lastName)
|
||||
})
|
||||
}
|
||||
@@ -60,30 +60,32 @@
|
||||
integrations = new Set(res.map((p) => p.type))
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['comments', 'name', 'channels', 'title'] })
|
||||
})
|
||||
const sendOpen = () => dispatch('open', { ignoreKeys: ['comments', 'name', 'channels', 'title'] })
|
||||
onMount(sendOpen)
|
||||
afterUpdate(sendOpen)
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-row-streach flex-grow">
|
||||
<div class="mr-8">
|
||||
<Avatar avatar={object.avatar} size={'x-large'} />
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="name">
|
||||
<EditBox placeholder="John" maxWidth="20rem" bind:value={firstName} on:change={firstNameChange} />
|
||||
</div>
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
</div>
|
||||
<div class="title">
|
||||
<AttributeEditor maxWidth="20rem" _class={recruit.class.Candidate} {object} key="title" />
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="name">
|
||||
<EditBox placeholder="John" maxWidth="20rem" bind:value={firstName} on:change={firstNameChange} />
|
||||
</div>
|
||||
<div class="name">
|
||||
<EditBox placeholder="Appleseed" maxWidth="20rem" bind:value={lastName} on:change={lastNameChange} />
|
||||
</div>
|
||||
<div class="title">
|
||||
<AttributeEditor maxWidth="20rem" _class={recruit.mixin.Candidate} {object} key="title" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
<div class="flex-between">
|
||||
<div class="flex-between channels">
|
||||
<div class="flex-row-center">
|
||||
{#if !object.channels || object.channels.length === 0}
|
||||
<CircleButton
|
||||
@@ -132,6 +134,12 @@
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.channels {
|
||||
margin-top: 0.75rem;
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
margin: 1rem 0;
|
||||
height: 1px;
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
import type { Client, Doc } from '@anticrm/core'
|
||||
|
||||
import CreateVacancy from './components/CreateVacancy.svelte'
|
||||
import CreateCandidates from './components/CreateCandidates.svelte'
|
||||
import CreateCandidate from './components/CreateCandidate.svelte'
|
||||
import CreateApplication from './components/CreateApplication.svelte'
|
||||
import EditCandidate from './components/EditCandidate.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
@@ -27,6 +25,7 @@ import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import Applications from './components/Applications.svelte'
|
||||
import EditApplication from './components/EditApplication.svelte'
|
||||
import Candidates from './components/Candidates.svelte'
|
||||
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { OK, Resources, Severity, Status } from '@anticrm/platform'
|
||||
@@ -63,8 +62,6 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
component: {
|
||||
CreateVacancy,
|
||||
CreateCandidates,
|
||||
CreateCandidate,
|
||||
CreateApplication,
|
||||
EditCandidate,
|
||||
EditApplication,
|
||||
@@ -73,6 +70,7 @@ export default async (): Promise<Resources> => ({
|
||||
ApplicationsPresenter,
|
||||
EditVacancy,
|
||||
TemplatesIcon,
|
||||
Applications
|
||||
Applications,
|
||||
Candidates
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Ref, Space } from '@anticrm/core'
|
||||
import type { IntlString, StatusCode } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import recruit, { recruitId } from '@anticrm/recruit'
|
||||
@@ -29,13 +30,23 @@ export default mergeIds(recruitId, recruit, {
|
||||
VacancyDescription: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
MakePrivateDescription: '' as IntlString,
|
||||
CreateCandidates: '' as IntlString,
|
||||
CandidatesName: '' as IntlString,
|
||||
CandidatesDescription: '' as IntlString,
|
||||
CreateCandidate: '' as IntlString,
|
||||
CreateAnApplication: '' as IntlString,
|
||||
NoApplicationsForCandidate: '' as IntlString,
|
||||
FirstName: '' as IntlString,
|
||||
LastName: '' as IntlString
|
||||
LastName: '' as IntlString,
|
||||
Candidates: '' as IntlString,
|
||||
CreateApplication: '' as IntlString,
|
||||
Vacancy: '' as IntlString,
|
||||
SelectVacancy: '' as IntlString,
|
||||
Candidate: '' as IntlString,
|
||||
AssignRecruiter: '' as IntlString,
|
||||
Recruiters: '' as IntlString,
|
||||
UnAssignRecruiter: '' as IntlString,
|
||||
Create: '' as IntlString
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import type { Person } from '@anticrm/contact'
|
||||
import type { Class, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Class, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
|
||||
@@ -65,10 +65,12 @@ export const recruitId = 'recruit' as Plugin
|
||||
const recruit = plugin(recruitId, {
|
||||
class: {
|
||||
Applicant: '' as Ref<Class<Applicant>>,
|
||||
Candidate: '' as Ref<Class<Candidate>>,
|
||||
Candidates: '' as Ref<Class<Candidates>>,
|
||||
Vacancy: '' as Ref<Class<Vacancy>>
|
||||
},
|
||||
mixin: {
|
||||
Candidate: '' as Ref<Mixin<Candidate>>
|
||||
},
|
||||
icon: {
|
||||
RecruitApplication: '' as Asset,
|
||||
Vacancy: '' as Asset,
|
||||
|
||||
@@ -94,8 +94,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <DialogHeader {space} {object} {newValue} {resume} create={true} on:save={createCandidate}/> -->
|
||||
|
||||
<Card
|
||||
label={task.string.CreateTask}
|
||||
okAction={createTask}
|
||||
|
||||
Reference in New Issue
Block a user