TSK-857: Create company button (#2762)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-03-17 22:57:36 +07:00 committed by GitHub
parent 8e3a6989cd
commit 34b8a6cdd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 363 additions and 82 deletions

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { Class, Data, Doc, Ref } from '@hcengineering/core'
import { InlineAttributeBarEditor } from '..'
import { KeyedAttribute } from '../attributes'
import { getClient, getFiltredKeys, isCollectionAttr } from '../utils'
export let object: Doc | Data<Doc>
export let _class: Ref<Class<Doc>>
export let toClass: Ref<Class<Doc>> | undefined = undefined
export let ignoreKeys: string[] = []
export let extraKeys: string[] = []
export let extraProps: Record<string, any> = {}
let keys: KeyedAttribute[]
const client = getClient()
function updateKeys (_class: Ref<Class<Doc>>, ignoreKeys: string[], to: Ref<Class<Doc>> | undefined): void {
const filtredKeys = getFiltredKeys(client.getHierarchy(), _class, ignoreKeys, to)
keys = filtredKeys.filter(
(key) => (extraKeys.includes(key.key) || !isCollectionAttr(client.getHierarchy(), key)) && !key.attr.readonly
)
}
$: updateKeys(_class, ignoreKeys, toClass)
</script>
{#each keys as key (typeof key === 'string' ? key : key.key)}
<InlineAttributeBarEditor {key} {_class} {object} readonly={false} draft={true} on:update {extraProps} />
{/each}

View File

@ -0,0 +1,79 @@
<!--
// 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 type { Class, Doc, Ref } from '@hcengineering/core'
import { AnySvelteComponent } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { getAttribute, KeyedAttribute, updateAttribute } from '../attributes'
import { getAttributeEditor, getClient } from '../utils'
export let key: KeyedAttribute | string
export let object: Doc | Record<string, any>
export let _class: Ref<Class<Doc>>
export let maxWidth: string | undefined = undefined
export let focus: boolean = false
export let readonly = false
export let draft = false
export let extraProps: Record<string, any> = {}
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
let editor: Promise<void | AnySvelteComponent> | undefined
function onChange (value: any) {
const doc = object as Doc
if (draft) {
;(doc as any)[attributeKey] = value
dispatch('update', { key, value })
} else {
updateAttribute(client, doc, _class, { key: attributeKey, attr: attribute }, value)
}
}
$: attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr
$: attributeKey = typeof key === 'string' ? key : key.key
$: editor = getAttributeEditor(client, _class, key)
$: isReadonly = (attribute.readonly ?? false) || readonly
</script>
{#if editor}
{#await editor then instance}
{#if instance}
<div class="flex min-w-0">
<svelte:component
this={instance}
{...extraProps}
readonly={isReadonly}
label={attribute?.label}
placeholder={attribute?.label}
kind={'link'}
size={'large'}
width={'100%'}
justify={'left'}
type={attribute?.type}
{maxWidth}
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
space={object.space}
{onChange}
{focus}
{object}
/>
</div>
{/if}
{/await}
{/if}

View File

@ -47,6 +47,7 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
cursor: pointer; cursor: pointer;
font-weight: inherit;
&.inline { &.inline {
display: inline-flex; display: inline-flex;

View File

@ -19,6 +19,8 @@ import { presentationId } from './plugin'
export * from './attributes' export * from './attributes'
export { default as AttributeBarEditor } from './components/AttributeBarEditor.svelte' export { default as AttributeBarEditor } from './components/AttributeBarEditor.svelte'
export { default as AttributeEditor } from './components/AttributeEditor.svelte' export { default as AttributeEditor } from './components/AttributeEditor.svelte'
export { default as InlineAttributeBarEditor } from './components/InlineAttributeBarEditor.svelte'
export { default as InlineAttributeBar } from './components/InlineAttributeBar.svelte'
export { default as AttributesBar } from './components/AttributesBar.svelte' export { default as AttributesBar } from './components/AttributesBar.svelte'
export { default as Avatar } from './components/Avatar.svelte' export { default as Avatar } from './components/Avatar.svelte'
export { default as AssigneeBox } from './components/AssigneeBox.svelte' export { default as AssigneeBox } from './components/AssigneeBox.svelte'

View File

@ -402,3 +402,33 @@ export async function getAttributeEditor (
console.error(getAttributeEditorNotFoundError(_class, key, ex)) console.error(getAttributeEditorNotFoundError(_class, key, ex))
} }
} }
function filterKeys (hierarchy: Hierarchy, keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
/**
* @public
*/
export function getFiltredKeys (
hierarchy: Hierarchy,
objectClass: Ref<Class<Doc>>,
ignoreKeys: string[],
to?: Ref<Class<Doc>>
): KeyedAttribute[] {
const keys = [...hierarchy.getAllAttributes(objectClass, to).entries()]
.filter(([, value]) => value.hidden !== true)
.map(([key, attr]) => ({ key, attr }))
return filterKeys(hierarchy, keys, ignoreKeys)
}
/**
* @public
*/
export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}

View File

@ -14,8 +14,8 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Channel, findContacts, Organization } from '@hcengineering/contact' import { Channel, findContacts, Organization } from '@hcengineering/contact'
import { AttachedData, generateId, WithLookup } from '@hcengineering/core' import { AttachedData, generateId, Ref, TxOperations, WithLookup } from '@hcengineering/core'
import { Card, getClient } from '@hcengineering/presentation' import { Card, getClient, InlineAttributeBar } from '@hcengineering/presentation'
import { Button, createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui' import { Button, createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import contact from '../plugin' import contact from '../plugin'
@ -23,11 +23,13 @@
import Company from './icons/Company.svelte' import Company from './icons/Company.svelte'
import OrganizationPresenter from './OrganizationPresenter.svelte' import OrganizationPresenter from './OrganizationPresenter.svelte'
export let onCreate: ((orgId: Ref<Organization>, client: TxOperations) => Promise<void>) | undefined = undefined
export function canClose (): boolean { export function canClose (): boolean {
return object.name === '' return object.name === ''
} }
const id = generateId() const id: Ref<Organization> = generateId()
const object: Organization = { const object: Organization = {
name: '' name: ''
@ -51,6 +53,9 @@
} }
) )
} }
if (onCreate !== undefined) {
await onCreate?.(id, client)
}
dispatch('close', id) dispatch('close', id)
} }
@ -90,12 +95,22 @@
/> />
</div> </div>
<svelte:fragment slot="pool"> <svelte:fragment slot="pool">
<div class="flex-row-center flex-wrap">
<ChannelsDropdown <ChannelsDropdown
bind:value={channels} bind:value={channels}
focusIndex={10} focusIndex={10}
editable editable
highlighted={matchedChannels.map((it) => it.provider)} highlighted={matchedChannels.map((it) => it.provider)}
/> />
<InlineAttributeBar
_class={contact.class.Organization}
{object}
toClass={contact.class.Contact}
on:update
extraProps={{ showNavigate: false }}
/>
</div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
{#if matches.length > 0} {#if matches.length > 0}

View File

@ -29,6 +29,7 @@
export let justify: 'left' | 'center' = 'center' export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined export let width: string | undefined = undefined
export let readonly = false export let readonly = false
export let showNavigate = true
$: _class = type?.to ?? contact.class.Employee $: _class = type?.to ?? contact.class.Employee
@ -50,4 +51,5 @@
titleDeselect={contact.string.Cancel} titleDeselect={contact.string.Cancel}
bind:value bind:value
on:change={(e) => onChange(e.detail)} on:change={(e) => onChange(e.detail)}
{showNavigate}
/> />

View File

@ -28,6 +28,7 @@
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'left' export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = 'min-content' export let width: string | undefined = 'min-content'
export let showNavigate = true
</script> </script>
<UserBox <UserBox
@ -43,4 +44,5 @@
on:change={(evt) => { on:change={(evt) => {
onChange(evt.detail) onChange(evt.detail)
}} }}
{showNavigate}
/> />

View File

@ -57,6 +57,7 @@ import PersonEditor from './components/PersonEditor.svelte'
import PersonPresenter from './components/PersonPresenter.svelte' import PersonPresenter from './components/PersonPresenter.svelte'
import PersonRefPresenter from './components/PersonRefPresenter.svelte' import PersonRefPresenter from './components/PersonRefPresenter.svelte'
import SocialEditor from './components/SocialEditor.svelte' import SocialEditor from './components/SocialEditor.svelte'
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
import contact from './plugin' import contact from './plugin'
import { import {
employeeSort, employeeSort,
@ -86,7 +87,9 @@ export {
EditPerson, EditPerson,
EmployeeRefPresenter, EmployeeRefPresenter,
AccountArrayEditor, AccountArrayEditor,
AccountBox AccountBox,
CreateOrganization,
ExpandRightDouble
} }
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({ const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { Employee } from '@hcengineering/contact' import { Employee } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { Card, getClient, SpaceSelector, EmployeeBox } from '@hcengineering/presentation' import { Card, EmployeeBox, getClient, SpaceSelector } from '@hcengineering/presentation'
import { Button, createFocusManager, EditBox, FocusHandler } from '@hcengineering/ui' import { Button, createFocusManager, EditBox, FocusHandler } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import hr from '../plugin' import hr from '../plugin'
@ -82,6 +82,7 @@
placeholder={hr.string.TeamLead} placeholder={hr.string.TeamLead}
bind:value={lead} bind:value={lead}
allowDeselect allowDeselect
showNavigate={false}
titleDeselect={hr.string.UnAssignLead} titleDeselect={hr.string.UnAssignLead}
/> />
</svelte:fragment> </svelte:fragment>

View File

@ -20,7 +20,7 @@
import { OK, Status } from '@hcengineering/platform' import { OK, Status } from '@hcengineering/platform'
import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@hcengineering/presentation' import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@hcengineering/presentation'
import task, { calcRank } from '@hcengineering/task' import task, { calcRank } from '@hcengineering/task'
import { createFocusManager, EditBox, FocusHandler, Label, Status as StatusControl, Button } from '@hcengineering/ui' import { Button, createFocusManager, EditBox, FocusHandler, Label, Status as StatusControl } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import lead from '../plugin' import lead from '../plugin'
@ -152,6 +152,7 @@
label={lead.string.Assignee} label={lead.string.Assignee}
bind:value={assignee} bind:value={assignee}
allowDeselect allowDeselect
showNavigate={false}
titleDeselect={lead.string.UnAssign} titleDeselect={lead.string.UnAssign}
/> />
{#if !preserveCustomer} {#if !preserveCustomer}

View File

@ -9,6 +9,7 @@
"CreateVacancy": "Create Vacancy", "CreateVacancy": "Create Vacancy",
"Vacancy": "Vacancy", "Vacancy": "Vacancy",
"VacancyCreateLabel": "Vacancy", "VacancyCreateLabel": "Vacancy",
"CompanyCreateLabel": "Company",
"VacancyPlaceholder": "Software Engineer", "VacancyPlaceholder": "Software Engineer",
"CreateAnApplication": "New Application", "CreateAnApplication": "New Application",
"NoApplicationsForTalent": "There are no applications for this talent.", "NoApplicationsForTalent": "There are no applications for this talent.",
@ -103,7 +104,10 @@
"PerformMatch": "Match", "PerformMatch": "Match",
"MoveApplication": "Move to another vacancy", "MoveApplication": "Move to another vacancy",
"SearchVacancy": "Search vacancy...", "SearchVacancy": "Search vacancy...",
"Organizations": "Companies" "Organizations": "Companies",
"TemplateReplace": "You you replace selected template?",
"TemplateReplaceConfirm": "All field changes will be override by template values"
}, },
"status": { "status": {
"TalentRequired": "Please select talent", "TalentRequired": "Please select talent",

View File

@ -9,6 +9,7 @@
"CreateVacancy": "Создать вакансию", "CreateVacancy": "Создать вакансию",
"Vacancy": "Вакансия", "Vacancy": "Вакансия",
"VacancyCreateLabel": "Вакансию", "VacancyCreateLabel": "Вакансию",
"CompanyCreateLabel": "Компания",
"VacancyPlaceholder": "Разработчик", "VacancyPlaceholder": "Разработчик",
"CreateAnApplication": "Новый Кандидат", "CreateAnApplication": "Новый Кандидат",
"NoApplicationsForTalent": "Нет кандидатов для данного таланта.", "NoApplicationsForTalent": "Нет кандидатов для данного таланта.",
@ -105,7 +106,9 @@
"PerformMatch": "Сопоставить", "PerformMatch": "Сопоставить",
"MoveApplication": "Поменять Вакансию", "MoveApplication": "Поменять Вакансию",
"SearchVacancy": "Найти вакансию...", "SearchVacancy": "Найти вакансию...",
"Organizations": "Компании" "Organizations": "Компании",
"TemplateReplace": "Вы хотите заменить выбранный шаблон?",
"TemplateReplaceConfirm": "Все внесенные изменения в будут заменены значениями из шаблоном"
}, },
"status": { "status": {
"TalentRequired": "Пожалуйста выберите таланта", "TalentRequired": "Пожалуйста выберите таланта",

View File

@ -17,7 +17,7 @@
import chunter from '@hcengineering/chunter' import chunter from '@hcengineering/chunter'
import type { Contact, Employee, Person } from '@hcengineering/contact' import type { Contact, Employee, Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact' import contact from '@hcengineering/contact'
import ExpandRightDouble from '@hcengineering/contact-resources/src/components/icons/ExpandRightDouble.svelte' import { ExpandRightDouble } from '@hcengineering/contact-resources'
import { import {
Account, Account,
Class, Class,
@ -36,6 +36,7 @@
createQuery, createQuery,
EmployeeBox, EmployeeBox,
getClient, getClient,
InlineAttributeBar,
SpaceSelect, SpaceSelect,
UserBox UserBox
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
@ -140,6 +141,7 @@
recruit.mixin.Candidate, recruit.mixin.Candidate,
'applications', 'applications',
{ {
...doc,
state: state._id, state: state._id,
doneState: null, doneState: null,
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
@ -284,6 +286,8 @@
let btn: HTMLButtonElement let btn: HTMLButtonElement
let descriptionBox: AttachmentStyledBox let descriptionBox: AttachmentStyledBox
const assignAttr = getClient().getHierarchy().getAttribute(recruit.class.Applicant, 'assignee')
</script> </script>
<FocusHandler {manager} /> <FocusHandler {manager} />
@ -378,10 +382,11 @@
{#key doc} {#key doc}
<EmployeeBox <EmployeeBox
focusIndex={2} focusIndex={2}
label={recruit.string.AssignRecruiter} label={assignAttr.label}
placeholder={recruit.string.Recruiters} placeholder={assignAttr.label}
bind:value={doc.assignee} bind:value={doc.assignee}
allowDeselect allowDeselect
showNavigate={false}
titleDeselect={recruit.string.UnAssignRecruiter} titleDeselect={recruit.string.UnAssignRecruiter}
/> />
{#if states.length > 0} {#if states.length > 0}
@ -416,6 +421,16 @@
</div> </div>
</Button> </Button>
{/if} {/if}
{#if vacancy}
<InlineAttributeBar
_class={recruit.class.Applicant}
object={doc}
toClass={task.class.Task}
ignoreKeys={['assignee']}
extraProps={{ showNavigate: false }}
/>
{/if}
{/key} {/key}
</svelte:fragment> </svelte:fragment>
</Card> </Card>

View File

@ -38,6 +38,7 @@
EditableAvatar, EditableAvatar,
getClient, getClient,
getUserDraft, getUserDraft,
InlineAttributeBar,
KeyedAttribute, KeyedAttribute,
MessageBox, MessageBox,
PDFViewer, PDFViewer,
@ -257,9 +258,22 @@
remote: object.remote remote: object.remote
} }
const id = await client.createDoc(contact.class.Person, contact.space.Contacts, candidate, candidateId) // Store all extra values.
await client.createMixin( for (const [k, v] of Object.entries(object)) {
id as Ref<Person>, if (v != null && k !== 'createOn' && k !== 'avatar') {
if (client.getHierarchy().getAttribute(recruit.mixin.Candidate, k).attributeOf === recruit.mixin.Candidate) {
;(candidateData as any)[k] = v
} else {
;(candidate as any)[k] = v
}
}
}
const applyOps = client.apply(candidateId)
await applyOps.createDoc(contact.class.Person, contact.space.Contacts, candidate, candidateId)
await applyOps.createMixin(
candidateId as Ref<Person>,
contact.class.Person, contact.class.Person,
contact.space.Contacts, contact.space.Contacts,
recruit.mixin.Candidate, recruit.mixin.Candidate,
@ -267,10 +281,10 @@
) )
if (resume.uuid !== undefined) { if (resume.uuid !== undefined) {
client.addCollection( applyOps.addCollection(
attachment.class.Attachment, attachment.class.Attachment,
contact.space.Contacts, contact.space.Contacts,
id, candidateId,
contact.class.Person, contact.class.Person,
'attachments', 'attachments',
{ {
@ -283,7 +297,7 @@
) )
} }
for (const channel of channels) { for (const channel of channels) {
await client.addCollection( await applyOps.addCollection(
contact.class.Channel, contact.class.Channel,
contact.space.Contacts, contact.space.Contacts,
candidateId, candidateId,
@ -312,7 +326,7 @@
category: findTagCategory(skill.title, categories) category: findTagCategory(skill.title, categories)
}) })
} }
await client.addCollection(skill._class, skill.space, candidateId, recruit.mixin.Candidate, 'skills', { await applyOps.addCollection(skill._class, skill.space, candidateId, recruit.mixin.Candidate, 'skills', {
title: skill.title, title: skill.title,
color: skill.color, color: skill.color,
tag: skill.tag, tag: skill.tag,
@ -320,8 +334,10 @@
}) })
} }
await applyOps.commit()
if (!createMore) { if (!createMore) {
dispatch('close', id) dispatch('close', candidateId)
} }
resetObject() resetObject()
saveDraft() saveDraft()
@ -706,6 +722,15 @@
/> />
</div> </div>
{/if} {/if}
<div class="flex flex-grow flex-wrap">
<InlineAttributeBar
_class={recruit.mixin.Candidate}
{object}
toClass={contact.class.Contact}
ignoreKeys={['onsite', 'remote']}
extraProps={{ showNavigate: false }}
/>
</div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">

View File

@ -0,0 +1,34 @@
<!--
// 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 contact, { Organization } from '@hcengineering/contact'
import { CreateOrganization } from '@hcengineering/contact-resources'
import { Ref, TxOperations } from '@hcengineering/core'
import recruit from '../plugin'
let createOrg: CreateOrganization
export function canClose (): boolean {
return createOrg.canClose()
}
async function onCreate (org: Ref<Organization>, client: TxOperations): Promise<void> {
await client.createMixin(org, contact.class.Organization, contact.space.Contacts, recruit.mixin.VacancyList, {
vacancies: 0
})
}
</script>
<CreateOrganization bind:this={createOrg} {onCreate} on:close />

View File

@ -15,8 +15,8 @@
<script lang="ts"> <script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources' import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import contact, { Organization } from '@hcengineering/contact' import contact, { Organization } from '@hcengineering/contact'
import core, { FindResult, generateId, getCurrentAccount, Ref, SortingOrder } from '@hcengineering/core' import core, { Data, FindResult, generateId, getCurrentAccount, Ref, SortingOrder } from '@hcengineering/core'
import { Card, createQuery, getClient, UserBox } from '@hcengineering/presentation' import { Card, createQuery, getClient, InlineAttributeBar, MessageBox, UserBox } from '@hcengineering/presentation'
import { Vacancy as VacancyClass } from '@hcengineering/recruit' import { Vacancy as VacancyClass } from '@hcengineering/recruit'
import tags from '@hcengineering/tags' import tags from '@hcengineering/tags'
import task, { createKanban, KanbanTemplate } from '@hcengineering/task' import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
@ -28,7 +28,15 @@
IssueTemplateData, IssueTemplateData,
Project Project
} from '@hcengineering/tracker' } from '@hcengineering/tracker'
import { Button, Component, createFocusManager, EditBox, FocusHandler, IconAttachment } from '@hcengineering/ui' import {
Button,
Component,
createFocusManager,
EditBox,
FocusHandler,
IconAttachment,
showPopup
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import recruit from '../plugin' import recruit from '../plugin'
import Company from './icons/Company.svelte' import Company from './icons/Company.svelte'
@ -42,11 +50,25 @@
let objectId: Ref<VacancyClass> = generateId() let objectId: Ref<VacancyClass> = generateId()
let issueTemplates: FindResult<IssueTemplate> let issueTemplates: FindResult<IssueTemplate>
let fullDescription: string = template?.description ?? '' let fullDescription: string = ''
export let company: Ref<Organization> | undefined export let company: Ref<Organization> | undefined
export let preserveCompany: boolean = false export let preserveCompany: boolean = false
let vacancyData: Data<VacancyClass> = {
archived: false,
description: '',
members: [],
name: '',
number: 0,
private: false,
attachments: 0,
comments: 0,
company: '' as Ref<Organization>,
fullDescription: '',
location: ''
}
export function canClose (): boolean { export function canClose (): boolean {
return name === '' && templateId !== undefined return name === '' && templateId !== undefined
} }
@ -56,11 +78,9 @@
const client = getClient() const client = getClient()
const templateQ = createQuery() const templateQ = createQuery()
$: templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => { $: templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => {
template = result[0] const { _class, _id, description, ...templateData } = result[0]
if (!changed || descriptionBox?.isEmptyContent()) { vacancyData = { ...(templateData as unknown as Data<VacancyClass>), fullDescription: description }
changed = false fullDescription = description ?? ''
fullDescription = template?.description ?? fullDescription
}
}) })
const issueTemplatesQ = createQuery() const issueTemplatesQ = createQuery()
@ -142,7 +162,7 @@
recruit.class.Vacancy, recruit.class.Vacancy,
core.space.Space, core.space.Space,
{ {
...template, ...vacancyData,
name, name,
description: template?.shortDescription ?? '', description: template?.shortDescription ?? '',
fullDescription, fullDescription,
@ -174,6 +194,27 @@
const manager = createFocusManager() const manager = createFocusManager()
let descriptionBox: AttachmentStyledBox let descriptionBox: AttachmentStyledBox
function handleTemplateChange (evt: CustomEvent<Ref<KanbanTemplate>>): void {
if (templateId == null) {
templateId = evt.detail
return
}
// Template is already specified, ask to replace.
showPopup(
MessageBox,
{
label: recruit.string.TemplateReplace,
message: recruit.string.TemplateReplaceConfirm
},
'top',
(result?: boolean) => {
if (result === true) {
templateId = evt.detail ?? undefined
}
}
)
}
</script> </script>
<FocusHandler {manager} /> <FocusHandler {manager} />
@ -199,7 +240,7 @@
/> />
</div> </div>
</div> </div>
{#key template?.description} {#key vacancyData?.fullDescription}
<AttachmentStyledBox <AttachmentStyledBox
bind:this={descriptionBox} bind:this={descriptionBox}
{objectId} {objectId}
@ -241,9 +282,17 @@
template: templateId, template: templateId,
focusIndex: 4 focusIndex: 4
}} }}
on:change={(evt) => { on:change={handleTemplateChange}
templateId = evt.detail />
}} </svelte:fragment>
<svelte:fragment slot="pool">
<InlineAttributeBar
_class={recruit.class.Vacancy}
object={vacancyData}
toClass={core.class.Space}
ignoreKeys={['fullDescription', 'company']}
extraProps={{ showNavigate: false }}
/> />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">

View File

@ -32,13 +32,13 @@
FilterBar, FilterBar,
FilterButton, FilterButton,
getViewOptions, getViewOptions,
viewOptionStore,
setActiveViewletId, setActiveViewletId,
TableBrowser, TableBrowser,
ViewletSettingButton ViewletSettingButton,
viewOptionStore
} from '@hcengineering/view-resources' } from '@hcengineering/view-resources'
import recruit from '../plugin' import recruit from '../plugin'
import CreateVacancy from './CreateVacancy.svelte' import CreateOrganization from './CreateOrganization.svelte'
import VacancyListApplicationsPopup from './organizations/VacancyListApplicationsPopup.svelte' import VacancyListApplicationsPopup from './organizations/VacancyListApplicationsPopup.svelte'
import VacancyListCountPresenter from './organizations/VacancyListCountPresenter.svelte' import VacancyListCountPresenter from './organizations/VacancyListCountPresenter.svelte'
import VacancyPopup from './organizations/VacancyPopup.svelte' import VacancyPopup from './organizations/VacancyPopup.svelte'
@ -123,7 +123,7 @@
} }
function showCreateDialog () { function showCreateDialog () {
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, 'top') showPopup(CreateOrganization, { space: recruit.space.CandidatesPublic }, 'top')
} }
const applicationSorting = (a: Doc, b: Doc) => const applicationSorting = (a: Doc, b: Doc) =>
(applications?.get(b._id as Ref<Organization>)?.count ?? 0) - (applications?.get(b._id as Ref<Organization>)?.count ?? 0) -
@ -252,7 +252,7 @@
<div class="ac-header-full" class:secondRow={twoRows}> <div class="ac-header-full" class:secondRow={twoRows}>
<Button <Button
icon={IconAdd} icon={IconAdd}
label={recruit.string.VacancyCreateLabel} label={recruit.string.CompanyCreateLabel}
size={'small'} size={'small'}
kind={'primary'} kind={'primary'}
on:click={showCreateDialog} on:click={showCreateDialog}
@ -277,8 +277,7 @@
config={createConfig(descr, preference)} config={createConfig(descr, preference)}
options={descr.options} options={descr.options}
query={{ query={{
...resultQuery, ...resultQuery
_id: { $in: Array.from(vacancies.keys()) }
}} }}
showNotification showNotification
/> />

View File

@ -44,6 +44,7 @@ export default mergeIds(recruitId, recruit, {
ApplicationCreateLabel: '' as IntlString, ApplicationCreateLabel: '' as IntlString,
Vacancy: '' as IntlString, Vacancy: '' as IntlString,
VacancyCreateLabel: '' as IntlString, VacancyCreateLabel: '' as IntlString,
CompanyCreateLabel: '' as IntlString,
SelectVacancy: '' as IntlString, SelectVacancy: '' as IntlString,
Talent: '' as IntlString, Talent: '' as IntlString,
TalentCreateLabel: '' as IntlString, TalentCreateLabel: '' as IntlString,
@ -118,7 +119,10 @@ export default mergeIds(recruitId, recruit, {
Match: '' as IntlString, Match: '' as IntlString,
PerformMatch: '' as IntlString, PerformMatch: '' as IntlString,
MoveApplication: '' as IntlString, MoveApplication: '' as IntlString,
Application: '' as IntlString Application: '' as IntlString,
TemplateReplace: '' as IntlString,
TemplateReplaceConfirm: '' as IntlString
}, },
space: { space: {
CandidatesPublic: '' as Ref<Space> CandidatesPublic: '' as Ref<Space>

View File

@ -266,13 +266,13 @@
"AddedAsBlocked": "Marked as blocked", "AddedAsBlocked": "Marked as blocked",
"AddedAsBlocking": "Marked as blocking", "AddedAsBlocking": "Marked as blocking",
"IssueTemplate": "Pattern", "IssueTemplate": "Template",
"IssueTemplates": "Patterns", "IssueTemplates": "Templates",
"NewProcess": "New Pattern", "NewProcess": "New Template",
"SaveProcess": "Save Pattern", "SaveProcess": "Save Template",
"NoIssueTemplate": "No Pattern", "NoIssueTemplate": "No Template",
"TemplateReplace": "You you replace applied pattern?", "TemplateReplace": "You you replace selected template?",
"TemplateReplaceConfirm": "All changes to task values will be override by template.", "TemplateReplaceConfirm": "All field changes will be override by template values",
"CurrentWorkDay": "Current Working Day", "CurrentWorkDay": "Current Working Day",
"PreviousWorkDay": "Previous Working Day", "PreviousWorkDay": "Previous Working Day",

View File

@ -272,7 +272,7 @@
"SaveProcess": "Сохранить шаблон", "SaveProcess": "Сохранить шаблон",
"NoIssueTemplate": "Шаблон не задан", "NoIssueTemplate": "Шаблон не задан",
"TemplateReplace": "Вы хотите заменить выбранный шаблон?", "TemplateReplace": "Вы хотите заменить выбранный шаблон?",
"TemplateReplaceConfirm": "Все внесенные изменения в задачу будут заменены шаблоном", "TemplateReplaceConfirm": "Все внесенные изменения в будут заменены значениями из шаблоном",
"CurrentWorkDay": "Текущий Рабочий День", "CurrentWorkDay": "Текущий Рабочий День",
"PreviousWorkDay": "Предыдущий Рабочий День", "PreviousWorkDay": "Предыдущий Рабочий День",

View File

@ -151,7 +151,9 @@
kind={'large-style'} kind={'large-style'}
focus focus
on:input={() => { on:input={() => {
if (isNew) {
identifier = name.toLocaleUpperCase().replaceAll(' ', '_').substring(0, 5) identifier = name.toLocaleUpperCase().replaceAll(' ', '_').substring(0, 5)
}
}} }}
/> />
<EditBox <EditBox
@ -221,6 +223,7 @@
kind="link-bordered" kind="link-bordered"
bind:value={defaultAssignee} bind:value={defaultAssignee}
titleDeselect={tracker.string.Unassigned} titleDeselect={tracker.string.Unassigned}
showNavigate={false}
showTooltip={{ label: tracker.string.DefaultAssignee }} showTooltip={{ label: tracker.string.DefaultAssignee }}
/> />
</div> </div>

View File

@ -14,18 +14,19 @@
--> -->
<script lang="ts"> <script lang="ts">
import { TypeDate } from '@hcengineering/core' import { TypeDate } from '@hcengineering/core'
// import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import { DateRangePresenter } from '@hcengineering/ui' import { DateRangePresenter } from '@hcengineering/ui'
export let value: number | null | undefined export let value: number | null | undefined
export let type: TypeDate | undefined export let type: TypeDate | undefined
// export let label: IntlString export let label: IntlString | undefined = undefined
export let onChange: (value: any) => void export let onChange: (value: any) => void
export let kind: 'no-border' | 'link' = 'no-border' export let kind: 'no-border' | 'link' = 'no-border'
</script> </script>
<DateRangePresenter <DateRangePresenter
{value} {value}
labelNull={label}
mode={type?.mode} mode={type?.mode}
noShift={!type?.withShift} noShift={!type?.withShift}
editable editable

View File

@ -49,6 +49,8 @@ import { writable } from 'svelte/store'
import plugin from './plugin' import plugin from './plugin'
import { noCategory } from './viewOptions' import { noCategory } from './viewOptions'
export { getFiltredKeys, isCollectionAttr } from '@hcengineering/presentation'
/** /**
* Define some properties to be used to show component until data is properly loaded. * Define some properties to be used to show component until data is properly loaded.
*/ */
@ -442,26 +444,6 @@ export function getCollectionCounter (hierarchy: Hierarchy, object: Doc, key: Ke
return (object as any)[key.key] ?? 0 return (object as any)[key.key] ?? 0
} }
function filterKeys (hierarchy: Hierarchy, keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
export function getFiltredKeys (
hierarchy: Hierarchy,
objectClass: Ref<Class<Doc>>,
ignoreKeys: string[],
to?: Ref<Class<Doc>>
): KeyedAttribute[] {
const keys = [...hierarchy.getAllAttributes(objectClass, to).entries()]
.filter(([, value]) => value.hidden !== true)
.map(([key, attr]) => ({ key, attr }))
return filterKeys(hierarchy, keys, ignoreKeys)
}
export interface CategoryKey { export interface CategoryKey {
key: KeyedAttribute key: KeyedAttribute
category: AttributeCategory category: AttributeCategory
@ -505,10 +487,6 @@ export function categorizeFields (
return result return result
} }
export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
function makeViewletKey (loc?: Location): string { function makeViewletKey (loc?: Location): string {
loc = loc ?? getCurrentLocation() loc = loc ?? getCurrentLocation()
loc.fragment = undefined loc.fragment = undefined