Vacancy templates (#2351)

Signed-off-by: muhtimur <timur.mukhamedishin@xored.com>
This commit is contained in:
Timur Mukhamedishin 2022-11-09 18:24:02 +07:00 committed by GitHub
parent bd1079cb53
commit cdbead6584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 378 additions and 72 deletions

View File

@ -531,7 +531,8 @@ export function createModel (builder: Builder): void {
{
name: recruit.string.Vacancies,
description: recruit.string.ManageVacancyStatuses,
icon: recruit.component.TemplatesIcon
icon: recruit.component.TemplatesIcon,
editor: recruit.component.VacancyTemplateEditor
},
recruit.space.VacancyTemplates
)

View File

@ -142,6 +142,8 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Kanba
kanbanId: recruit.template.DefaultVacancy,
space: recruit.space.VacancyTemplates as Ref<Doc> as Ref<Space>,
title: 'Default vacancy',
description: '',
shortDescription: '',
states: defaultKanban.states,
doneStates: defaultKanban.doneStates
})

View File

@ -87,7 +87,8 @@ export default mergeIds(recruitId, recruit, {
OpinionPresenter: '' as AnyComponent,
NewCandidateHeader: '' as AnyComponent,
ApplicantFilter: '' as AnyComponent,
VacancyList: '' as AnyComponent
VacancyList: '' as AnyComponent,
VacancyTemplateEditor: '' as AnyComponent
},
template: {
DefaultVacancy: '' as Ref<KanbanTemplate>,

View File

@ -163,14 +163,14 @@ export function createModel (builder: Builder): void {
core.space.Model,
{
name: 'statuses',
label: setting.string.ManageStatuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses,
label: setting.string.ManageTemplates,
icon: task.icon.ManageTemplates,
component: setting.component.ManageTemplates,
group: 'settings-editor',
secured: false,
order: 4000
},
setting.ids.ManageStatuses
setting.ids.ManageTemplates
)
builder.createDoc(
setting.class.WorkspaceSettingCategory,

View File

@ -180,7 +180,7 @@ export class TIssue extends TTask implements Issue {
@Index(IndexKind.FullText)
name!: string
@Prop(TypeMarkup(), task.string.TaskDescription)
@Prop(TypeMarkup(), task.string.Description)
@Index(IndexKind.FullText)
description!: string
@ -211,6 +211,7 @@ export class TKanbanTemplateSpace extends TDoc implements KanbanTemplateSpace {
name!: IntlString
description!: IntlString
icon!: AnyComponent
editor!: AnyComponent
}
@Model(task.class.StateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN, [task.interface.DocWithRank])
@ -244,6 +245,12 @@ export class TKanbanTemplate extends TDoc implements KanbanTemplate {
@Index(IndexKind.FullText)
title!: string
@Prop(TypeString(), task.string.Description)
description!: string
@Prop(TypeString(), task.string.ShortDescription)
shortDescription!: string
@Prop(Collection(task.class.StateTemplate), task.string.States)
statesC!: number
@ -347,7 +354,7 @@ export function createModel (builder: Builder): void {
core.space.Model,
{
label: task.string.States,
icon: task.icon.ManageStatuses,
icon: task.icon.ManageTemplates,
component: task.component.StatusTableView
},
task.viewlet.StatusTable
@ -381,6 +388,10 @@ export function createModel (builder: Builder): void {
presenter: task.component.TaskPresenter
})
builder.mixin(task.class.KanbanTemplate, core.class.Class, view.mixin.AttributePresenter, {
presenter: task.component.KanbanTemplatePresenter
})
builder.mixin(task.class.Issue, core.class.Class, view.mixin.ObjectEditor, {
editor: task.component.EditIssue
})

View File

@ -28,6 +28,8 @@ export interface KanbanTemplateData {
kanbanId: Ref<KanbanTemplate>
space: Ref<Space>
title: KanbanTemplate['title']
description?: string
shortDescription?: string
states: Pick<StateTemplate, 'title' | 'color'>[]
doneStates: (Pick<DoneStateTemplate, 'title'> & { isWon: boolean })[]
}
@ -157,6 +159,8 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Kanba
kanbanId: task.template.DefaultProject,
space: task.space.ProjectTemplates as Ref<Doc> as Ref<Space>,
title: 'Default project',
description: '',
shortDescription: '',
states: defaultKanban.states,
doneStates: defaultKanban.doneStates
})

View File

@ -46,6 +46,7 @@ export default mergeIds(taskId, task, {
CreateProject: '' as AnyComponent,
EditIssue: '' as AnyComponent,
TaskPresenter: '' as AnyComponent,
KanbanTemplatePresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent,
StatePresenter: '' as AnyComponent,

View File

@ -302,6 +302,9 @@ export class TIssueTemplate extends TDoc implements IssueTemplate {
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
attachments!: number
@Prop(ArrOf(TypeRef(core.class.TypeRelatedDocument)), tracker.string.RelatedTo)
relations!: RelatedDocument[]
}
/**

View File

@ -397,6 +397,7 @@ input.search {
.mb-3 { margin-bottom: .75rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.mb-9 { margin-bottom: 2.25rem; }
.mb-10 { margin-bottom: 2.5rem; }
.mx-1 { margin: 0 .25rem; }
.mx-2 { margin: 0 .5rem; }

View File

@ -53,4 +53,8 @@
<symbol id="skills" viewBox="0 0 24 24">
<path d="M22.8,8.7c0-0.3-0.2-0.6-0.5-0.7L11.8,3.8c-0.2-0.1-0.4-0.1-0.6,0L0.7,8C0.4,8.1,0.2,8.4,0.2,8.7 s0.2,0.6,0.5,0.7L5,11.1v6c0,0.2,0.1,0.4,0.2,0.5l0.5-0.5c-0.5,0.5-0.5,0.5-0.5,0.5l0,0l0,0l0,0l0,0c0,0,0.1,0.1,0.1,0.1 c0.1,0.1,0.2,0.2,0.3,0.3C6,18.3,6.5,18.6,7,19c1.1,0.6,2.7,1.3,4.5,1.3c1.8,0,3.4-0.7,4.5-1.3c0.5-0.3,1-0.6,1.3-0.9 c0.2-0.1,0.3-0.2,0.3-0.3c0,0,0.1-0.1,0.1-0.1l0,0l0,0l0,0l0,0l-0.5-0.5c0.5,0.5,0.5,0.5,0.5,0.5c0.1-0.1,0.2-0.3,0.2-0.5v-6 l3.3-1.3v3.6c0,0.4,0.3,0.8,0.8,0.8c0.4,0,0.8-0.3,0.8-0.8L22.8,8.7C22.8,8.7,22.8,8.7,22.8,8.7C22.8,8.7,22.8,8.7,22.8,8.7z M16.5,16.8c0,0-0.1,0.1-0.1,0.1c-0.3,0.2-0.6,0.5-1.1,0.8c-0.9,0.6-2.2,1.1-3.7,1.1c-1.5,0-2.8-0.5-3.7-1.1 c-0.5-0.3-0.8-0.5-1.1-0.8c-0.1,0-0.1-0.1-0.1-0.1v-5.1l4.7,1.9c0.2,0.1,0.4,0.1,0.6,0l4.7-1.9V16.8z M11.5,12.1L3,8.7l8.5-3.4 L20,8.7L11.5,12.1z" />
</symbol>
<symbol id="issue" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -54,7 +54,7 @@
"PersonFirstNamePlaceholder": "John",
"PersonLastNamePlaceholder": "Appleseed",
"PersonLocationPlaceholder": "Location",
"ManageVacancyStatuses": "Manage vacancy statuses",
"ManageVacancyStatuses": "Manage vacancy templates",
"EditVacancy": "Edit",
"FullDescription": "Full description",
"CreateReview": "Schedule an Review",

View File

@ -54,7 +54,7 @@
"PersonFirstNamePlaceholder": "John",
"PersonLastNamePlaceholder": "Appleseed",
"PersonLocationPlaceholder": "Местоположение",
"ManageVacancyStatuses": "Управление статусами вакансии",
"ManageVacancyStatuses": "Управление шаблонами вакансии",
"EditVacancy": "Редактировать",
"FullDescription": "Детальное описание",

View File

@ -29,7 +29,8 @@ loadMetadata(recruit.icon, {
CreateCandidate: `${icons}#new-candidate`,
AssignedToMe: `${icons}#assignedToMe`,
Reviews: `${icons}#reviews`,
Skills: `${icons}#skills`
Skills: `${icons}#skills`,
Issue: `${icons}#issue`
})
addStringsLoader(recruitId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -15,10 +15,11 @@
<script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import contact, { Organization } from '@hcengineering/contact'
import core, { generateId, getCurrentAccount, Ref } from '@hcengineering/core'
import { Card, getClient, UserBox } from '@hcengineering/presentation'
import core, { FindResult, generateId, getCurrentAccount, Ref } from '@hcengineering/core'
import { Card, createQuery, getClient, UserBox } from '@hcengineering/presentation'
import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
import { Button, Component, createFocusManager, EditBox, FocusHandler, IconAttachment } from '@hcengineering/ui'
import tracker, { IssueStatus, IssueTemplate } from '@hcengineering/tracker'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import { Vacancy as VacancyClass } from '@hcengineering/recruit'
@ -28,10 +29,10 @@
const dispatch = createEventDispatcher()
let name: string = ''
const description: string = ''
let fullDescription: string = ''
let template: KanbanTemplate | undefined
let templateId: Ref<KanbanTemplate> | undefined
let objectId: Ref<VacancyClass> = generateId()
let issueTemplates: FindResult<IssueTemplate>
export let company: Ref<Organization> | undefined
export let preserveCompany: boolean = false
@ -41,6 +42,15 @@
}
const client = getClient()
const templateQ = createQuery()
$: templateQ.query(task.class.KanbanTemplate, { _id: templateId }, (result) => {
template = result[0]
})
const issueTemplatesQ = createQuery()
$: issueTemplatesQ.query(tracker.class.IssueTemplate, { 'relations._id': templateId }, async (result) => {
issueTemplates = result
})
async function createVacancy () {
if (
@ -54,9 +64,10 @@
recruit.class.Vacancy,
core.space.Space,
{
...template,
name,
description,
fullDescription,
description: template?.shortDescription ?? '',
fullDescription: template?.description ?? '',
private: false,
archived: false,
company,
@ -65,6 +76,45 @@
objectId
)
for (const issueTemplate of issueTemplates) {
const incResult = await client.updateDoc(
tracker.class.Team,
core.space.Space,
issueTemplate.space,
{
$inc: { sequence: 1 }
},
true
)
await client.addCollection(
tracker.class.Issue,
issueTemplate.space,
tracker.ids.NoParent,
tracker.class.Issue,
'subIssues',
{
title: issueTemplate.title,
description: issueTemplate.description,
assignee: issueTemplate.assignee,
project: issueTemplate.project,
sprint: issueTemplate.sprint,
number: (incResult as any).object.sequence,
status: '' as Ref<IssueStatus>,
priority: issueTemplate.priority,
rank: '',
comments: 0,
subIssues: 0,
dueDate: null,
parents: [],
reportedTime: 0,
estimation: issueTemplate.estimation,
reports: 0,
relations: [{ _id: id, _class: recruit.class.Vacancy }],
childInfo: []
}
)
}
await createKanban(client, id, templateId)
await descriptionBox.createAttachments()
@ -117,20 +167,6 @@
showNavigate={false}
create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }}
/>
</svelte:fragment>
<AttachmentStyledBox
bind:this={descriptionBox}
{objectId}
_class={recruit.class.Vacancy}
space={objectId}
alwaysEdit
showButtons={false}
maxHeight={'card'}
bind:content={fullDescription}
placeholder={recruit.string.FullDescription}
/>
<svelte:fragment slot="pool">
<Component
is={task.component.KanbanTemplateSelector}
props={{
@ -143,6 +179,19 @@
}}
/>
</svelte:fragment>
{#key template?.description}
<AttachmentStyledBox
bind:this={descriptionBox}
{objectId}
_class={recruit.class.Vacancy}
space={objectId}
alwaysEdit
showButtons={false}
maxHeight={'card'}
content={template?.description ?? ''}
placeholder={recruit.string.FullDescription}
/>
{/key}
<svelte:fragment slot="footer">
<Button
icon={IconAttachment}

View File

@ -0,0 +1,95 @@
<!--
// 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 { AttributesBar, getClient } from '@hcengineering/presentation'
import { Button, Component, EditBox, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { StyledTextBox } from '@hcengineering/text-editor'
import { KanbanTemplate } from '@hcengineering/task'
import { getFiltredKeys } from '@hcengineering/view-resources/src/utils'
import tracker from '@hcengineering/tracker'
import recruit from '../plugin'
export let template: KanbanTemplate
const client = getClient()
const hierarchy = client.getHierarchy()
const customKeys = getFiltredKeys(hierarchy, template._class, []).filter((key) => key.attr.isCustom)
async function onDescriptionChange (value: string) {
await client.update(template, { description: value })
}
async function onShortDescriptionChange (value: string) {
await client.update(template, { shortDescription: value })
}
</script>
<div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={recruit.string.Description} />
</div>
<div class="mt-3">
<EditBox
kind={'small-style'}
bind:value={template.shortDescription}
on:change={() => onShortDescriptionChange(template.shortDescription ?? '')}
/>
</div>
<div class="mt-9">
<div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={recruit.string.FullDescription} />
</div>
<div class="mt-3">
{#key template._id}
<StyledTextBox
emphasized
alwaysEdit
showButtons={false}
content={template.description ?? ''}
on:value={(evt) => onDescriptionChange(evt.detail)}
/>
{/key}
</div>
</div>
<div class="antiSection mt-9 mb-9">
<div class="antiSection-header">
<div class="antiSection-header__icon">
<Icon icon={recruit.icon.Issue} size={'small'} />
</div>
<span class="antiSection-header__title">
<Label label={recruit.string.RelatedIssues} />
</span>
<div class="buttons-group small-gap">
<Button
id="add-sub-issue"
width="min-content"
icon={IconAdd}
label={undefined}
labelParams={{ subIssues: 0 }}
kind={'transparent'}
size={'small'}
on:click={() => showPopup(tracker.component.CreateIssueTemplate, { relatedTo: template })}
/>
</div>
</div>
<div class="flex-row">
<Component is={tracker.component.RelatedIssueTemplates} props={{ object: template }} />
</div>
</div>
{#if customKeys && customKeys.length > 0}
<div class="antiSection mb-9">
<AttributesBar object={template} _class={template._class} keys={customKeys} />
</div>
{/if}

View File

@ -52,6 +52,7 @@ import VacancyPresenter from './components/VacancyPresenter.svelte'
import recruit from './plugin'
import { objectIdProvider, objectLinkProvider, getApplicationTitle } from './utils'
import VacancyList from './components/VacancyList.svelte'
import VacancyTemplateEditor from './components/VacancyTemplateEditor.svelte'
async function createOpinion (object: Doc): Promise<void> {
showPopup(CreateOpinion, { space: object.space, review: object._id })
@ -289,7 +290,8 @@ export default async (): Promise<Resources> => ({
ApplicantFilter,
VacancyList
VacancyList,
VacancyTemplateEditor
},
completion: {
ApplicationQuery: async (

View File

@ -131,7 +131,8 @@ const recruit = plugin(recruitId, {
CreateCandidate: '' as Asset,
AssignedToMe: '' as Asset,
Reviews: '' as Asset,
Skills: '' as Asset
Skills: '' as Asset,
Issue: '' as Asset
},
space: {
VacancyTemplates: '' as Ref<KanbanTemplateSpace>,

View File

@ -1,7 +1,7 @@
{
"string": {
"Setting": "Setting",
"ManageStatuses": "Manage Statuses",
"ManageTemplates": "Manage Templates",
"Integrations": "Integrations",
"Support": "Support",
"Privacy": "Privacy",

View File

@ -1,7 +1,7 @@
{
"string": {
"Setting": "Настройки",
"ManageStatuses": "Управление статусами",
"ManageTemplates": "Управление шаблонами",
"Integrations": "Интеграции",
"Support": "Поддержка",
"Privacy": "Конфиденциальность",

View File

@ -61,8 +61,8 @@
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={task.icon.ManageStatuses} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.ManageStatuses} /></div>
<div class="ac-header__icon"><Icon icon={task.icon.ManageTemplates} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.ManageTemplates} /></div>
</div>
<div class="ac-body columns hScroll">
<div class="ac-column">
@ -75,7 +75,11 @@
</div>
<div class="ac-column max">
{#if template !== undefined}
<Component is={task.component.KanbanTemplateEditor} props={{ kanban: template }} on:delete={onDeleteState} />
<Component
is={task.component.KanbanTemplateEditor}
props={{ kanban: template, folder }}
on:delete={onDeleteState}
/>
{/if}
</div>
</div>

View File

@ -30,7 +30,7 @@ import Password from './components/Password.svelte'
import Privacy from './components/Privacy.svelte'
import Profile from './components/Profile.svelte'
import Settings from './components/Settings.svelte'
import ManageStatuses from './components/statuses/ManageStatuses.svelte'
import ManageTemplates from './components/statuses/ManageTemplates.svelte'
import Support from './components/Support.svelte'
import Terms from './components/Terms.svelte'
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
@ -79,7 +79,7 @@ export default async (): Promise<Resources> => ({
Support,
Privacy,
Terms,
ManageStatuses,
ManageTemplates,
ClassSetting,
StringTypeEditor,
HyperlinkTypeEditor,

View File

@ -85,7 +85,7 @@ export default plugin(settingId, {
Password: '' as Ref<Doc>,
Setting: '' as Ref<Doc>,
Integrations: '' as Ref<Doc>,
ManageStatuses: '' as Ref<Doc>,
ManageTemplates: '' as Ref<Doc>,
Support: '' as Ref<Doc>,
Privacy: '' as Ref<Doc>,
Terms: '' as Ref<Doc>,
@ -108,7 +108,7 @@ export default plugin(settingId, {
Password: '' as AnyComponent,
WorkspaceSettings: '' as AnyComponent,
Integrations: '' as AnyComponent,
ManageStatuses: '' as AnyComponent,
ManageTemplates: '' as AnyComponent,
Support: '' as AnyComponent,
Privacy: '' as AnyComponent,
Terms: '' as AnyComponent,
@ -119,7 +119,7 @@ export default plugin(settingId, {
Setting: '' as IntlString,
WorkspaceSetting: '' as IntlString,
Integrations: '' as IntlString,
ManageStatuses: '' as IntlString,
ManageTemplates: '' as IntlString,
Support: '' as IntlString,
Privacy: '' as IntlString,
Terms: '' as IntlString,

View File

@ -20,7 +20,7 @@
<symbol id='todo-uncheck' viewBox="0 0 16 16">
<path d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"/>
</symbol>
<symbol id="manage-statuses" viewBox="0 0 16 16">
<symbol id="manage-templates" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,5 +1,8 @@
{
"string": {
"Description": "Description",
"DescriptionPlaceholder": "Description",
"ShortDescription": "Short description",
"StartDate": "Start date",
"DueDate": "Due date",
"TaskState": "State",
@ -14,7 +17,6 @@
"Task": "Task",
"TaskParent": "Parent",
"IssueName": "Name",
"TaskDescription": "Description",
"TaskComments": "Comments",
"TaskLabels": "Labels",
"TaskAssignee": "Assignee",
@ -34,7 +36,6 @@
"CreateProject": "New Project",
"ProjectNamePlaceholder": "Project name",
"TaskNamePlaceholder": "The boring task",
"TaskDescriptionPlaceholder": "Description",
"TodoDescriptionPlaceholder": "todo...",
"MakePrivate": "Make Private",
"MakePrivateDescription": "Only members can see it",
@ -76,6 +77,7 @@
"Assigned": "Assigned to me",
"TodoItems": "Todos",
"Dashboard": "Dashboard",
"AllTime": "All time"
"AllTime": "All time",
"RelatedIssues": "Related processes"
}
}

View File

@ -1,5 +1,8 @@
{
"string": {
"Description": "Описание",
"DescriptionPlaceholder": "Описание",
"ShortDescription": "Короткое описание",
"StartDate": "Начало",
"DueDate": "Срок",
"TaskState": "Статус",
@ -14,7 +17,6 @@
"Task": "Задача",
"TaskParent": "Родитель",
"IssueName": "Название",
"TaskDescription": "Описание",
"TaskComments": "Комментарии",
"TaskLabels": "Ярлыки",
"TaskAssignee": "Назначен",
@ -34,7 +36,6 @@
"CreateProject": "Новый проект",
"ProjectNamePlaceholder": "Название проекта",
"TaskNamePlaceholder": "Задача",
"TaskDescriptionPlaceholder": "Описание",
"TodoDescriptionPlaceholder": "todo...",
"MakePrivate": "Сделать личным",
"MakePrivateDescription": "Только пользователи могут видеть это",
@ -76,6 +77,7 @@
"Assigned": "Назначения",
"TodoItems": "Todos",
"Dashboard": "Дашборд",
"AllTime": "Все время"
"AllTime": "Все время",
"RelatedIssues": "Связанные процессы"
}
}

View File

@ -22,7 +22,7 @@ loadMetadata(task.icon, {
Kanban: `${icons}#kanban`,
TodoCheck: `${icons}#todo-check`,
TodoUnCheck: `${icons}#todo-uncheck`,
ManageStatuses: `${icons}#manage-statuses`,
ManageTemplates: `${icons}#manage-templates`,
TaskState: `${icons}#task-state`,
Dashboard: `${icons}#dashboard`
})

View File

@ -53,7 +53,7 @@
>
<Label label={task.string.AllTime} />
</div>
{#each values as value, i}
{#each values as value}
<div
class="menu-item"
on:click={() => {

View File

@ -59,7 +59,7 @@
<div class="description">
<StyledTextBox
bind:content={object.description}
placeholder={plugin.string.TaskDescriptionPlaceholder}
placeholder={plugin.string.DescriptionPlaceholder}
on:value={onChangeDescription}
/>
</div>

View File

@ -0,0 +1,29 @@
<!--
// 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 { getClient } from '@hcengineering/presentation'
import type { KanbanTemplate } from '@hcengineering/task'
import { Label } from '@hcengineering/ui'
export let value: KanbanTemplate
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
</script>
<span class="label nowrap">
{#if shortLabel}
<Label label={shortLabel} />-{/if}{value.title}</span
>

View File

@ -18,12 +18,20 @@
import { Ref, Space, SortingOrder, Class } from '@hcengineering/core'
import core from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import type { State, DoneStateTemplate, KanbanTemplate, StateTemplate, DoneState } from '@hcengineering/task'
import type {
State,
DoneStateTemplate,
KanbanTemplate,
StateTemplate,
DoneState,
KanbanTemplateSpace
} from '@hcengineering/task'
import task, { calcRank } from '@hcengineering/task'
import StatesEditor from '../state/StatesEditor.svelte'
export let kanban: KanbanTemplate
export let folder: KanbanTemplateSpace
let states: StateTemplate[] = []
let doneStates: DoneStateTemplate[] = []
@ -119,6 +127,8 @@
</script>
<StatesEditor
template={kanban}
space={folder}
{states}
{wonStates}
{lostStates}

View File

@ -51,7 +51,7 @@
<DropdownLabels
{focusIndex}
{items}
icon={task.icon.ManageStatuses}
icon={task.icon.ManageTemplates}
bind:selected={selectedItem}
label={plugin.string.States}
/>

View File

@ -103,7 +103,7 @@
<svelte:fragment slot="title">
<div class="antiTitle icon-wrapper">
<div class="wrapped-icon">
<Icon icon={task.icon.ManageStatuses} size={'small'} />
<Icon icon={task.icon.ManageTemplates} size={'small'} />
</div>
<div class="title-wrapper">
<span class="wrapped-title">

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { Class, Ref } from '@hcengineering/core'
import { AttributeEditor, getClient } from '@hcengineering/presentation'
import type { DoneState, State } from '@hcengineering/task'
import type { DoneState, KanbanTemplate, KanbanTemplateSpace, State } from '@hcengineering/task'
import {
CircleButton,
IconAdd,
@ -24,7 +24,8 @@
Label,
showPopup,
getPlatformColor,
eventToHTMLElement
eventToHTMLElement,
Component
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { ColorsPopup } from '@hcengineering/view-resources'
@ -34,6 +35,8 @@
import Won from '../icons/Won.svelte'
import Lost from '../icons/Lost.svelte'
export let template: KanbanTemplate | undefined = undefined
export let space: KanbanTemplateSpace | undefined = undefined
export let states: State[] = []
export let wonStates: DoneState[] = []
export let lostStates: DoneState[] = []
@ -85,6 +88,9 @@
}
</script>
{#if space?.editor}
<Component is={space.editor} props={{ template }} />
{/if}
<div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={task.string.ActiveStates} />
<CircleButton

View File

@ -32,6 +32,7 @@ import StatePresenter from './components/state/StatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte'
import TaskHeader from './components/TaskHeader.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanTemplatePresenter from './components/KanbanTemplatePresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
@ -51,6 +52,7 @@ export default async (): Promise<Resources> => ({
component: {
CreateProject,
TaskPresenter,
KanbanTemplatePresenter,
EditIssue,
KanbanCard,
Dashboard,

View File

@ -21,13 +21,14 @@ import { AnyComponent } from '@hcengineering/ui'
export default mergeIds(taskId, task, {
string: {
CreateProject: '' as IntlString,
Description: '' as IntlString,
DescriptionPlaceholder: '' as IntlString,
ShortDescription: '' as IntlString,
ProjectName: '' as IntlString,
ProjectNamePlaceholder: '' as IntlString,
TaskCreateLabel: '' as IntlString,
TaskAssignee: '' as IntlString,
TaskNamePlaceholder: '' as IntlString,
TaskDescription: '' as IntlString,
TaskDescriptionPlaceholder: '' as IntlString,
States: '' as IntlString,
DoneStates: '' as IntlString,
TodoDescriptionPlaceholder: '' as IntlString,
@ -67,6 +68,7 @@ export default mergeIds(taskId, task, {
CantStatusDeleteError: '' as IntlString,
Archive: '' as IntlString,
Unarchive: '' as IntlString,
RelatedIssues: '' as IntlString,
Tasks: '' as IntlString,
Assigned: '' as IntlString,

View File

@ -167,6 +167,8 @@ export interface LostStateTemplate extends DoneStateTemplate, LostState {}
*/
export interface KanbanTemplate extends Doc {
title: string
description?: string
shortDescription?: string
statesC: number
doneStatesC: number
}
@ -178,6 +180,7 @@ export interface KanbanTemplateSpace extends Doc {
name: IntlString
description: IntlString
icon: AnyComponent
editor?: AnyComponent
}
/**
@ -255,7 +258,7 @@ const task = plugin(taskId, {
Kanban: '' as Asset,
TodoCheck: '' as Asset,
TodoUnCheck: '' as Asset,
ManageStatuses: '' as Asset,
ManageTemplates: '' as Asset,
TaskState: '' as Asset,
Dashboard: '' as Asset
},

View File

@ -1,8 +1,8 @@
{
"string": {
"Cancel": "Cancel",
"Templates": "Templates",
"TemplatesHeader": "TEMPLATES",
"Templates": "Text Templates",
"TemplatesHeader": "TEXT TEMPLATES",
"CreateTemplate": "CREATE TEMPLATE",
"SaveTemplate": "Save template",
"EditTemplate": "Edit template",

View File

@ -1,8 +1,8 @@
{
"string": {
"Cancel": "Отменить",
"Templates": "Шаблоны",
"TemplatesHeader": "ШАБЛОНЫ",
"Templates": "Текстовые шаблоны",
"TemplatesHeader": "ТЕКСТОВЫЕ ШАБЛОНЫ",
"CreateTemplate": "СОЗДАТЬ ШАБЛОН",
"SaveTemplate": "Сохранить шаблон",
"EditTemplate": "Редактировать шаблон",

View File

@ -0,0 +1,59 @@
<!--
// 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 { Doc, DocumentQuery } from '@hcengineering/core'
import presentation, { createQuery } from '@hcengineering/presentation'
import { IssueTemplate } from '@hcengineering/tracker'
import { Label, Spinner } from '@hcengineering/ui'
import tracker from '../../../plugin'
import IssueTemplatePresenter from '../../templates/IssueTemplatePresenter.svelte'
export let object: Doc
let templates: IssueTemplate[] = []
let query: DocumentQuery<IssueTemplate>
$: query = { 'relations._id': object._id, 'relations._class': object._class }
const templatesQ = createQuery()
$: templatesQ.query(tracker.class.IssueTemplate, query, async (result) => (templates = result))
</script>
<div class="mt-1">
{#if templates !== undefined}
{#if templates.length > 0}
{#each templates as template}
<div class="flex-between row p-3">
<IssueTemplatePresenter value={template} />
</div>
{/each}
{:else}
<div class="p-1">
<Label label={presentation.string.NoMatchesFound} />
</div>
{/if}
{:else}
<div class="flex-center pt-3">
<Spinner />
</div>
{/if}
</div>
<style lang="scss">
.row {
position: relative;
border-bottom: 1px solid var(--divider-color);
}
</style>

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { Employee } from '@hcengineering/contact'
import { Data, generateId, Ref } from '@hcengineering/core'
import { Data, Doc, generateId, Ref } from '@hcengineering/core'
import { Card, getClient, KeyedAttribute, SpaceSelector } from '@hcengineering/presentation'
import tags, { TagElement } from '@hcengineering/tags'
import { IssuePriority, IssueTemplate, Project, Sprint, Team } from '@hcengineering/tracker'
@ -35,6 +35,7 @@
export let assignee: Ref<Employee> | null = null
export let project: Ref<Project> | null = $activeProject ?? null
export let sprint: Ref<Sprint> | null = $activeSprint ?? null
export let relatedTo: Doc | undefined
let labels: TagElement[] = []
@ -50,7 +51,8 @@
children: [],
labels: [],
comments: 0,
attachments: 0
attachments: 0,
relations: []
}
const dispatch = createEventDispatcher()
@ -92,7 +94,8 @@
children: object.children,
comments: 0,
attachments: 0,
labels: labels.map((it) => it._id)
labels: labels.map((it) => it._id),
relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : []
}
await client.createDoc(tracker.class.IssueTemplate, _space, value, objectId)

View File

@ -19,6 +19,7 @@ import { ObjectSearchResult } from '@hcengineering/presentation'
import { Issue, Team } from '@hcengineering/tracker'
import { showPopup } from '@hcengineering/ui'
import CreateIssue from './components/CreateIssue.svelte'
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import Active from './components/issues/Active.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
@ -83,6 +84,7 @@ import ReportedTimeEditor from './components/issues/timereport/ReportedTimeEdito
import TimeSpendReport from './components/issues/timereport/TimeSpendReport.svelte'
import RelatedIssues from './components/issues/related/RelatedIssues.svelte'
import RelatedIssueTemplates from './components/issues/related/RelatedIssueTemplates.svelte'
import ProjectSelector from './components/ProjectSelector.svelte'
@ -196,6 +198,7 @@ export default async (): Promise<Resources> => ({
IssuePreview,
RelationsPopup,
CreateIssue,
CreateIssueTemplate,
Sprints,
SprintPresenter,
SprintStatusPresenter,
@ -208,6 +211,7 @@ export default async (): Promise<Resources> => ({
SubIssuesSelector,
GrowPresenter,
RelatedIssues,
RelatedIssueTemplates,
ProjectSelector,
IssueTemplates,
IssueTemplatePresenter,

View File

@ -258,6 +258,8 @@ export interface IssueTemplate extends Doc, IssueTemplateData {
// Discussion stuff
comments: number
attachments?: number
relations?: RelatedDocument[]
}
/**
@ -385,8 +387,10 @@ export default plugin(trackerId, {
Tracker: '' as AnyComponent,
TrackerApp: '' as AnyComponent,
RelatedIssues: '' as AnyComponent,
RelatedIssueTemplates: '' as AnyComponent,
EditIssue: '' as AnyComponent,
CreateIssue: '' as AnyComponent
CreateIssue: '' as AnyComponent,
CreateIssueTemplate: '' as AnyComponent
},
issueStatusCategory: {
Backlog: '' as Ref<IssueStatusCategory>,

View File

@ -69,7 +69,7 @@ test.describe('contact tests', () => {
// Click text=Edit template
})
test('manage-status-templates', async ({ page }) => {
test('manage-templates', async ({ page }) => {
// Go to http://localhost:8083/workbench%3Acomponent%3AWorkbenchApp/sanity-ws
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws`)
// Click #profile-button
@ -79,8 +79,8 @@ test.describe('contact tests', () => {
await page.click('button:has-text("Settings")')
// Click text=Workspace Notifications >> button
await page.click('text=Workspace Notifications >> button')
// Click button:has-text("Manage Statuses")
await page.click('text="Manage Statuses"')
// Click button:has-text("Manage Templates")
await page.click('text="Manage Templates"')
// Click text=Vacancies
await page.click('text=Vacancies')
// Click #create-template div