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;
text-overflow: ellipsis;
cursor: pointer;
font-weight: inherit;
&.inline {
display: inline-flex;

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Employee } from '@hcengineering/contact'
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 { createEventDispatcher } from 'svelte'
import hr from '../plugin'
@ -82,6 +82,7 @@
placeholder={hr.string.TeamLead}
bind:value={lead}
allowDeselect
showNavigate={false}
titleDeselect={hr.string.UnAssignLead}
/>
</svelte:fragment>

View File

@ -20,7 +20,7 @@
import { OK, Status } from '@hcengineering/platform'
import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@hcengineering/presentation'
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 lead from '../plugin'
@ -152,6 +152,7 @@
label={lead.string.Assignee}
bind:value={assignee}
allowDeselect
showNavigate={false}
titleDeselect={lead.string.UnAssign}
/>
{#if !preserveCustomer}

View File

@ -9,6 +9,7 @@
"CreateVacancy": "Create Vacancy",
"Vacancy": "Vacancy",
"VacancyCreateLabel": "Vacancy",
"CompanyCreateLabel": "Company",
"VacancyPlaceholder": "Software Engineer",
"CreateAnApplication": "New Application",
"NoApplicationsForTalent": "There are no applications for this talent.",
@ -103,7 +104,10 @@
"PerformMatch": "Match",
"MoveApplication": "Move to another 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": {
"TalentRequired": "Please select talent",

View File

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

View File

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

View File

@ -38,6 +38,7 @@
EditableAvatar,
getClient,
getUserDraft,
InlineAttributeBar,
KeyedAttribute,
MessageBox,
PDFViewer,
@ -257,9 +258,22 @@
remote: object.remote
}
const id = await client.createDoc(contact.class.Person, contact.space.Contacts, candidate, candidateId)
await client.createMixin(
id as Ref<Person>,
// Store all extra values.
for (const [k, v] of Object.entries(object)) {
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.space.Contacts,
recruit.mixin.Candidate,
@ -267,10 +281,10 @@
)
if (resume.uuid !== undefined) {
client.addCollection(
applyOps.addCollection(
attachment.class.Attachment,
contact.space.Contacts,
id,
candidateId,
contact.class.Person,
'attachments',
{
@ -283,7 +297,7 @@
)
}
for (const channel of channels) {
await client.addCollection(
await applyOps.addCollection(
contact.class.Channel,
contact.space.Contacts,
candidateId,
@ -312,7 +326,7 @@
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,
color: skill.color,
tag: skill.tag,
@ -320,8 +334,10 @@
})
}
await applyOps.commit()
if (!createMore) {
dispatch('close', id)
dispatch('close', candidateId)
}
resetObject()
saveDraft()
@ -706,6 +722,15 @@
/>
</div>
{/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 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">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import contact, { Organization } from '@hcengineering/contact'
import core, { FindResult, generateId, getCurrentAccount, Ref, SortingOrder } from '@hcengineering/core'
import { Card, createQuery, getClient, UserBox } from '@hcengineering/presentation'
import core, { Data, FindResult, generateId, getCurrentAccount, Ref, SortingOrder } from '@hcengineering/core'
import { Card, createQuery, getClient, InlineAttributeBar, MessageBox, UserBox } from '@hcengineering/presentation'
import { Vacancy as VacancyClass } from '@hcengineering/recruit'
import tags from '@hcengineering/tags'
import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
@ -28,7 +28,15 @@
IssueTemplateData,
Project
} 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 recruit from '../plugin'
import Company from './icons/Company.svelte'
@ -42,11 +50,25 @@
let objectId: Ref<VacancyClass> = generateId()
let issueTemplates: FindResult<IssueTemplate>
let fullDescription: string = template?.description ?? ''
let fullDescription: string = ''
export let company: Ref<Organization> | undefined
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 {
return name === '' && templateId !== undefined
}
@ -56,11 +78,9 @@
const client = getClient()
const templateQ = createQuery()
$: templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => {
template = result[0]
if (!changed || descriptionBox?.isEmptyContent()) {
changed = false
fullDescription = template?.description ?? fullDescription
}
const { _class, _id, description, ...templateData } = result[0]
vacancyData = { ...(templateData as unknown as Data<VacancyClass>), fullDescription: description }
fullDescription = description ?? ''
})
const issueTemplatesQ = createQuery()
@ -142,7 +162,7 @@
recruit.class.Vacancy,
core.space.Space,
{
...template,
...vacancyData,
name,
description: template?.shortDescription ?? '',
fullDescription,
@ -174,6 +194,27 @@
const manager = createFocusManager()
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>
<FocusHandler {manager} />
@ -199,7 +240,7 @@
/>
</div>
</div>
{#key template?.description}
{#key vacancyData?.fullDescription}
<AttachmentStyledBox
bind:this={descriptionBox}
{objectId}
@ -241,9 +282,17 @@
template: templateId,
focusIndex: 4
}}
on:change={(evt) => {
templateId = evt.detail
}}
on:change={handleTemplateChange}
/>
</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 slot="footer">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,6 +49,8 @@ import { writable } from 'svelte/store'
import plugin from './plugin'
import { noCategory } from './viewOptions'
export { getFiltredKeys, isCollectionAttr } from '@hcengineering/presentation'
/**
* 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
}
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 {
key: KeyedAttribute
category: AttributeCategory
@ -505,10 +487,6 @@ export function categorizeFields (
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 {
loc = loc ?? getCurrentLocation()
loc.fragment = undefined