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, name: recruit.string.Vacancies,
description: recruit.string.ManageVacancyStatuses, description: recruit.string.ManageVacancyStatuses,
icon: recruit.component.TemplatesIcon icon: recruit.component.TemplatesIcon,
editor: recruit.component.VacancyTemplateEditor
}, },
recruit.space.VacancyTemplates recruit.space.VacancyTemplates
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ export default mergeIds(taskId, task, {
CreateProject: '' as AnyComponent, CreateProject: '' as AnyComponent,
EditIssue: '' as AnyComponent, EditIssue: '' as AnyComponent,
TaskPresenter: '' as AnyComponent, TaskPresenter: '' as AnyComponent,
KanbanTemplatePresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent, KanbanCard: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent, TemplatesIcon: '' as AnyComponent,
StatePresenter: '' 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) @Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
attachments!: number 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-3 { margin-bottom: .75rem; }
.mb-4 { margin-bottom: 1rem; } .mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; } .mb-6 { margin-bottom: 1.5rem; }
.mb-9 { margin-bottom: 2.25rem; }
.mb-10 { margin-bottom: 2.5rem; } .mb-10 { margin-bottom: 2.5rem; }
.mx-1 { margin: 0 .25rem; } .mx-1 { margin: 0 .25rem; }
.mx-2 { margin: 0 .5rem; } .mx-2 { margin: 0 .5rem; }

View File

@ -53,4 +53,8 @@
<symbol id="skills" viewBox="0 0 24 24"> <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" /> <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>
<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> </svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

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

View File

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

View File

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

View File

@ -15,10 +15,11 @@
<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, { generateId, getCurrentAccount, Ref } from '@hcengineering/core' import core, { FindResult, generateId, getCurrentAccount, Ref } from '@hcengineering/core'
import { Card, getClient, UserBox } from '@hcengineering/presentation' import { Card, createQuery, getClient, UserBox } from '@hcengineering/presentation'
import task, { createKanban, KanbanTemplate } from '@hcengineering/task' import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
import { Button, Component, createFocusManager, EditBox, FocusHandler, IconAttachment } from '@hcengineering/ui' import { Button, Component, createFocusManager, EditBox, FocusHandler, IconAttachment } from '@hcengineering/ui'
import tracker, { IssueStatus, IssueTemplate } from '@hcengineering/tracker'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import recruit from '../plugin' import recruit from '../plugin'
import { Vacancy as VacancyClass } from '@hcengineering/recruit' import { Vacancy as VacancyClass } from '@hcengineering/recruit'
@ -28,10 +29,10 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let name: string = '' let name: string = ''
const description: string = '' let template: KanbanTemplate | undefined
let fullDescription: string = ''
let templateId: Ref<KanbanTemplate> | undefined let templateId: Ref<KanbanTemplate> | undefined
let objectId: Ref<VacancyClass> = generateId() let objectId: Ref<VacancyClass> = generateId()
let issueTemplates: FindResult<IssueTemplate>
export let company: Ref<Organization> | undefined export let company: Ref<Organization> | undefined
export let preserveCompany: boolean = false export let preserveCompany: boolean = false
@ -41,6 +42,15 @@
} }
const client = getClient() 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 () { async function createVacancy () {
if ( if (
@ -54,9 +64,10 @@
recruit.class.Vacancy, recruit.class.Vacancy,
core.space.Space, core.space.Space,
{ {
...template,
name, name,
description, description: template?.shortDescription ?? '',
fullDescription, fullDescription: template?.description ?? '',
private: false, private: false,
archived: false, archived: false,
company, company,
@ -65,6 +76,45 @@
objectId 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 createKanban(client, id, templateId)
await descriptionBox.createAttachments() await descriptionBox.createAttachments()
@ -117,20 +167,6 @@
showNavigate={false} showNavigate={false}
create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }} 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 <Component
is={task.component.KanbanTemplateSelector} is={task.component.KanbanTemplateSelector}
props={{ props={{
@ -143,6 +179,19 @@
}} }}
/> />
</svelte:fragment> </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"> <svelte:fragment slot="footer">
<Button <Button
icon={IconAttachment} 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 recruit from './plugin'
import { objectIdProvider, objectLinkProvider, getApplicationTitle } from './utils' import { objectIdProvider, objectLinkProvider, getApplicationTitle } from './utils'
import VacancyList from './components/VacancyList.svelte' import VacancyList from './components/VacancyList.svelte'
import VacancyTemplateEditor from './components/VacancyTemplateEditor.svelte'
async function createOpinion (object: Doc): Promise<void> { async function createOpinion (object: Doc): Promise<void> {
showPopup(CreateOpinion, { space: object.space, review: object._id }) showPopup(CreateOpinion, { space: object.space, review: object._id })
@ -289,7 +290,8 @@ export default async (): Promise<Resources> => ({
ApplicantFilter, ApplicantFilter,
VacancyList VacancyList,
VacancyTemplateEditor
}, },
completion: { completion: {
ApplicationQuery: async ( ApplicationQuery: async (

View File

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

View File

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

View File

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

View File

@ -61,8 +61,8 @@
<div class="antiComponent"> <div class="antiComponent">
<div class="ac-header short divide"> <div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={task.icon.ManageStatuses} size={'medium'} /></div> <div class="ac-header__icon"><Icon icon={task.icon.ManageTemplates} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.ManageStatuses} /></div> <div class="ac-header__title"><Label label={setting.string.ManageTemplates} /></div>
</div> </div>
<div class="ac-body columns hScroll"> <div class="ac-body columns hScroll">
<div class="ac-column"> <div class="ac-column">
@ -75,7 +75,11 @@
</div> </div>
<div class="ac-column max"> <div class="ac-column max">
{#if template !== undefined} {#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} {/if}
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -20,7 +20,7 @@
<symbol id='todo-uncheck' viewBox="0 0 16 16"> <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"/> <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>
<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"/> <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"/> <ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol> </symbol>

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,7 @@
<div class="description"> <div class="description">
<StyledTextBox <StyledTextBox
bind:content={object.description} bind:content={object.description}
placeholder={plugin.string.TaskDescriptionPlaceholder} placeholder={plugin.string.DescriptionPlaceholder}
on:value={onChangeDescription} on:value={onChangeDescription}
/> />
</div> </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 { Ref, Space, SortingOrder, Class } from '@hcengineering/core'
import core from '@hcengineering/core' import core from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation' 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 task, { calcRank } from '@hcengineering/task'
import StatesEditor from '../state/StatesEditor.svelte' import StatesEditor from '../state/StatesEditor.svelte'
export let kanban: KanbanTemplate export let kanban: KanbanTemplate
export let folder: KanbanTemplateSpace
let states: StateTemplate[] = [] let states: StateTemplate[] = []
let doneStates: DoneStateTemplate[] = [] let doneStates: DoneStateTemplate[] = []
@ -119,6 +127,8 @@
</script> </script>
<StatesEditor <StatesEditor
template={kanban}
space={folder}
{states} {states}
{wonStates} {wonStates}
{lostStates} {lostStates}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
{ {
"string": { "string": {
"Cancel": "Отменить", "Cancel": "Отменить",
"Templates": "Шаблоны", "Templates": "Текстовые шаблоны",
"TemplatesHeader": "ШАБЛОНЫ", "TemplatesHeader": "ТЕКСТОВЫЕ ШАБЛОНЫ",
"CreateTemplate": "СОЗДАТЬ ШАБЛОН", "CreateTemplate": "СОЗДАТЬ ШАБЛОН",
"SaveTemplate": "Сохранить шаблон", "SaveTemplate": "Сохранить шаблон",
"EditTemplate": "Редактировать шаблон", "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"> <script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources' import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { Employee } from '@hcengineering/contact' 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 { Card, getClient, KeyedAttribute, SpaceSelector } from '@hcengineering/presentation'
import tags, { TagElement } from '@hcengineering/tags' import tags, { TagElement } from '@hcengineering/tags'
import { IssuePriority, IssueTemplate, Project, Sprint, Team } from '@hcengineering/tracker' import { IssuePriority, IssueTemplate, Project, Sprint, Team } from '@hcengineering/tracker'
@ -35,6 +35,7 @@
export let assignee: Ref<Employee> | null = null export let assignee: Ref<Employee> | null = null
export let project: Ref<Project> | null = $activeProject ?? null export let project: Ref<Project> | null = $activeProject ?? null
export let sprint: Ref<Sprint> | null = $activeSprint ?? null export let sprint: Ref<Sprint> | null = $activeSprint ?? null
export let relatedTo: Doc | undefined
let labels: TagElement[] = [] let labels: TagElement[] = []
@ -50,7 +51,8 @@
children: [], children: [],
labels: [], labels: [],
comments: 0, comments: 0,
attachments: 0 attachments: 0,
relations: []
} }
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -92,7 +94,8 @@
children: object.children, children: object.children,
comments: 0, comments: 0,
attachments: 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) 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 { Issue, Team } from '@hcengineering/tracker'
import { showPopup } from '@hcengineering/ui' import { showPopup } from '@hcengineering/ui'
import CreateIssue from './components/CreateIssue.svelte' import CreateIssue from './components/CreateIssue.svelte'
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
import Inbox from './components/inbox/Inbox.svelte' import Inbox from './components/inbox/Inbox.svelte'
import Active from './components/issues/Active.svelte' import Active from './components/issues/Active.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.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 TimeSpendReport from './components/issues/timereport/TimeSpendReport.svelte'
import RelatedIssues from './components/issues/related/RelatedIssues.svelte' import RelatedIssues from './components/issues/related/RelatedIssues.svelte'
import RelatedIssueTemplates from './components/issues/related/RelatedIssueTemplates.svelte'
import ProjectSelector from './components/ProjectSelector.svelte' import ProjectSelector from './components/ProjectSelector.svelte'
@ -196,6 +198,7 @@ export default async (): Promise<Resources> => ({
IssuePreview, IssuePreview,
RelationsPopup, RelationsPopup,
CreateIssue, CreateIssue,
CreateIssueTemplate,
Sprints, Sprints,
SprintPresenter, SprintPresenter,
SprintStatusPresenter, SprintStatusPresenter,
@ -208,6 +211,7 @@ export default async (): Promise<Resources> => ({
SubIssuesSelector, SubIssuesSelector,
GrowPresenter, GrowPresenter,
RelatedIssues, RelatedIssues,
RelatedIssueTemplates,
ProjectSelector, ProjectSelector,
IssueTemplates, IssueTemplates,
IssueTemplatePresenter, IssueTemplatePresenter,

View File

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

View File

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